30. Tessellation

Tessellation Stages란?
- Hull Shader, Tessellator, Domain Shader 단계를 묶어서 Tessellation Stages라고 한다.
- 지오메트리와 동일하게 정점을 추가하는 개념
- 지오메트리와 비교하면 테셀레이션은 좀 더 큰 규모에 적용이 된다.
동적 LOD란?
- LOD란 Level of detail이라는 의미로 디테일의 정도를 의미하며 로우폴리, 하이폴리같은 디테일의 정도에 따른 메쉬의 분류를 생각하면 이해가 쉽다.
- 동적 LOD란 이런 디테일이 실시간으로 변화하는 것
- 예를 들어 카메라 멀리 있는 물체는 정점의 개수를 줄여서 대충 그리고, 가까이 있는 물체는 디테일 하게 그리는 것
- 이런 작업은 렌더링 성능 향상에 도움이 된다.
물론 로우폴리와 하이폴리 메쉬를 준비해서 바꿔치기해도 되지만,
테셀레이션을 이용하면 프로그래밍상으로도 구현이 가능하다.
가장 대표적인게 터레인(지형)
가까이에 있는 나무도 있고 멀리 있는 나무도 있는데,
멀리 있는 나무는 대충 그려도 된다 왜냐면 어차피 희미하게 보이기 때문에
내부를 결정하는 점(삼각형의 경우 3개)을 Control Point라고 하고, 그 그룹을 Patch라고 한다.
Vertex라는 단어를 놔두고 왜 Control Point라고 하는가?
제어를 한다는 뉘앙스가 강하기 때문 (직역하면 제어점)
그래서 테셀레이션을 활용하면 VS에서 받은 인자를 그냥 바로 Hull Shader로 넘겨준다.
PatchTess ConstantHS(InputPatch<VS_OUT, 3> input, int patchID : SV_PrimitiveID)
{
PatchTess output = (PatchTess) 0.f;
output.edgeTess[0] = 1;
output.edgeTess[1] = 1;
output.edgeTess[2] = 1;
output.insideTess = 1;
return output;
}
[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("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;
}
- 용어 정리를 먼저 하면 ControlPoint는 정점(제어점)의 의미이고, Patch는 ControlPoint의 집합을 의미한다 (삼각형)
- HS_Main 함수에서는 InputPatch<VS_OUT, 3>을 받는데, 이는 정점의 집합 즉 하나의 Patch를 받는 것이다.
- 그리고 자신이 몇 번 그 Patch에서 몇 번 인덱스인지 확인하기 위해 patchID라는 인자를 추가로 받는다.
- ConstantHS 함수는 Patch 단위로 받는데, PatchTess의 edgeTess 멤버는 삼각형의 각 변을 의미한다.
- 0번 변에 1을 넣는다는 것은 그 변을 1로 분할한다, 즉 그대로 둔다는 의미하고, 2로 둔다는 것은 2개로 분할한다는 의미
이렇게 해서 삼각형을 추가로 구성하는 작업이 끝나게 되면 Tessellator에서 이 추가된 정점을 통해 삼각형을 재정의하게 되고,
이 삼각형에 대한 처리를 하는 게 도메인 쉐이더의 역할이다.
Domain Shader 단계에서는 새로 만들어진 정점들도 다 도메인 쉐이더로 들어오게 되고,
기존 버텍스 쉐이더에서 했던 작업들을 여기서 진행해주게 된다.
정보는 패치(제어점의 집합)과 위치 정보로 들어오게 된다.
struct DS_OUT
{
float4 pos : SV_Position;
float2 uv : TEXCOORD;
};
[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];
output.pos = mul(float4(localPos, 1.f), g_matWVP);
output.uv = uv;
return output;
}
- location은 현재 위치의 비율값으로 들어오게 되는데, 정점의 인덱스에 대한 자신의 위치를 0~1 사이의 값으로 세팅한다.
- 그래서 localPos를 구하는 과정은 실제 좌표를 찾는 과정이며,
- 그 아래 output.pos에 mul(...)은 이전에 버텍스 쉐이더에서 WVP를 곱해서 클립좌표를 구하는 부분이다.
- 그 값을 반환하는것까지 버텍스쉐이더에서 하는 일과 동일한 일을 한다는 것을 확인할 수 있다.

