테셀레이션

2023. 9. 10. 20:40Public/Shader

특징

-테셀레이션 셰이더는 비용이 많이든다. 하지만 사촌격인 지오메트리 셰이더보다 일반적으로 저렴하다.

-하드웨어 지원이 더 좋다.

-메시를 더 단순하게 유지할 수 있어 일괄 처리에 도움이 될 수 있다.

-프로그래밍 가능한 두 개의 추가 단계가 있다. 이를 헐 및 도메인 단계라고 부른다.

- 동적 LOD 사용에 적합

 

출처:MSDN(https://learn.microsoft.com/en-us/windows/win32/direct3d11/direct3d-11-advanced-stages-tessellation)

 

 

 

ControlPoint  :  내부를 제어하는 점

Patch : ControlPoint의 그룹 , 지합 

 

왜? Vertex라고 부르지 않는가?

ControlPoint는 제어를 한다는 뉘앙스가 강하다. 베지어 곡선의 제어점(ControlPoint)을 생각하면 편하다.

 

 

 

Hull-Stage

단순히 정점 목록인 '패치'형식으로 데이터를 수신합니다.

Hull 단계는 Hull 함수와 patch 상수 함수라는 두 가지 함수로 구성되고

파라미터로 Patch Control Points를 받습니다.

Hull 함수는 패치의 정점당 한 번씩 실행되고

상수 함수는 패치당 한 번 실행되며 테셀레이션 인자를 출력합니다.

출처:MSDN

테셀레이터 단계

테셀레이터는 프로그래밍 할 수 없는 단계입니다.

이는 Hull에서 생성된 패치 데이터와 테셀레이션 요소를 사용하여 각 패치를 세분화 합니다.

테셀레이터는 새로운 메시의 모든 정점에 무게 중심 좌표라는 것을 생성합니다. . . . . 

 

 

도메인 단계

테셀레이션된 메쉬의 각 버텍스에 대해 실행되는 도메인 함수라는 하나의 함수로 구성됩니다.

함수의 목적은 버텍스에 대한 최종 데이터를 출력하는 것입니다. 이를 위해서 버텍스에 대한 무게중심 좌표와 원래 패치를 수신합니다. 여기에는 헐 및 패치 상수 함수에 의해 생성된 모든 데이터가 포함됩니다.

 

 

 

 

유니티에서의 사용

#pragma 지시문을 사용하여 헐 및 도메인 기능을 사용할 수 있습니다.

테셀레이션 셰이더에는 Shader target 5.0이 필요하므로 이를 조정하지 않으면 Untiy에서 에러가 발생합니다.

 

Shader "Unlit/Tessel"
{
    Properties
    {
        
    }
    SubShader
    {
        Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "IgnoreProjector" = "True"}

        Pass
        {
            Name "ForwardLit"
            Tags{"LightMode"="UniversalForward"}

            HLSLPROGRAM
            
            #pragma target 5.0
            #pragma vertex Vertex
            #pragma hull Hull
            #pragma domain Domain
            #pragma fragment Fragment

            #include"TessellationSample.hlsl"
            ENDHLSL
        }
    }
}

 

 

 

[TessellationSample.hlsl]

#ifndef TESSELLATION_SAMPLE_INCLUDED
#define TESSELLATION_SAMPLE_INCLUDED

#include"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

#if defined(_TESSELLATION_SMOOTHING_VCOLORS) || defined(_TESSELLATION_FACTOR_VCOLORS)
    #define REQUIRES_VERTEX_COLORS

#endif

// Define the length of the bezier array
#if defined(_TESSELLATION_SMOOTHING_BEZIER_LINEAR_NORMALS)
#define NUM_BEZIER_CONTROL_POINTS 7
#elif defined(_TESSELLATION_SMOOTHING_BEZIER_QUAD_NORMALS)
#define NUM_BEZIER_CONTROL_POINTS 10
#else
#define NUM_BEZIER_CONTROL_POINTS 0
#endif


struct Attributes {
    float3 positionOS : POSITION;
    float3 normalOS : NORMAL;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct TessellationControlPoint {
    float3 positionWS : INTERNALTESSPOS; //POSITION 시멘틱은 금지 대신 INTERNALTESSPOS를 사용합니다
    float3 normalWS : NORMAL;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

//단순히 위치와 법선을 월드 공간으로 변환합니다
//출력은 Hull 단계에  공급됩니다
TessellationControlPoint Vertex(Attributes input) {
    TessellationControlPoint output;

    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);

    VertexPositionInputs posnInputs = GetVertexPositionInputs(input.positionOS);
    VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS);

    output.positionWS = posnInputs.positionWS;
    output.normalWS = normalInputs.normalWS;
    return output;
}





// Hull 함수는 버텍(정점)당 한번씩 실행됩니다. 정 을 수정하는데 사용할 수 있습니다
// 삼각형 전체의 값을 기준으로 한 자료
[domain("tri")]//삼각형을 입력하는 신호입니다 입력패치의 유형입니.
[outputcontrolpoints(3)] //삼각형의 점은 3개 출력패치 유형을 결정
[outputtopology("triangle_cw")] //삼각형을 출력한다는 신호 출력패치 유형을 결정
[patchconstantfunc("PatchConstantFunction")] //패치 상수 함수를 등록
[partitioning("integer")] //분할 모드를 선택 : int , fractional_odd,fractional_evenor 또는 pow2   삼각형을 세분화하는 데 사용할 알고리즘을 테셀레이터에 알려줌
TesselationControlPoint Hull(
    InputPatch<TessellationControlPoint,3> patch /*삼각형 입력*//* 정점 함수 출력 구조와 패치의 버텍스 수는 괄호안에 들어감. 배열처럼 패치의 각 구조에 액세스 가능*/,
    uint id : SV_OutputControlPointID /*삼각형의 정  인덱스*/)
{
    return patch[id];
}

 

 

 

 

Hull

함수 자체는 InputPatch 구성을 사용하여 입력 패치를 받습니다.

버텍스 함수의 출력 구조와 패치의 버텍스 수는 꺽쇠 괄호안에 들어갑니다. 배열처럼 각 구조에 직접 액세스할 수 있습니다.

헐 함수는 SV_OutputControlPointID 시멘틱을 사용하여 버텍스 인덱스를 받습니다. 이 시멘틱은 데이터를 출력할 패치의 정점을 알려줍니다.

마지막으로 반환유형을 설정할 수 있습니다. 이 예시에서는 버텍스 출력 구조와 동일하지만 자유롭게 커스텀할 수 있습니다. 단 주의할점은 POSITION 시멘틱 대신 INTERNALTESSPOS를 사용해야합니다. 주의해야 합니다. 

 

 

 

 

//추가
struct TessellationFactors
{
    float edge[3] : SV_TessFactor;
    float inside : SV_InsideTessFactor;
    float3 bezierPoints[NUM_BEZIER_CONTROL_POINTS] : BEZIERPOS;
};

.
.
.
.


// Hull 함수는 버텍(정점)당 한번씩 실행됩니다. 정 을 수정하는데 사용할 수 있습니다
// 삼각형 전체의 값을 기준으로 한 자료
[domain("tri")]//삼각형을 입력하는 신호입니다 입력패치의 유형입니.
[outputcontrolpoints(3)] //삼각형의 점은 3개 출력패치 유형을 결정
[outputtopology("triangle_cw")] //삼각형을 출력한다는 신호 출력패치 유형을 결정
[patchconstantfunc("PatchConstantFunction")] //패치 상수 함수를 등록
[partitioning("integer")] //분할 모드를 선택 : int , fractional_odd,fractional_evenor 또는 pow2   삼각형을 세분화하는 데 사용할 알고리즘을 테셀레이터에 알려줌
TesselationControlPoint Hull(
    InputPatch<TessellationControlPoint,3> patch /*삼각형 입력*//* 정점 함수 출력 구조와 패치의 버텍스 수는 괄호안에 들어감. 배열처럼 패치의 각 구조에 액세스 가능*/,
    uint id : SV_OutputControlPointID /*삼각형의 정  인덱스*/)
{
    return patch[id];
}


/////////////////////////패치 상수 함수 ////////////////////////
// 패치 상수 함수는 삼각형당 한 번 실행되거나 "patch"
// Hull 함수와 병렬적으로 작동합니다 .
TessellationFactors PatchConstantFunction(InputPatch<TessellationControlPoint,3> patch)
{
    UNITY_SETUP_INSTANCE_ID(patch[0]); //Set up instancing
    //Calculate tesellation factors
    TessellationFactors f;
    f.edge[0] = 1;
    f.edge[1] = 1;
    f.edge[2] = 1;
    f.inside = 1;
    return f;
}

Hull 함수와 유사하게 Patch를 받지만 자체 데이터 구조를 출력합니다. 이 구조에는 SV_TessFactor 를 사용하여 삼각형의 모서리별로 지정된 테셀레이션 요소가 포함되어야 합니다 . 모서리는 동일한 인덱스를 갖는 꼭지점의 반대편에 배열됩니다. 따라서 가장자리 0은 정점 1과 정점 2 사이에 있습니다.

 

 

//추가
struct TessellationFactors
{
    float edge[3] : SV_TessFactor;
    float inside : SV_InsideTessFactor;
    float3 bezierPoints[NUM_BEZIER_CONTROL_POINTS] : BEZIERPOS;
};

SV_InsideTessFactor 태그가 지정된 중앙 테셀레이션 요소도 있습니다 . 곧 이러한 요소가 최종 테셀레이션 패턴에 어떻게 영향을 미치는지 시각화할 것입니다. 하지만 지금은 edge가 세분화되는 횟수이고 insde의 제곱은 대략 원본 내부에 생성된 새 삼각형의 수라는 점을 인식하세요.

BEZIERPOS 시멘틱은 float3 배열 형식을 취할 수 있으므로 유용합니다 . 나중에 이를 베지어 곡선 기반 평활화 알고리즘에 대한 제어점을 사용하는데 필요한 모든 것을 저장할 수 있습니다.

 

 

 

 

 

 

 

 

 

Domain

struct Interpolators {
    float3 normalWS                 : TEXCOORD0;
    float3 positionWS               : TEXCOORD1;
    float4 positionCS               : SV_POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
    UNITY_VERTEX_OUTPUT_STEREO
};


// Call this macro to interpolate between a triangle patch, passing the field name
#define BARYCENTRIC_INTERPOLATE(fieldName) \
		patch[0].fieldName * barycentricCoordinates.x + \
		patch[1].fieldName * barycentricCoordinates.y + \
		patch[2].fieldName * barycentricCoordinates.z




[domain("tri")]
Interpolators Domain
(
    TessellationFactors factors, // 패치 상수 함수의 출력값
    OutputPatch<TessellationControlPoint,3> patch, //삼각형
    float3 barycentricCoordinates : SV_DomainLocation //삼각형의 정점의 무게중심 좌표
)
{
    Interpolators output;

    //Setup instancing and stereo support (for VR)
    UNITY_SETUP_INSTANCE_ID(patch[0]);
    UNITY_TRANSFER_INSTANCE_ID(patch[0],output);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

    float3 positionWS = BARYCENTRIC_INTERPOLATE(positionWS);
    float3 normalWS = BARYCENTRIC_INTERPOLATE(normalWS);

    output.positionCS = TransformWorldToHClip(positionWS);
    output.normalWS = normalWS;
    output.positionWS = positionWS;

    return output;
}

도메인 함수에는 Hull 함수의 출력 토폴로지와 일치해야 하는 도메인 속성도 있습니다.

인수로 패치에 배열된 Hull 함수의 출력과 패치 상수 함수의 출력을 받습니다. 마지막으로 SV_DomainLocation 태그가 지정된 작업할 정점의 무게중심 좌표도 받습니다.

 

출력 구조는 버텍스 함수에서 출력하는 것과 매우 유사합니다. 여기에는 클립 공간 위치(Geometry Stage를 사용하지 않는 한)와 Fragment 함수가 조명에 필요한 모든 필드가 포함되어야 합니다. 또한 BARYCENTRIC_INTERPOLATE 매크로를 사용해 패치 구조의 속성을 보간할 수 있습니다.

이것이 일반적인 테셀레이션의 구조입니다.

 

#ifndef TESSELLATION_SAMPLE_INCLUDED
#define TESSELLATION_SAMPLE_INCLUDED

#include"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

#if defined(_TESSELLATION_SMOOTHING_VCOLORS) || defined(_TESSELLATION_FACTOR_VCOLORS)
    #define REQUIRES_VERTEX_COLORS

#endif

// Define the length of the bezier array
#if defined(_TESSELLATION_SMOOTHING_BEZIER_LINEAR_NORMALS)
#define NUM_BEZIER_CONTROL_POINTS 7
#elif defined(_TESSELLATION_SMOOTHING_BEZIER_QUAD_NORMALS)
#define NUM_BEZIER_CONTROL_POINTS 10
#else
#define NUM_BEZIER_CONTROL_POINTS 0
#endif



// Call this macro to interpolate between a triangle patch, passing the field name
#define BARYCENTRIC_INTERPOLATE(fieldName) \
		patch[0].fieldName * barycentricCoordinates.x + \
		patch[1].fieldName * barycentricCoordinates.y + \
		patch[2].fieldName * barycentricCoordinates.z



struct Attributes {
    float3 positionOS : POSITION;
    float3 normalOS : NORMAL;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct TessellationControlPoint {
    float3 positionWS : INTERNALTESSPOS; //POSITION 시멘틱은 금지 대신 INTERNALTESSPOS를 사용합니다
    float3 normalWS : NORMAL;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct TessellationFactors
{
    float edge[3] : SV_TessFactor;
    float inside : SV_InsideTessFactor;
};

struct Interpolators
{
    float3 normalWS                 : TEXCOORD0;
    float3 positionWS               : TEXCOORD1;
    float4 positionCS               : SV_POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
    UNITY_VERTEX_OUTPUT_STEREO
};



//단순히 위치와 법선을 월드 공간으로 변환합니다
//출력은 Hull 단계에  공급됩니다
TessellationControlPoint Vertex(Attributes input) {
    TessellationControlPoint output;

    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);

    VertexPositionInputs posnInputs = GetVertexPositionInputs(input.positionOS);
    VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS);

    output.positionWS = posnInputs.positionWS;
    output.normalWS = normalInputs.normalWS;
    return output;
}





// Hull 함수는 버텍(정점)당 한번씩 실행됩니다. 정 을 수정하는데 사용할 수 있습니다
// 삼각형 전체의 값을 기준으로 한 자료
[domain("tri")]//삼각형을 입력하는 신호입니다 입력패치의 유형입니.
[outputcontrolpoints(3)] //삼각형의 점은 3개 출력패치 유형을 결정
[outputtopology("triangle_cw")] //삼각형을 출력한다는 신호 출력패치 유형을 결정
[patchconstantfunc("PatchConstantFunction")] //패치 상수 함수를 등록
[partitioning("pow2")] //분할 모드를 선택 : integer , fractional_odd,fractional_evenor 또는 pow2   삼각형을 세분화하는 데 사용할 알고리즘을 테셀레이터에 알려줌
TessellationControlPoint Hull(
    InputPatch<TessellationControlPoint,3> patch /*삼각형 입력*//* 정점 함수 출력 구조와 패치의 버텍스 수는 괄호안에 들어감. 배열처럼 패치의 각 구조에 액세스 가능*/,
    uint id : SV_OutputControlPointID /*삼각형의 정  인덱스*/)
{
    return patch[id];
}

// 패치 상수 함수는 삼각형당 한 번 실행되거나 "patch"
// Hull 함수와 병렬적으로 작동합니다 .
TessellationFactors PatchConstantFunction(InputPatch<TessellationControlPoint,3> patch)
{
    UNITY_SETUP_INSTANCE_ID(patch[0]); //Set up instancing
    //Calculate tesellation factors
    TessellationFactors f;
    f.edge[0] = _TessellationFactor;
    f.edge[1] = _TessellationFactor;
    f.edge[2] = _TessellationFactor;
    f.inside = 3;
    return f;
}




// The domain function runs once per vertex in the final, tessellated mesh
// Use it to reposition vertices and prepare for the fragment stage
[domain("tri")] // Signal we're inputting triangles
Interpolators Domain(
    TessellationFactors factors, // The output of the patch constant function
    OutputPatch<TessellationControlPoint, 3> patch, // The Input triangle
    float3 barycentricCoordinates : SV_DomainLocation) { // The barycentric coordinates of the vertex on the triangle

    Interpolators output;

    // Setup instancing and stereo support (for VR)
    UNITY_SETUP_INSTANCE_ID(patch[0]);
    UNITY_TRANSFER_INSTANCE_ID(patch[0], output);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

    float3 positionWS = BARYCENTRIC_INTERPOLATE(positionWS);
    float3 normalWS = BARYCENTRIC_INTERPOLATE(normalWS);

    output.positionCS = TransformWorldToHClip(positionWS);
    output.normalWS = normalWS;
    output.positionWS = positionWS;

    return output;
}

half4 Fragment(Interpolators input) : SV_TARGET {

    return half4(1,1,1,1);
}

#endif

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

VS_OUT VS_Main(VS_IN input)
{
    VS_OUT output = input;
    return output;
}


//Control Point HS
[domain("tri")]//패치의 종류 (tri,quad,isoline)
[partitioning("integer")]// 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;
} 






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;

    //숫자를 지정 , 삼각형 분할 관련
    output.edgeTess[0] = 1; //1이면 쪼개지않음
    output.edgeTess[1] = 2; //2이면 변을 2개로 2등분을 한다는 뜻
    output.edgeTess[2] = 3; //3이면 변을 3개....
    output.insideTess = 1; //중앙에서의 삼각형 분할 멀리 있을때는 1 ,1 , 1 가까이 올수록 저 많이 쪼개면 됨

    return output;
}


//////------여기까지 HullShader------------

//---------------
//Domain Shader
//--------------

struct DS_OUT
{
   float4 pos : SV_POSITION;
   float2 uv : TEXCOORD;
};

//Patch 정보와 location 정보가 넘어옴
[domain("tri")]
DS_OUT DS_Main(const OuptutPatch<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;
}

Tessel.shader
0.00MB
TessellationSample.hlsl
0.01MB

'Public > Shader' 카테고리의 다른 글

CustomTMP(Glitch)  (0) 2023.09.16
CommandBuffer(Blur)  (0) 2023.09.12
[ShaderGraph] ToonShader(툰쉐이더)  (0) 2023.06.19
Alpha2Pass  (0) 2022.07.28
zwrite on/off  (0) 2022.07.28