GameChoi
Choi Programming
GameChoi
전체 방문자
오늘
어제
  • 분류 전체보기 (468)
    • C++ Algorithm & Study (184)
      • C++ & Algorithm Strategies (45)
      • Game Math & DirectX 11 (72)
      • Server + UE5 (29)
      • Lyra Clone Coding (37)
    • Create Game (284)
      • [Window API] Game Client & .. (55)
      • [DirectX] DirectX 2D & 3D (155)
      • [UE5] BLUEPRINT & C++ (74)
    • odds and ends (0)
      • English (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • protobuf
  • Algorithm Strategies
  • Game Room
  • Direct11
  • server
  • c++
  • core
  • GAME Client
  • Network Worker
  • Other Character
  • Player Move Packet
  • Player State
  • client
  • Destination Move Packet
  • job queue
  • UE5
  • session
  • Direct3D
  • Game Server
  • RPG Game

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
GameChoi

Choi Programming

[Direct11] 39. 3D Direct Application - Terrian Rendering #1
C++ Algorithm & Study/Game Math & DirectX 11

[Direct11] 39. 3D Direct Application - Terrian Rendering #1

2023. 6. 30. 19:07

1. 3D Direct Application

1.1 Terrian Rendering

1.1.1 Terrian Rendering System

1.1.1.1 Height Map

 - 지형의 산과 계곡에 대한 정보를 높이맵에서 가져옴

   - 높이맵은 일종의 2차원 배열으로 원소는 지형 격자의 해당 정점의 높이를 의미

     - 격자의 각 정점마다 높이맵의 원소 하나가 대응

 - 높이맵은 각 원소를 하나의 바이트로 저장, 하나의 높이는 최소 0, 최대 255

   - 지형의 높이들 사이의 전이를 보존하기에 충분하나 응용 프로그램 안에서는 이 구간의 높이들을 응용 프로그램이 렌더링할 3차원 세계의 축척에 맞게 비례시킬 필요가 잇음

     - 255미터가 넘는 산을 표현하지 못하므로 응용 프로그램으로 적재할 때 float로 설정

1.1.1.2 Smoothing

 - 8비트 높이맵을 지형 렌더링에 사용하는 것의 문제점은 표현 가능한 서로 다른 높이 단계들이 256개

   - 높이 값들으 그대로 사용하지 못하고 소수부가 절단된 값들을 사용, 따라서 더 거친 지형이 만들어짐

     - 이를 해결하기 위해 적절히 평활화한다면 소수부가 있는 모습을 얻는 것도 가능

 - 부동소수점 높이맵에 평활화 필터를 적용하여 인접한 원소들 사이의 극단적인 높이차를 줄임

   - 높이맵의 각 픽셀마다 그 픽셀과 이웃  픽셀 8개의 평균을 구해 새로운 높이로 사용

float Terrain::Average(int32 i, int32 j);

 - 부동소수점 높이맵인 2차원 배열의 원소를 전달받아 픽셀의 평균 높이를 계산하는 함수 생성

   - 위에서 말했듯이 ij 픽셀 및 그 이웃의 여덟 픽셀의 평균을 구함

     - 가장자리 픽셀들의 경우 만일 해당 방향의 이웃 픽셀이 없으면 그냥 평균에 포함시키지 않고 넘김

float avg = 0.0f; float num = 0.0f;
for (int32 m = i - 1; m <= i + 1; ++m)
{
    for (int32 n = j - 1; n <= j + 1; ++n)
        if (InBounds(m, n)) {  avg += _heightmap[m * _info.heightmapWidth + n];  num += 1.0f; }
}
return avg / num;

 - 주어진 Index들이 높이맵의 유효한 원소에 해당하면 true 그렇지 않으면 false를 반환

   - 가장자리 픽셀에서 높이맵의 일부가 아닌, 즉 존재하지 않는 이웃 항목을 추출하는 경우 포함하지 않음

bool Terrain::InBounds(int32 i, int32 j)
{ return i >= 0 && i < (int32)_info.heightmapHeight && j >= 0 && j < (int32)_info.heightmapWidth; }

 - 높이맵의 모든 원소에 위의 함수를 통해 적용하여 높이맵 전체를 평활화하는 함수 사용

std::vector<float> dest(_heightmap.size());
for (uint32 i = 0; i < _info.heightmapHeight; ++i)
{
    for (uint32 j = 0; j < _info.heightmapWidth; ++j)
        dest[i * _info.heightmapWidth + j] = Average(i, j);
}
_heightmap = dest;

 - 높이맵에 대한 셰이더 자원 뷰를 생성, 내용은 생략

void Terrain::BuildHeightmapSRV(ComPtr<ID3D11Device> device);

1.1.2 Tesselation

 - 지형을 구축하는 데에 많은 수의 삼각형이 필요

   - 그런데 지형 중에 카메라에서 멀리 떨어져 있는 부분은 어차피 세부사항이 잘 보이지 않음

     - 이러한 사항들을 적절히 처리해주는 세부 수준 시스템을 지형 렌더링에 적용

 - 사각형 패치들로 이루어진 격자를 배치, 그 패치들을 카메라와의 거리에 기초해서 테셀레이션 실행

   - 높이맵에 대한 셰이더 자원 뷰를 파이프라인에 묶어 두고 영역 셰이더에서 높이맵에서 추출한 높이 값들로 변위 매핑을 수행해서 새 정점들의 높이를 적절히 조정

1.1.2.1 Cell Spacing

 - 지형을 여러 패치들의 격자로 나누는데 이 패치 격자의 한 패치는 65 ✕ 65 영역을 포괄

   - 최대 테셀레이션 계수가 64개, 하나의 패치를 최대로 테셀레이션하면 64 ✕ 64 낱칸, 정점으로 치면 65  ✕ 65

     - 한 패치가 최대로 테셀레이션되면 생성된 모든 정점에 대해 높이맵의 높이 정보가 부여

       - 패치의 테셀레이션 계수가 1이면 그 패치는 전혀 세분되지 않고 그냥 두 개의 삼각형으로 렌더링

static const int CellsPerPatch = 64;
_numPatchVertRows = ((_info.heightmapHeight - 1) / CellsPerPatch) + 1;
_numPatchVertCols = ((_info.heightmapWidth - 1) / CellsPerPatch) + 1;
_numPatchVertices = _numPatchVertRows * _numPatchVertCols;
_numPatchQuadFaces = (_numPatchVertRows - 1) * (_numPatchVertCols - 1);

1.1.2.1.1 Vertex Buffer Create

void Terrain::BuildQuadPatchVB(ComPtr<ID3D11Device> device);

 - 절반의 가로 & 세로, 패치당 가로 & 세로 개수, 사각형 한개당 가로 & 세로 크기 설정

float halfWidth = 0.5f * GetWidth(); float halfDepth = 0.5f * GetDepth();
float patchWidth = GetWidth() / (_numPatchVertCols - 1); float patchDepth = GetDepth() / (_numPatchVertRows - 1);
float du = 1.0f / (_numPatchVertCols - 1); float dv = 1.0f / (_numPatchVertRows - 1);

   - 화면 좌표상 좌상단 부터 시작하므로 x, z 값을 설정

     - Terrian에 각 정점에 따라 높이 맵을 설정하고 Texture 적용을 위해 설정 

for (uint32 i = 0; i < _numPatchVertRows; ++i)
{
    float z = halfDepth - i * patchDepth;
    for (uint32 j = 0; j < _numPatchVertCols; ++j)
    {
        float x = -halfWidth + j * patchWidth;
        patchVertices[i * _numPatchVertCols + j].Pos = XMFLOAT3(x, 0.0f, z);
        patchVertices[i * _numPatchVertCols + j].Tex.x = j * du;
        patchVertices[i * _numPatchVertCols + j].Tex.y = i * dv;
    }
}

   - 축 정렬 경계상자의 Y 경계들을 왼쪽 상당 모퉁이 패치에 저장

for (uint32 i = 0; i < _numPatchVertRows - 1; ++i)
{
    for (uint32 j = 0; j < _numPatchVertCols - 1; ++j)
    {
        uint32 patchID = i * (_numPatchVertCols - 1) + j;
        patchVertices[i * _numPatchVertCols + j].BoundsY = _patchBoundsY[patchID];
    }
}

   - 마지막으로 정점 버퍼를 생성

D3D11_BUFFER_DESC vbd; D3D11_SUBRESOURCE_DATA vinitData; /* 생략 */
HR(device->CreateBuffer(&vbd, &vinitData, _quadPatchVB.GetAddressOf()));

1.1.2.1.2 Index Buffer Create

void Terrain::BuildQuadPatchIB(ComPtr<ID3D11Device> device);

 - 각 사각형마다 Index 계산

   - 사각형 패치의 위쪽 정점 2개, 아래쪽 정점 2개를 사용하고 다음 사각형으로 넘김

int32 k = 0;
for (uint32 i = 0; i < _numPatchVertRows - 1; ++i)
    for (uint32 j = 0; j < _numPatchVertCols - 1; ++j)
    {
        indices[k] = i * _numPatchVertCols + j;
        indices[k + 1] = i * _numPatchVertCols + j + 1;
        indices[k + 2] = (i + 1) * _numPatchVertCols + j;
        indices[k + 3] = (i + 1) * _numPatchVertCols + j + 1;
        k += 4;
    }

 

 - 마지막으로 색인 버퍼 생성

D3D11_BUFFER_DESC ibd; D3D11_SUBRESOURCE_DATA iinitData; /* 생략 */
HR(device->CreateBuffer(&ibd, &iinitData, _quadPatchIB.GetAddressOf()));

1.1.2.2 Vertex Shader

 - 테셀레이션을 사용하므로 정점 셰이더는 정점이 아닌 제어점을 처리하는 역할

   - 높이맵 값을 읽어 패치 제어점에 대한 변위 매핑을 수행한다는 점만 제외하면 그대로 통과 셰이더에 가까움

     - 변위 매핑은 제어점의 y 성분을 적절한 높이로 설정하는 역할

       - 덮개 셰이더에서 각 패치와 시점 사이의 거리를 계산, 패치의 모퉁이 정점들이 xz평면이 아닌 적절한 높이에 있으면 그러한 계산이 좀 더 정확해지기 때문

VertexOut vout;
vout.PosW = vin.PosL;
vout.PosW.y = gHeightMap.SampleLevel(samHeightmap, vin.Tex, 0).r;
vout.Tex = vin.Tex; vout.BoundsY = vin.BoundsY;
return vout;

1.1.2.3 Hull Shader

PatchTess ConstantHS(InputPatch<VertexOut, 4> patch, uint patchID : SV_PrimitiveID);

 - 각 패치마다 그 패치를 얼마나 세분할것인지 결정하는 테셀레이션 계수들을 계산 및 출력

   - 이 상수 덮개 셰이더를 GPU에서 절두체 선별을 수행하는 기회로 사용 (다음 글을 통해 설명)

     - 시점과 패치 각 변 중점 사이의 거리에 기초해 변 테셀레이션 계수를 계산

float CalcTessFactor(float3 p)
{
	float d = distance(p, gEyePosW);
	float s = saturate((d - gMinDist) / (gMaxDist - gMinDist));
	return pow(2, (lerp(gMaxTess, gMinTess, s)));
}

 - 세부수준이 하나 증가함에 따라 세분 정도는 두배가 됨, 제곱수를 사용

   - 2의 거듭제곱 함수를 사용하면 세부수준들이 거리에 따라 좀 더 잘 분포

     - 패치의 중정과 패치의 각 변의 중점에 이 테셀레이션 계수 계산 함수를 적용하여 결정

 - 각 변의 중점과 패치 자체의 중점을 계산

PatchTess pt;
/* 절두체 선별 생략 */
else
{
    float3 e0 = 0.5f * (patch[0].PosW + patch[2].PosW);
    float3 e1 = 0.5f * (patch[0].PosW + patch[1].PosW);
    float3 e2 = 0.5f * (patch[1].PosW + patch[3].PosW);
    float3 e3 = 0.5f * (patch[2].PosW + patch[3].PosW);
    float3  c = 0.25f * (patch[0].PosW + patch[1].PosW + patch[2].PosW + patch[3].PosW);

    pt.EdgeTess[0] = CalcTessFactor(e0);
    pt.EdgeTess[1] = CalcTessFactor(e1);
    pt.EdgeTess[2] = CalcTessFactor(e2);
    pt.EdgeTess[3] = CalcTessFactor(e3);

    pt.InsideTess[0] = CalcTessFactor(c);
    pt.InsideTess[1] = pt.InsideTess[0];
    return pt;
}

1.1.2.4 Displacement Mapping & Domain Shader

[domain("quad")]
DomainOut DS(PatchTess patchTess, float2 uv : SV_DomainLocation, const OutputPatch<HullOut, 4> quad);

 - 테셀레이션된 정점 위치의 매개변수 좌표를 이용하여 제어점 자료를 보간해 실제의 정점위치와 Texture 좌표를 유도

   - 높이맵의 높이를 추출해서 변위 매핑 수행

 - 겹선형 보간을 사용 & 지형에 Texture 계승들을 타일링하고 변위 매핑 후 동차 절단공간으로 투영

DomainOut dout;
dout.PosW = lerp(lerp(quad[0].PosW, quad[1].PosW, uv.x), lerp(quad[2].PosW, quad[3].PosW, uv.x),  uv.y);
dout.Tex = lerp(lerp(quad[0].Tex, quad[1].Tex, uv.x), lerp(quad[2].Tex, quad[3].Tex, uv.x),  uv.y);

dout.TiledTex = dout.Tex * gTexScale;
dout.PosW.y = gHeightMap.SampleLevel(samHeightmap, dout.Tex, 0).r;
dout.PosH = mul(float4(dout.PosW, 1.0f), gViewProj);
return dout;

1.1.2.5 Pixel Shader

float4 PS(DomainOut pin, uniform int gLightCount, uniform bool gFogEnabled) : SV_Target

 - 높이맵에서 얻은 높이들에 중심차분법을 적용하여 즉석에서 추정

float2 leftTex = pin.Tex + float2(-gTexelCellSpaceU, 0.0f);
float2 rightTex = pin.Tex + float2(gTexelCellSpaceU, 0.0f);
float2 bottomTex = pin.Tex + float2(0.0f, gTexelCellSpaceV);
float2 topTex = pin.Tex + float2(0.0f, -gTexelCellSpaceV);

float leftY = gHeightMap.SampleLevel(samHeightmap, leftTex, 0).r;
float rightY = gHeightMap.SampleLevel(samHeightmap, rightTex, 0).r;
float bottomY = gHeightMap.SampleLevel(samHeightmap, bottomTex, 0).r;
float topY = gHeightMap.SampleLevel(samHeightmap, topTex, 0).r;
float3 tangent = normalize(float3(2.0f * gWorldCellSpace, rightY - leftY, 0.0f));
float3 bitan = normalize(float3(0.0f, bottomY - topY, -2.0f * gWorldCellSpace));
float3 normalW = cross(tangent, bitan);
저작자표시 (새창열림)

'C++ Algorithm & Study > Game Math & DirectX 11' 카테고리의 다른 글

[Direct11] 41. 3D Direct Application - Particle System #1  (0) 2023.07.03
[Direct11] 40. 3D Direct Application - Terrian Rendering #2  (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
[Direct11] 36. 3D Direct Application - Dynamic Cube Mapping System  (0) 2023.06.27
    'C++ Algorithm & Study/Game Math & DirectX 11' 카테고리의 다른 글
    • [Direct11] 41. 3D Direct Application - Particle System #1
    • [Direct11] 40. 3D Direct Application - Terrian Rendering #2
    • [Direct11] 38. 3D Direct Application - Displacement Mapping
    • [Direct11] 37. 3D Direct Application - Normal Mapping
    GameChoi
    GameChoi

    티스토리툴바