Shader Learning_ Tessellation Shader

Tessellation
in detail

Of course, in addition to subdivision and simplification, there is another operation of the same type called == Surface Regularization (Mesh Regularization) == What it does is to make the triangular faces as identical as possible, thereby also achieving improvement The purpose of model effects

Surface subdivision shader content:

Link to the original text

  • Surface subdivision is divided into: Hull shader, Tessellation Primitive Generator, Domain shader
    • The main function of the Hull shader: define some subdivision parameters (such as: how to subdivide each edge, how to subdivide the inner triangle)
    • Tessellation Primitive Generator, non-programmable
    • Domain shader: The point subdivided by the surface subdivision shader is located in the center of gravity space, and the function of this part is to transform it into the space we want to use.

Hull Shader

Consists of two parts:

  • Constant Hull Shader: This constant hull shader is executed once for each patch (original triangle), and its function is to output the so-called tessellation factor. The tessellation factor is used to tell the hardware how to patch in the tessellation stage to subdivide.
struct PatchTess
{
    
    
    float EdgeTess[3]:SV_TessFactor;
    float InsideTess:SV_InsideTessFactor;
};
PatchTess constantHS(InputPatch<VertexOut,3> patch,uint patchID:SV_PrimitiveID)
{
    
    
    PatchTess pt;
    pt.EdgeTess[0]=3;
    pt.EdgeTess[1]=3;
    pt.EdgeTess[2]=3;
    pt.InsideTess=3;    
    
}

The constant hull shader takes all the control points in the patch as input through InputPatch<VertexOut,3>. Looking at the rendering pipeline diagram, we can know that the input of the hull shader comes from the vertex shader, so VertexOut is the output of the vertex shader. At the same time, the system provides a variable called patch ID through SV_PrimitiveID, which is the unique identification of the patch in this drawing. The constant hull shader must have a tessellation factor as output. For a model whose topology is triangular, the structure of its subdivision factor is shown in the code above. The results for different subdivision factors are shown below
The effect of different subdivision factors

  • Control Point Hull Shader
    control point The hull shader takes multiple control points as input (raw model vertices) and outputs multiple control points. The control point hull shader is called every time a control point is output. Usually the number of control points output in the hull shader stage is the same as the number of input control points, unless we want to change the geometry of the model, such as outputting a triangular surface as a third-order Bezier surface. The actual tessellation is done in the next tessellation stage.
struct HullOut
{
    
    
    float3 PosL:TEXCOORD0;
};

[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("ConstantHS")]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut,3> p,uint i:SV_OutputControlPointID)
{
    
    
    HullOut hout;
    hout.PosL=p[i].PosL;
    return hout;
}

As mentioned earlier, the control point hull shader is executed once for each output control point, so the parameter i of SV_OutputControlPointID semantic modification is introduced here, indicating the index of the control point that the current hull shader is processing. In the example, the number of input control points is the same as the number of output control points, but in practice, the number of output control points can be more than the number of input control points, and the information of the extra control points can be calculated according to the algorithm and the input control points . The control point hull shader introduces a series of properties:

  • domain: The patch type or the topological structure of the model primitive. The valid parameters are tri, quad, and isoline. tri means triangle
  • partitioning: Specifies the split mode of the tessellation
    • integer: New vertices are only added or deleted when the subdivision factor is an integer, and the fractional part of the subdivision factor is ignored. In this case, for the dynamic surface subdivision factor, it will appear at a certain point (integer point) model accuracy Sudden increase or decrease, there will be a sudden change in accuracy visible to the naked eye
    • fraction: New vertices are still added or removed at integer subdivision factors (1.0, 2.0, 3.0, etc.), but smoothly transitioned according to the fractional part of the subdivision factor. fraction mode, including fractional_even and fractional_odd
  • outputtopology: the wrapping method of the output triangular face front
    • triangle_cw: The vertices are arranged clockwise to represent the front
    • triangle_ccw: The vertices are arranged counterclockwise to represent the front
    • line: only for line subdivision
  • outputcontrolpoints: The number of output control points, which is also the execution times of the control point hull shader, because every time a control point is output, the hull shader is executed once. The total number of index values ​​specified by the semantics of SV_OutputControlPointID corresponds to the number of control points here
  • patchconstantfunc: The name of the constant hull shader function specified by a string, which is the previous constant hull shader
  • maxtessfactor: Tell the hardware the maximum subdivision factor. With this upper limit value, the hardware can perform certain optimizations, such as knowing in advance how many resources are needed to perform tessellation. The maximum value supported by Direct3D 11 is 64. The maximum value supported by other hardware may be 16, so the use in unity usually uses 16 as the upper limit of the maximum value.

Tessellation Primitive Generator

As a programmer, we cannot control the execution of the tessellation stage. The tasks at this stage are all completed by the hardware. The hardware decides how to subdivide the patch according to the subdivision factor and control point output by the constant hull shader.

Domain Shader

The tessellation stage outputs all of our newly created vertices. For each vertex output by the tessellation stage, the domain shader is called once.
When tessellation is enabled, the function of the vertex shader is to process each control point, and the domain shader is the vertex shader that actually processes the subdivided patch. In use, we usually project the subdivided vertex coordinates to the homogeneous clipping space, including vertex normal, tangent, and UV processing. In the domain shader, the subdivision factor output by the constant hull shader and the control point output by the control point hull shader, and the parameterized (u, v, w) coordinates related to the position of the subdivided vertices are used as input, using this The parameterized (u, v, w) coordinates and other input parameters corresponding to the actual vertex position one-to-one, we can calculate the actual vertex coordinates.
For primitives whose topological structure is a triangular surface, the (uvw) three-dimensional coordinates here are the coordinates of the center of gravity. For other topologies such as quads, only two dimensions (uv) are required to describe subdivision coordinates (similar to texture uv).

struct DomainOut
{
    
    
    float4 PosH:SV_POSITION;    
};
[domain("tri")]
DomainOut DS(PatchTess patchTess,float3 baryCoords:SV_DomainLocation,const OutputPatch<HullOut,3> triangles)
{
    
    
    DomainOut dout;              
    float3 p=triangles[0].PosL*baryCoords.x+triangles[1].PosL*baryCoords.y+triangles[2].PosL*baryCoords.z;    
    dout.PosH=TransformObjectToHClip(p.xyz);
    return dout;
}

Tessellation Implementation

Step-by-step implementation of tessellation

  • A header file for a tessellation implementation:
    Customtessellation.cginc
// Tessellation programs based on this article by Catlike Coding:
// https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/

struct vertexInput
{
    
    
	float4 vertex : POSITION;
	float3 normal : NORMAL;
	float4 tangent : TANGENT;
};

struct vertexOutput
{
    
    
	float4 vertex : SV_POSITION;
	float3 normal : NORMAL;
	float4 tangent : TANGENT;
};

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

vertexInput vert(vertexInput v)
{
    
    
	return v;
}

vertexOutput tessVert(vertexInput v)
{
    
    
	vertexOutput o;
	// Note that the vertex is NOT transformed to clip
	// space here; this is done in the grass geometry shader.
	o.vertex = v.vertex;
	o.normal = v.normal;
	o.tangent = v.tangent;
	return o;
}

float _TessellationUniform;

TessellationFactors patchConstantFunction (InputPatch<vertexInput, 3> patch)
{
    
    
	TessellationFactors f;
	f.edge[0] = _TessellationUniform;
	f.edge[1] = _TessellationUniform;
	f.edge[2] = _TessellationUniform;
	f.inside = _TessellationUniform;
	return f;
}

[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("integer")]
[UNITY_patchconstantfunc("patchConstantFunction")]
vertexInput hull (InputPatch<vertexInput, 3> patch, uint id : SV_OutputControlPointID)
{
    
    
	return patch[id];
}

[UNITY_domain("tri")]
vertexOutput domain(TessellationFactors factors, OutputPatch<vertexInput, 3> patch, float3 barycentricCoordinates : SV_DomainLocation)
{
    
    
	vertexInput v;

	#define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) v.fieldName = \
		patch[0].fieldName * barycentricCoordinates.x + \
		patch[1].fieldName * barycentricCoordinates.y + \
		patch[2].fieldName * barycentricCoordinates.z;

	MY_DOMAIN_PROGRAM_INTERPOLATE(vertex)
	MY_DOMAIN_PROGRAM_INTERPOLATE(normal)
	MY_DOMAIN_PROGRAM_INTERPOLATE(tangent)

	return tessVert(v);
}

Complete shader:

Shader "Unlit/TessShader"
{
    
    
    Properties
    {
    
    
        _TessellationUniform("TessellationUniform",Range(1,64)) = 1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100
        Pass
        {
    
    
            CGPROGRAM
            //定义2个函数 hull domain
            #pragma hull hullProgram
            #pragma domain ds
           
            #pragma vertex tessvert
            #pragma fragment frag

            #include "UnityCG.cginc"
            //引入曲面细分的头文件
            #include "Tessellation.cginc" 

            #pragma target 5.0
            
            struct VertexInput
            {
    
    
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct VertexOutput
            {
    
    
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            VertexOutput vert (VertexInput v)
            //这个函数应用在domain函数中,用来空间转换的函数
            {
    
    
                VertexOutput o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.tangent = v.tangent;
                o.normal = v.normal;
                return o;
            }

            //有些硬件不支持曲面细分着色器,定义了该宏就能够在不支持的硬件上不会变粉,也不会报错
            #ifdef UNITY_CAN_COMPILE_TESSELLATION
                //顶点着色器结构的定义
                struct TessVertex{
    
    
                    float4 vertex : INTERNALTESSPOS;
                    float3 normal : NORMAL;
                    float4 tangent : TANGENT;
                    float2 uv : TEXCOORD0;
                };

                struct OutputPatchConstant {
    
     
                    //不同的图元,该结构会有所不同
                    //该部分用于Hull Shader里面
                    //定义了patch的属性
                    //Tessellation Factor和Inner Tessellation Factor
                    float edge[3] : SV_TESSFACTOR;
                    float inside  : SV_INSIDETESSFACTOR;
                };

                TessVertex tessvert (VertexInput v){
    
    
                    //顶点着色器函数
                    TessVertex o;
                    o.vertex  = v.vertex;
                    o.normal  = v.normal;
                    o.tangent = v.tangent;
                    o.uv      = v.uv;
                    return o;
                }

                float _TessellationUniform;
                OutputPatchConstant hsconst (InputPatch<TessVertex,3> patch){
    
    
                    //定义曲面细分的参数
                    OutputPatchConstant o;
                    o.edge[0] = _TessellationUniform;
                    o.edge[1] = _TessellationUniform;
                    o.edge[2] = _TessellationUniform;
                    o.inside  = _TessellationUniform;
                    return o;
                }

                [UNITY_domain("tri")]//确定图元,quad,triangle等
                [UNITY_partitioning("fractional_odd")]//拆分edge的规则,equal_spacing,fractional_odd,fractional_even
                [UNITY_outputtopology("triangle_cw")]
                [UNITY_patchconstantfunc("hsconst")]//一个patch一共有三个点,但是这三个点都共用这个函数
                [UNITY_outputcontrolpoints(3)]      //不同的图元会对应不同的控制点
              
                TessVertex hullProgram (InputPatch<TessVertex,3> patch,uint id : SV_OutputControlPointID){
    
    
                    //定义hullshaderV函数
                    return patch[id];
                }

                [UNITY_domain("tri")]//同样需要定义图元
                VertexOutput ds (OutputPatchConstant tessFactors, const OutputPatch<TessVertex,3>patch,float3 bary :SV_DOMAINLOCATION)
                //bary:重心坐标
                {
    
    
                    VertexInput v;
                    v.vertex = patch[0].vertex*bary.x + patch[1].vertex*bary.y + patch[2].vertex*bary.z;
			        v.tangent = patch[0].tangent*bary.x + patch[1].tangent*bary.y + patch[2].tangent*bary.z;
			        v.normal = patch[0].normal*bary.x + patch[1].normal*bary.y + patch[2].normal*bary.z;
			        v.uv = patch[0].uv*bary.x + patch[1].uv*bary.y + patch[2].uv*bary.z;

                    VertexOutput o = vert (v);
                    return o;
                }
            #endif

            float4 frag (VertexOutput i) : SV_Target
            {
    
    

                return float4(1.0,1.0,1.0,1.0);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

Guess you like

Origin blog.csdn.net/suixinger_lmh/article/details/125140224