Tessellation Shader
总结一下在vertex和fragment shader里面如何使用曲面细分着色器,在Surface shader里面这个过程已经被高度封装好了,所以非常简单,直接tessellation :tess调用处理函数就行了,要在vertex-frag shader里面做就需要完全了解它的底层实现。
这个shader和Geometry Shader是顶点着色器之后的可选着色器,是在光栅化之前最后对顶点做操作的机会。为了使用细分着色器,在顶点着色器里面就可以不对顶点做任何的操作,直接把数据传到细分着色器。
细分着色器是由三个阶段实现的:
Hull Shader
由两部分组成:Constant Hull Shader、Control Point Shader
对于Constant Hull Shader主要就是设置细分因子,告诉后面的细分阶段如何去细分。
然后是Control Point Shader,它主要就是把目前模型以及有的顶点作为控制点然后输入,最后输出多个控制点(一般还是输出一个控制点),每输出一个控制点都要调用一次这个阶段。
传入InputPatch数据以及SV_OutputControlPointID,曲面细分可以处理三角形、四边形或者线。patch是一个mesh vertices的集合,对于这个输入参数需要定义它是怎样的structure和每一个patch包含了几个vertices,SV_OutputControlPointID则是需要传入的id,每次向tessellation stage传送的数据集合只能有一个,它的标识是一个unsighed integer,用来确定具体是哪一个。
然后还需要指定如何进行细分:
1 2 3 4 5 6 7 8 9 10
| [UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("integer")]
[UNITY_patchconstantfunc("MyPatchConstantFunction")]
|
hullshader的作用就是pass the required vertex data to the tessellation stage。
Tessellation Stage
不可编程,由硬件完成,根据hull shader的数据(细分因子和控制点)进行细分,但它并不会生成新的顶点,只是对之前的顶点生成了重心坐标,之后Domain Shader才用这些数据去生成新的坐标点。
Domain Shader
把细分过的顶点坐标,投影到Clip Space。在domain shader中,TessellationFactors、细分过的顶点数据、重心坐标(SV_DomainLocation)会作为(u,v,w)输入,使用这个和实际顶点位置一来计算得到实际的顶点坐标。
Okk,来实践
首先是顶点着色器相关的处理流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #pragma vertex vert struct vertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; };
struct vertexOutput { float4 vertex : SV_POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; };
vertexInput vert(vertexInput v) { return v; }
vertexOutput tessVertTransformed(vertexInput v) { vertexOutput o; o.vertex = UnityObjectToClipPos(v.vertex); o.normal = v.normal; o.tangent = v.tangent; return o; }
|
进入细分着色器,首先是Hull Shader:
1 2 3 4 5 6 7 8 9
| [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]; }
|
设置细分因子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| struct TessellationFactors { float edge[3] : SV_TessFactor; float inside : SV_InsideTessFactor; };
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; }
|
计算顶点,插值操作,确定新顶点位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| [UNITY_domain("tri")] vertexOutput custom_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 tessVertTransformed(v); }
|
这样就从最基础的层面实现了曲面细分shader,也就知道了surfaceshader是如何帮我们打包的了。