본문 바로가기

그래픽스

[그래픽스] Hull Shader, Tessellation, Domain Shader

Tessellation은 도형을 더 잘게 나누어 상세도를 높여준다.

반대로 도형을 나누는 개수를 줄여 단순하게 표현할 수 있다.

실제로 거리에 따라 가까워지면 도형을 더 잘게 나누어 표현하고 

멀면 단순하게 표현하는 기법이 있는데 이것을 LOD(Level Of Detail)라고 한다.

 

 

 

저번에 본 그림인데, Tessellation을 진행할 때 Hull Shader과 Domain Shader을 같이 진행한다.

 

 

도형을 잘게 나눌 때 그 작은 도형들을 하나하나 다 그리기는 어려우므로 Control Point라는 것을 이용한다.

위 그림에서 작은 도형 위에 듬성듬성 있는 점들이 Control Point고 그것들로 이루어진 한 면을 patch라고 한다.

patch들이 모여 곡면을 이루게 되는데 이 선박껍데기 같은 곡면을 Hull이라고 하고 그래서 patch들을

다루는 쉐이더를 Hull Shader라고 한다.

 

CreateShader 와 SetShader 하는 부분은 VS, PS와 유사하다. 다른점은 SetTopology를 할 때

TOPOLOGY_4_CONTROL_POINT_PATCHLIST로 4개의 Control Point를 사용하는 Patch하나를 그린다.

 

그리고 VertexShader에서는 정점을 그대로 넘겨주고 Hull Shader에서 도형을 어떻게 쪼갤지 출력을 해준다.

 

 

struct PatchConstOutput
{
    float edges[4] : SV_TessFactor;
    float inside[2] : SV_InsideTessFactor;
};

PatchConstOutput MyPatchConstantFunc(InputPatch<VertexOut, 4> patch,
                                     uint patchID : SV_PrimitiveID)
{
    PatchConstOutput pt;
    
    pt.edges[0] = edges[0];
    pt.edges[1] = edges[1];
    pt.edges[2] = edges[2];
    pt.edges[3] = edges[3];	
    pt.inside[0] = inside[0];
    pt.inside[1] = inside[1];
	
    return pt;
}

 

MyPatchConstantFunc라는 함수를 새로 만들어서 이 안에서 도형을 나눈다. Semantics가 SV_TessFactor인 edges는

사각형을 나누는 것이므로 edges는 4개짜리 배열이고 Semantics가 SV_InsideTessFactor인  inside는 x축, y축 두방향으로 나누기 때문에 2개짜리 배열이다. 이런 함수를 Constant Function 이라고 한다.

 

 

 

Hull Shader은 Patch Control Point를 받아 변환된 Patch Control Point를 출력할 수 있고, 또 Patch를 어떻게 쪼갤지

Patch Constant Data를 출력한다. 위 함수에서 출력값 PatchConstOutput 구조체가 Patch Constant Data를 의미한다.

 

매개변수로 InputPatch<VertexOut,4>를 받는데, VertexOut은 Vertex Shader의 출력값 구조체이고 Patch를 입력받기 때문에 VertexOut 4개를 입력으로 받는다. 그리고 GS와 같이 SV_PrimitiveID Semantics의 patchID를 입력 받는다.

 

 

[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(4)]
[patchconstantfunc("MyPatchConstantFunc")]
[maxtessfactor(64.0f)]
HullOut main(InputPatch<VertexOut, 4> p,
           uint i : SV_OutputControlPointID,
           uint patchId : SV_PrimitiveID)
{
    HullOut hout;
	
    hout.pos = p[i].pos.xyz;

    return hout;
}


Hull Shader의 Main 코드이다. 위 Constant Function 에서 Patch Constant Data를 출력했다면 main에서는 Patch Control

Points를 출력한다. 내용은 일단 HullOut으로 입력값을 그대로 출력하고 있다. 매개변수로 patchID뿐만 아니라 Control 

Point 한개씩 ID를 받아오고 있다. 그래서 Control Point를 입력한 순서대로 한개씩 main 함수가 실행된다. 

 

Attribute들이 많은데 먼저 domain("quad")는 patch가 사각형 형태이기 때문이고, partitioning은 도형을 쪼개는 타입이다.

outputtopology("triangle_cw") 는 시계방향 삼각형을 그리는 것을 의미한다. outputcontrolpoints는 GPU  내부에서 사용하는 스레드 하나당 출력하는 control point의 개수다. patchconstantfunc는 위에서 사용했던 constant function의 이름,

maxtessfactor도 constant function에서 사용했던 tessfactor(나누는 개수)의 최대치이다. 

 

 

 

Hull Shader가 끝나면 DirectX 내부적으로 Tessellation Stage가 진행된 뒤 Domain Shader가 실행된다. 

Domain Shader는 Hull Shader로부터 Output Control Points를 입력받고 Tessellator Stage로부터 텍스쳐 좌표를 입력받아

patch들을 쪼개서 나누어진 도형의 정점들의 좌표를 최종적으로 결정한다. 

 

 

[domain("quad")]
DomainOut main(PatchConstOutput patchConst,
             float2 uv : SV_DomainLocation,
             const OutputPatch<HullOut, 4> quad)
{
    DomainOut dout;

	// Bilinear interpolation.
    float3 v1 = lerp(quad[0].pos, quad[1].pos, uv.x);
    float3 v2 = lerp(quad[2].pos, quad[3].pos, uv.x);
    float3 p = lerp(v1, v2, uv.y);
    
    dout.pos = float4(p, 1.0);
    dout.pos = mul(dout.pos, view);
    dout.pos = mul(dout.pos, proj);
	
    return dout;
}

 

Domain Shader에서 도형을 쪼개는 코드이다. Bilinear Interpolation을 사용하는데 사각형일 때 먼저 위쪽 x축을 lerp를 이용해 쪼개고(v1) 아래쪽 x축을 쪼갠다. (v2) 그리고 v1와 v2를 lerp해 y축으로 쪼갠다. (p) 쪼개진 결과 정점의 개수만큼

DomainShader가 실행이 되는 것이다. 

'그래픽스' 카테고리의 다른 글

[그래픽스] Normal Mapping  (0) 2023.07.21
[그래픽스] Mipmap, LOD  (0) 2023.07.21
[그래픽스] Geometry Shader  (0) 2023.07.19
[그래픽스] 쿼터니언(실습)  (0) 2023.07.19
[그래픽스] 쿼터니언(이론)  (0) 2023.07.19