DirectX 11 3d

230530 tess.fx 이어서 작업

슬뷔 2023. 5. 30. 21:46

1. tess.fx 에서 Hull Shader

// ===========
// Hull Shader
// ===========
// Patch Constant Function
// 패치의 분할 횟수를 지정하는 함수
struct PatchOutput
{
    float Edges[3]  : SV_TessFactor;
    float Inside    : SV_InsideTessFactor;
};

PatchOutput PatchConstFunc(InputPatch<VS_INOUT, 3> _input
                            , uint PatchID : SV_PrimitiveID)
{
    PatchOutput output = (PatchOutput) 0.f;
    
    output.Edges[0] = g_float_0;
    output.Edges[1] = g_float_0;
    output.Edges[2] = g_float_0;    
    output.Inside = g_float_0;
    
    return output;
}


struct HS_OUT
{
    float3 vPos : POSITION;
    float2 vUV : TEXCOORD;
};

[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("PatchConstFunc")]
[maxtessfactor(64.f)]
HS_OUT HS_Tess(InputPatch<VS_INOUT, 3> _input
               , uint i : SV_OutputControlPointID
               , uint PatchID : SV_PrimitiveID)
{
    HS_OUT output = (HS_OUT) 0.f;
    
    output.vPos = _input[i].vPos;    
    output.vUV = _input[i].vUV;
    
    return output;
}

[domain] -> 팻치 하나가 어떻게 구성되어있는지, 우리는 정점3개가 팻치1개라 tri

[partitioning("integer")] -> 분할하는 방식.. 정수와 실수가 있다.

=> integer 정수단위 분할

=> fractional_odd 실수단위 분할

 

[outputtopology("triangle_cw")] -> 삼각형 단위로 하나의 면으로 보는 방식

[outputcontrolpoints(3)] -> 우린 트라이앵글 사용해서 3개

[patchconstantfunc("PatchConstFunc")] -> 팻치 상수함수가 누구인지 알려주기.

=> 홀 쉐이더가 호출될 때 정점마다 홀 쉐이더가 호출되고 3개씩 팻치를 묶어서 팻치당 실행될 함수는 누군지 알려줘야 컴파일러가 컴파일할 때 적혀진 함수보고 함수명 (PatchConstFunc)을 찾아서 팻치마다 실행해줄 함수인지 구분해서 컴파일 할 때 같이 묶어서 진행한다.

 

[maxtessfactor(64.f)] -> 분할할 수 있는 최대 개수 64개로 제한.

=> 나중에는 고정된 숫자로 하지 않고.. 카메라에서 가까워지면 분할 개수 늘려주고, 멀어지면 분할 개수를 줄여줄 수 있게 변수화 시킬 것이다. 수식으로 하면 프로그램이 너무 늦어질 수 있기 때문에.. 

 

버텍스 쉐이더에서 리턴시킨 값이 VS_Pos가 아니라 로컬 Pos이다.

why? 버텍스 쉐이더는 이제 더이상 최종 마지막 단계가 아니기 때문이다.

홀 쉐이더도 이걸 그대로 받아서 그대로 넘기면서 분할 레벨을 2로 잡았다.

그대로 넘긴 이유 ? 모든 분할을 로컬 안에서 다 해놓고 일괄적으로 좌표계 변환 진행하기 위해서.

 

2. tess.fx 에서 Domain Shader

struct DS_OUT
{
    float4 vPosition : SV_Position;
    float2 vUV : TEXCOORD;
};


[domain("tri")]
DS_OUT DS_Tess(const OutputPatch<HS_OUT, 3> _origin
                , float3 _vWeight : SV_DomainLocation
                , PatchOutput _patchtess)
{
    DS_OUT output = (DS_OUT) 0.f;        
    
    //float3 vLocalPos = (_origin[0].vPos * _vWeight[0]) + (_origin[1].vPos * _vWeight[1]) + (_origin[2].vPos * _vWeight[2]);
    //float2 vUV = (_origin[0].vUV * _vWeight[0]) + (_origin[1].vUV * _vWeight[1]) + (_origin[2].vUV * _vWeight[2]);
  
    float3 vLocalPos = (float3) 0.f;
    float2 vUV = (float2) 0.f;
    
    for (int i = 0; i < 3; ++i)
    {
        vLocalPos += _origin[i].vPos * _vWeight[i];
        vUV += _origin[i].vUV * _vWeight[i];
    }    
    
    output.vPosition = mul(float4(vLocalPos, 1.f), g_matWVP);
    output.vUV = vUV;
    
    return output;
}

도메인 쉐이더에선 이제 최종 마지막 단계이기 때문에 SV_Pos를 준다.

why 마지막..? 지오메트리 쉐이더까지 있지 않아서 도메인 쉐이더 다음이 레스터라이저이기 때문이다.

만약 지오메트리 쉐이더가 있다면 도메인 쉐이더 다음이 되어서 SV_Pos 를 지오메트리가 사용하게 될 것이다.

 

[domain("tri")] => 도메인 쉐이더는 디스크립션을 이거 하나만 작성하면 된다.

(삼각형이라서 tri)

 

DS_OUT DS_Tess(const OutputPatch<HS_OUT, 3> _origin
                , float3 _vWeight : SV_DomainLocation
                , PatchOutput _patchtess)

 

_origin => 홀쉐이더에서 반환한 값 3개(const OutputPatch<hs_out, 3><HS_OUT, 3>)가 들어온다. 

_vWeight  => 최종좌표를 각각 원본 정점으로부터 가중치를 곱해줘야한다..

(가중치를 다 더하면 무조건 1이 되어야한다..) 

 

output.vPosition = mul(float4(vLocalPos, 1.f), g_matWVP) => 최종위치를 구하기 위해 월드뷰프로젝션작업을 해주는 것.

(원래는 로컬스페이스니까)

output. vUV => UV는 그냥 넘겨도 된다.

 

* ResMgr에서 pShader->SetTopology(D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST); 를 꼭 지정해주어야한다.. 삼각형이라 제어점이 3개여서 3이들어간다. 

최종 결과

현재 edge(제어점)이 2개이기 때문에 왼쪽과 같은 결과가 나왔다.. 오른쪽은 16개로 늘렸을 때의 결과이다.

 

ResMgr 에서 pShader->AddScalarParam(FLOAT_0, "Divide"); 를 지정해줘서 float값을 공개해서 edge(제어점)의 값을 제어할 수 있게 해준다.

Imgui 에서 내가 tessmtrl 에서 컨트롤 할 수 있게 만들어준다