Tessellation
렌더링 파이프라인에서 Geometry Shader보다 더 빨리 실행 되는 부분이 있다. VS 다음 Hul Shader, Tessellator, Domain Shader 단계 총 3가지를 합쳐서 Tessellation 이라고 한다.
그렇다면 Tessellation이란 뭘까?
기하 구조를 더 작은 삼각형으로 분할하고 새로 생긴 정점들의 위치를 적절한 방법으로 조절하는 것으로 정의할 수 있다.
말만 들으면 되게 와닿지 않는다.
그럼 사용하는 때를 생각해보면 Tessellation은 Terrain에서 가장 많이 사용한다. GPU에 lod 연산이 필요할 때, 효율적인 물리 애니메이션을 계산할 때, 메모리 전략으로 low 폴리곤 버전으로 전달해 준 객체를 high 폴리곤 버전으로 변환할 때 등 에서 사용한다.
LOD는 Level Of Detail의 약자로, 가까이 있는 물체는 세밀하게 표현해야 하지만, 거리가 멀어질수록 세밀하게 표현하나 그냥 삼각형 하나로 표현하나 구분할 수 없게 된다. 그래서 가까이 있는 물체는 세밀하게 멀리있는 물체는 덜 세밀하게 표현하는 방식을 LOD라고 한다. 만약 Terrain을 기준으로 가까이 있는 산은 세밀하게 표현되어야 하지만 멀리있는 산 까지 세밀하게 표현된다면 엄청난 과부화를 일으킬 것이다. 세밀하게 표현한다는 것은 모든 삼각형 정점마다 렌더링 파이프라인을 지나가며 쉐이더를 입히고 픽셀마다 색을 입혀야한다는 뜻이기 때문이다.
하나의 사각형을 GPU에게 넘겨주고 이를 거리에 따라 쪼개 지형지물을 나타내는 것을 Tessellation 이라고 할 수 있다. CPU 쪽에서는 단순히 사각형 하나를 넘겨주게 되고 이를 적절하게 분할시켜 디테일한 모습을 만드는 것이다.
결과물은 이렇다.
거리가 먼 상태에서 점점 가까워질때를 나타낸다.
코드를 살펴보자.
Hul Shader는 두 단계로 구분되는데 그 중 첫번째로 Constant 단계가 있다. 이는 각 패치마다 실행된다.
이 단계는 패치가 주어졌을 때, 얼마나 세밀하게 쪼갤지를 결정하는 단계이다.
struct PatchTess
{
float EdgeTess[4] : SV_TessFactor;
float InsideTess[2] : SV_InsideTessFactor;
};
PatchTess ConstantHS(InputPatch<VertexOut, 4> patch, uint patchID : SV_PrimitiveID)
{
PatchTess pt;
float3 centerL = 0.25f * (patch[0].PosL + patch[1].PosL + patch[2].PosL + patch[3].PosL);
float3 centerW = mul(float4(centerL, 1.0f), gWorld).xyz;
float d = distance(centerW, gEyePosW);
// Tessellate the patch based on distance from the eye such that
// the tessellation is 0 if d >= d1 and 60 if d <= d0. The interval
// [d0, d1] defines the range we tessellate in.
const float d0 = 20.0f;
const float d1 = 100.0f;
float tess = 64.0f * saturate((d1 - d) / (d1 - d0));
// Uniformly tessellate the patch.
pt.EdgeTess[0] = tess;
pt.EdgeTess[1] = tess;
pt.EdgeTess[2] = tess;
pt.EdgeTess[3] = tess;
pt.InsideTess[0] = tess;
pt.InsideTess[1] = tess;
return pt;
}
그 다음 HS는 Control Point 단계이다.
표면에 어떻게 표현할지 결정하는 단계이다. 여기서는 그냥 통과하긴 했다.
[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(4)]
[patchconstantfunc("ConstantHS")]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut, 4> p,
uint i : SV_OutputControlPointID,
uint patchId : SV_PrimitiveID)
{
HullOut hout;
hout.PosL = p[i].PosL;
return hout;
}
이렇게 HS단계를 지나면 Tessellator 단계로 넘어가게 되는데, 이는 우리가 입맛대로 커스텀하지 못하고 GPU가 자동으로 처리하게된다. 물론 Option 같은 건 넘겨줄 수 있다.
그 다음 단계인 Domain Shader로 넘어오게 된다.
그렇게 쪼개진 정점들의 최종 좌표를 지정해주는 단계이다.
struct DomainOut
{
float4 PosH : SV_POSITION;
};
// The domain shader is called for every vertex created by the tessellator.
// It is like the vertex shader after tessellation.
[domain("quad")]
DomainOut DS(PatchTess patchTess,
float2 uv : SV_DomainLocation,
const OutputPatch<HullOut, 4> quad)
{
DomainOut dout;
// Bilinear interpolation.
float3 v1 = lerp(quad[0].PosL, quad[1].PosL, uv.x);
float3 v2 = lerp(quad[2].PosL, quad[3].PosL, uv.x);
float3 p = lerp(v1, v2, uv.y);
// Displacement mapping
p.y = 0.3f * (p.z * sin(p.x) + p.x * cos(p.z));
dout.PosH = mul(float4(p, 1.0f), gWorldViewProj);
return dout;
}
위 예저는 Lod를 적용한 것이고 그와 다르게 그냥 삼각형 하나가 주어졌을 때, 이렇게 쪼개기도 가능하다.
Geometry Shader와 Tessellation의 공통점은 정점이 늘어난다는 것인데,
둘의 차이점을 이해해보면 Geometry는 하나의 정점을 기준으로 여러개로 늘리는 것이고
Tessellation은 하나의 물체를 여러개로 쪼개는 것이다.
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 물방울 책 - Terrain, Particle, Shadow, Ambient Occlusion (0) | 2024.10.31 |
---|---|
[DirectX] 물방울 책 - Picking, Mapping (1) | 2024.10.29 |
[DirectX] 물방울 책 - Shader (1) | 2024.10.25 |
[DirectX] 물방울 책 - 조명, 텍스처 (0) | 2024.10.24 |
[DirectX] 물방울 책 - 랜더링 파이프라인, 그리기 연산 (0) | 2024.10.23 |