본문 바로가기
DirectX/[Inflearn_rookiss] Part2: DirectX12

31. Terrain

by 헛둘이 2023. 2. 20.

- 터레인은 지형을 의미하며, 지형도 결국 삼각형 메쉬로 이루어져 있다.

- 게임의 지형을 보면 삼각형마다 높이가 제각각 다른데 이 높이를 어떻게 저장하지?

- 텍스쳐 하나를 만들고 각 정점마다 위치해야 할 높이 자체를 텍스쳐에 저장함

- 이 텍스쳐를 높이 맵이라고 한다.

 

- 예제에서는 지형 텍스쳐를 외부에서 구해서, 테셀레이션과 높이 맵을 적용하고 있다.

 

지난 시간 복습

-  Hull Shader? Control Point와 Patch를 통해 정점과 삼각형에 접근하고,

- SV_TessFactor 시맨틱으로 지정된 변수의 인덱스에 접근해서 어떤 변 위에 정점을 추가로 더 찍을 수 있다.

- SV_InsideTessFactor 시맨틱을 통해 내부의 점을 더 추가할 수 있다.

- SV_TessFactor - 변 위의 점, SV_InsideTessFactor - 삼각형 내부의 점

- 값을 0으로 변경해서 점을 다 지워버릴 수도 있다.

 

- Tessellator 단계에서는 이 데이터를 통해 실제로 정점을 추가해준다.

- Domain Shader 단계에서는 기존 Vertex Shader에서 하던 것처럼 각 정점에 대한 처리를 진행한다. (WVP 클립좌표 변환)

 

 

edge란?

- 점 위치의 각이 마주보는 변을 의미한다.

0번 위치의 점과 마주하는 변(edge)

// --------------
// Hull Shader
// --------------

struct PatchTess
{
    float edgeTess[3] : SV_TessFactor;
    float insideTess : SV_InsideTessFactor;
};

struct HS_OUT
{
    float3 pos : POSITION;
    float2 uv : TEXCOORD;
};

// Constant HS
PatchTess ConstantHS(InputPatch<VS_OUT, 3> input, int patchID : SV_PrimitiveID)
{
    PatchTess output = (PatchTess)0.f;

    float minDistance = g_vec2_1.x;
    float maxDistance = g_vec2_1.y;

    float3 edge0Pos = (input[1].pos + input[2].pos) / 2.f;
    float3 edge1Pos = (input[2].pos + input[0].pos) / 2.f;
    float3 edge2Pos = (input[0].pos + input[1].pos) / 2.f;

    edge0Pos = mul(float4(edge0Pos, 1.f), g_matWorld).xyz;
    edge1Pos = mul(float4(edge1Pos, 1.f), g_matWorld).xyz;
    edge2Pos = mul(float4(edge2Pos, 1.f), g_matWorld).xyz;

    float edge0TessLevel = CalculateTessLevel(g_vec4_0.xyz, edge0Pos, minDistance, maxDistance, 4.f);
    float edge1TessLevel = CalculateTessLevel(g_vec4_0.xyz, edge1Pos, minDistance, maxDistance, 4.f);
    float edge2TessLevel = CalculateTessLevel(g_vec4_0.xyz, edge2Pos, minDistance, maxDistance, 4.f);

    output.edgeTess[0] = edge0TessLevel;
    output.edgeTess[1] = edge1TessLevel;
    output.edgeTess[2] = edge2TessLevel;
    output.insideTess = edge2TessLevel;

    return output;
}

// Control Point HS
[domain("tri")] // 패치의 종류 (tri, quad, isoline)
[partitioning("fractional_odd")] // subdivision mode (integer 소수점 무시, fractional_even, fractional_odd)
[outputtopology("triangle_cw")] // (triangle_cw, triangle_ccw, line)
[outputcontrolpoints(3)] // 하나의 입력 패치에 대해, HS가 출력할 제어점 개수
[patchconstantfunc("ConstantHS")] // ConstantHS 함수 이름
HS_OUT HS_Main(InputPatch<VS_OUT, 3> input, int vertexIdx : SV_OutputControlPointID, int patchID : SV_PrimitiveID)
{
    HS_OUT output = (HS_OUT)0.f;

    output.pos = input[vertexIdx].pos;
    output.uv = input[vertexIdx].uv;

    return output;
}

- ConstantHS에서 하는 일은 Patch를 다루는 것이라고 했다.

- 이 값을 다루는데 글로벌로 넘겨받은 여러 변수들을 이용해서 삼각형의 밀도를 높여준다.

- 각 edge의 중점을 구한 후에, 그 중점을 월드 좌표로 변환한다.

- 그리고 CalculateTessLevel 함수를 통해 카메라 위치에 따라 총 4단계 중 몇 단계인지 계산한다.

 

float CalculateTessLevel(float3 cameraWorldPos, float3 patchPos, float min, float max, float maxLv)
{
    float distance = length(patchPos - cameraWorldPos);

    if (distance < min)
        return maxLv;
    if (distance > max)
        return 1.f;

    float ratio = (distance - min) / (max - min);
    float level = (maxLv - 1.f) * (1.f - ratio);
    return level;
}

- 현재 엣지의 중심 위치와 카메라의 위치를 받아와서 거리를 계산한다.

- 이 거리가 min보다 작으면 가까이 있다는거니까 최대 레벨을 적용한다 (4단계)

- 이 거리가 max보다 크면 멀리 있다는 거니까 최소 레벨을 적용한다 (1단계)

- 둘 다 아닌 경우 비례식을 통해 이게 min과 max 중 몇 %인지 계산한 후 반환한다.

 

 

이후 도메인 쉐이더에서 이 정점에 대한 처리를 진행

struct DS_OUT
{
    float4 pos : SV_Position;
    float2 uv : TEXCOORD;
    float3 viewPos : POSITION;
    float3 viewNormal : NORMAL;
    float3 viewTangent : TANGENT;
    float3 viewBinormal : BINORMAL;
};

[domain("tri")]
DS_OUT DS_Main(const OutputPatch<HS_OUT, 3> input, float3 location : SV_DomainLocation, PatchTess patch)
{
    DS_OUT output = (DS_OUT)0.f;

    float3 localPos = input[0].pos * location[0] + input[1].pos * location[1] + input[2].pos * location[2];
    float2 uv = input[0].uv * location[0] + input[1].uv * location[1] + input[2].uv * location[2];

    int tileCountX = g_int_1;
    int tileCountZ = g_int_2;
    int mapWidth = g_vec2_0.x;
    int mapHeight = g_vec2_0.y;

    float2 fullUV = float2(uv.x / (float)tileCountX, uv.y / (float)tileCountZ);
    float height = g_tex_2.SampleLevel(g_sam_0, fullUV, 0).x;

    // 높이맵 높이 적용
    localPos.y = height;


    // 높이가 바뀌었으니 노말을 다시 구해주고 있는 부분
    float2 deltaUV = float2(1.f / mapWidth, 1.f / mapHeight);
    float2 deltaPos = float2(tileCountX * deltaUV.x, tileCountZ * deltaUV.y);

    float upHeight = g_tex_2.SampleLevel(g_sam_0, float2(fullUV.x, fullUV.y - deltaUV.y), 0).x;
    float downHeight = g_tex_2.SampleLevel(g_sam_0, float2(fullUV.x, fullUV.y + deltaUV.y), 0).x;
    float rightHeight = g_tex_2.SampleLevel(g_sam_0, float2(fullUV.x + deltaUV.x, fullUV.y), 0).x;
    float leftHeight = g_tex_2.SampleLevel(g_sam_0, float2(fullUV.x - deltaUV.x, fullUV.y), 0).x;

    float3 localTangent = float3(localPos.x + deltaPos.x, rightHeight, localPos.z) - float3(localPos.x - deltaPos.x, leftHeight, localPos.z);
    float3 localBinormal = float3(localPos.x, upHeight, localPos.z + deltaPos.y) - float3(localPos.x, downHeight, localPos.z - deltaPos.y);

    output.pos = mul(float4(localPos, 1.f), g_matWVP);
    output.viewPos = mul(float4(localPos, 1.f), g_matWV).xyz;

    output.viewTangent = normalize(mul(float4(localTangent, 0.f), g_matWV)).xyz;
    output.viewBinormal = normalize(mul(float4(localBinormal, 0.f), g_matWV)).xyz;
    output.viewNormal = normalize(cross(output.viewBinormal, output.viewTangent));

    output.uv = uv;

    return output;
}

- float height 부분에서 텍스쳐에서 가져온 높이를 세팅해주는 부분이 있고,

- 그 아래로 높이가 변경되었으니 노말을 다시 구해주고 있는 부분

- 높이를 기준으로 상하좌우 좌표를 구한 후에, localTangent, localBinormal을 구해 준다

- 그 후 그 값을 외적해서 Normal을 다시 구해준다.

- 여기까지가 터레인 쉐이더가 다른 쉐이더와 다른 부분들이였음.

 

 

이 후 CPP 코드에서는 터레인을 컴포넌트로 가진 게임 오브젝트를 만들고, 터레인 쉐이더를 로드해서 터레인 메쉬에 터레인 쉐이더를 사용해서 렌더링하는 코드가 추가됨

 

- 터레인 컴포넌트에서 하는 일은 터레인의 크기를 받아준 후 Init에서 쉐이더에서 필요한 값들을 Material에 세팅

- FinalUpdate에서는 메인 카메라의 위치를 매번 Material에 저장해서 GPU에서 값을 사용할 수 있게 하는 작업을 한다.

 

 

 

 

 

'DirectX > [Inflearn_rookiss] Part2: DirectX12' 카테고리의 다른 글

33. Mesh  (0) 2023.02.21
32. Picking  (0) 2023.02.20
30. Tessellation  (0) 2023.02.19
29. Shadow Mapping  (0) 2023.02.18
28. Instancing  (0) 2023.02.14

댓글