1. 3D Direct Application
1.1 Terrian Rendering
1.1.1 Terrian Rendering System
1.1.1.1 Frustum
- 지형 패치들 중 대다수는 카메라에 보이지 않음, 절두체 선별을 적용하면 성능이 개선될 가능성이 큼
- 한 패치의 테셀레이션 계수들이 모두 0이면 GPU는 그 패치를 더이상 처리하지 않고 폐기
- 시야 절두체의 평면, 패치를 감싸는 경계입체가 필요
void ExtractFrustumPlanes(XMFLOAT4 planes[6], CXMMATRIX M);
- 각 패치는 사각형이므로 축 정렬 경계상자를 경계입체로 사용
- 패치들은 xz평면에 규칙적으로 배치, 패치 제어점들의 x, z성분의 경계는 아주 간단히 구할 수 있음
- y 성분의 경계를 구하기 위해 전처리 단계를 밟을 필요가 있음
- 하나의 패치는 높이맵의 여러 원소들을 덮음, 각 패치마다 그 패치가 포괄하는 높이맵 원소들을 훑으면서 y 성분의 최댓 값과 최솟 값을 구해 그 최대, 최솟값을 패치의 상단 왼쪽 제어점에 저장
- 2차원 배열을 생성하여 x 성분에 y의 최소, y성분의 y의 쵀댓값을 저장
void Terrain::CalcAllPatchBoundsY()
{
_patchBoundsY.resize(_numPatchQuadFaces);
for (uint32 i = 0; i < _numPatchVertRows - 1; ++i)
for (uint32 j = 0; j < _numPatchVertCols - 1; ++j) CalcPatchBoundsY(i, j);
}
- 이 패치가 포괄하는 높이맵 원소들을 훑으면서 최대, 최소 높이를 구함
void Terrain::CalcPatchBoundsY(uint32 i, uint32 j)
{
uint32 x0 = j * CellsPerPatch; uint32 x1 = (j + 1) * CellsPerPatch;
uint32 y0 = i * CellsPerPatch; uint32 y1 = (i + 1) * CellsPerPatch;
float minY = +MathHelper::Infinity; float maxY = -MathHelper::Infinity;
for (uint32 y = y0; y <= y1; ++y)
{
for (uint32 x = x0; x <= x1; ++x)
{
uint32 k = y * _info.heightmapWidth + x;
minY = MathHelper::Min(minY, _heightmap[k]);
maxY = MathHelper::Max(maxY, _heightmap[k]);
}
}
uint32 patchID = i * (_numPatchVertCols - 1) + j;
_patchBoundsY[patchID] = XMFLOAT2(minY, maxY);
}
1.1.1.2 Hull Shader
- 상수 덮개 셰이더에서 패치마다 해당하는 최대, 최솟값을 이용하여 경계상자를 만듦
- 상자 대 절두체 교차 판정을 통해 현재 패치의 경계상자가 절두체 바깥에 있는 지 판정
- OBB 대 평면 판정의 특별한 경우이므로 경계상자가 축에 정렬되어 있으므로 반지름 r에 대한 공식을 사용
1.1.1.2.1 AABB Behind Plane
bool AabbBehindPlaneTest(float3 center, float3 extents, float4 plane);
- 상자 전체가 평면의 뒤쪽에 있으면 True 반환
- 상자의 중점이 평면 뒤쪽으로 거리 e 이상 떨어져 있으면 상자 전체가 평면의 음의 빈공간에 있음
float3 n = abs(plane.xyz); float r = dot(extents, n);
float s = dot(float4(center, 1.0f), plane);
return (s + r) < 0.0f;
1.1.1.2.2 AABB Outside Frustum
bool AabbOutsideFrustumTest(float3 center, float3 extents, float4 frustumPlanes[6]);
- 상자 전체가 절두체 바깥이면 True 반환
- 상자 전체가 절두체의 적어도 한 평면의 뒤쪽에 있다면 상자는 절두체 바깥에 있음
for (int i = 0; i < 6; ++i)
if (AabbBehindPlaneTest(center, extents, frustumPlanes[i])) return true;
return false;
1.1.2.2 Constant Hull Shader
PatchTess ConstantHS(InputPatch<VertexOut, 4> patch, uint patchID : SV_PrimitiveID);
- 절두체를 선별하기 위해 패치의 BoundsY의 첫 번째 제어점에 저장 (이전에 생성한 최대, 최소 저장 위치)
float minY = patch[0].BoundsY.x; float maxY = patch[0].BoundsY.y;
- 축 정렬 경계상자를 생성 및 AABB 중점 및 한계 표현 생성
- 패치의 2의 경우 왼쪽 하단 모퉁이 제어점, 1의 경우 오른쪽 상단 모퉁이 제어점
float3 vMin = float3(patch[2].PosW.x, minY, patch[2].PosW.z);
float3 vMax = float3(patch[1].PosW.x, maxY, patch[1].PosW.z);
float3 boxCenter = 0.5f * (vMin + vMax); float3 boxExtents = 0.5f * (vMax - vMin);
- 경계 상자가 안에 있는 지 확인하고 바깥에 있는 경우 모두 0으로 처리
if (AabbOutsideFrustumTest(boxCenter, boxExtents, gWorldFrustumPlanes))
{
pt.EdgeTess[0] = 0.0f; pt.EdgeTess[1] = 0.0f;
pt.EdgeTess[2] = 0.0f; pt.EdgeTess[3] = 0.0f;
pt.InsideTess[0] = 0.0f; pt.InsideTess[1] = 0.0f;
return pt;
}
1.1.2.3 Get Height
float Terrain::GetHeight(float x, float z) const
- 지형을 렌더링하는 응용 프로그램에 주어진 x, z 좌표성분에 해당하는 지형표면의 높이를 구해야하는 경우가 많음
- 그 높이는 지형 표면에 물체를 배치하거나, 카메라를 지형 표면보다 약간 위쪽에 배치하고자 할 때 유용
- 높이맵은 격자점들에서의 지형 정점의 높이를 제공, 필요한 것은 그 정점들 사이에 있는 지형의 한 위치의 높이
- 주어진 이산적인 높이맵 표본들을 적절히 보간함으로써 지형을 나타내는 연속적인 표면을 형성
- 지형을 하나의 삼각형 메시로 근사하므로 선형 보간을 사용하는 것이 바람직함
- 주어진 x, z 좌표가 속한 격자 낱칸이 무엇인지를 파악
- 지형 국소 공간을 낱칸 공간으로 변환 및 주어진 위치가 속한 행과 열을 구함
float c = (x + 0.5f * GetWidth()) / _info.cellSpacing;
float d = (z - 0.5f * GetDepth()) / -_info.cellSpacing;
int row = (int)floorf(d); int col = (int)floorf(c);
- 주어진 위치가 속한 낱칸을 알아낸 다음 그 낱칸의 네 정점의 높이를 높이맵에서 알아냄
// A -- B
// | / |
// | / |
// C -- D
float A = _heightmap[row * _info.heightmapWidth + col];
float B = _heightmap[row * _info.heightmapWidth + col + 1];
float C = _heightmap[(row + 1) * _info.heightmapWidth + col];
float D = _heightmap[(row + 1) * _info.heightmapWidth + col + 1];
- 이제 주어진 위치가 속한 낱칸과 네 정점의 높이들을 알게 됨
- 필요한 것은 표면의 그 위치에서의 높이를 구하는 것, 하나의 낱칸이 두가지 방향으로 기울수 있기에 까다로움
- 높이를 구하려면 그 위치가 낱칸의 두 삼각형 중 어떤 것에 있는지를 알아야 함
- 이를 위해 좌표를 한 낱칸 안의 공간을 규정하는 또 다른 좌표계로 변환, 이동으로만 구성된 간단한 변환
float s = c - (float)col; float t = d - (float)row;
- s와 t를 더해 1보다 작은 경우 위쪽 삼각형에 있는 것이고, 그렇지 않으면 아래쪽 삼각형에 있는 것
- 종점 A에서 시작하는 삼각형 두변의 벡터를 구해 u에 따라 s만큼 선형 보간하고 v를 따라 t만큼 선형 보간해서 삼각형 내부의 한점을 구함
- 그 점의 y 성분이 바로 주어진 x, z위치의 높이, 높이만 구하는 것이기 때문에 y 성분만 보간 후 다른 성분 무시
if (s + t <= 1.0f) { float uy = B - A; float vy = C - A; return A + s * uy + t * vy; }
else { float uy = C - D; float vy = B - D; return D + (1.0f - s) * uy + (1.0f - t) * vy; }
- 높이를 이용해 카메라를 지형 표면 조금 위쪽에 배치함으로써 지형 위를 걸어 다니는 듯한 상황을 연출할 수 있음
if (_walkCamMode)
{
XMFLOAT3 camPos = _camera.GetPosition();
float y = _terrain.GetHeight(camPos.x, camPos.z);
_camera.SetPosition(camPos.x, y + 2.0f, camPos.z);
}
_camera.UpdateViewMatrix();
'C++ Algorithm & Study > Game Math & DirectX 11' 카테고리의 다른 글
[Direct11] 42. 3D Direct Application - Particle System #2 (0) | 2023.07.03 |
---|---|
[Direct11] 41. 3D Direct Application - Particle System #1 (0) | 2023.07.03 |
[Direct11] 39. 3D Direct Application - Terrian Rendering #1 (0) | 2023.06.30 |
[Direct11] 38. 3D Direct Application - Displacement Mapping (0) | 2023.06.30 |
[Direct11] 37. 3D Direct Application - Normal Mapping (0) | 2023.06.30 |