细分曲面(Tessellation)

渲染中的Tesselation阶段

渲染流程中细分曲面一直是个固定步骤,这个步骤是非可编程阶段,由GPU自动完成,位于vertex和geometry之间。这一阶段又细分为Hull,Tessellation和Domain三个子步骤。

Hull阶段会告诉接下来的细分阶段一些必要的数据信息,比如需要细分的面,细分的程度等。

细分阶段会根据Hull阶段传入的Patch(包含顶点信息)和细分程度参数将它们分割为更小的面。

Domain阶段是个可选阶段(optional),它会根据hull阶段和细分阶段的输出结果计算最终的图元(primitive)属性。

网格细化/Mesh Refinement

网格细化通常包含两个流程:

渲染流程

渲染流程分三步——Depth-tagging、CPU层面渲染、GPU层面细分。

深度标签/Depth-Tagging

细分mesh会带来更多的vertex,造成CPU和GPU(主要)压力,因此有了自适应网格细化技术,根据一些因素决定网格的细分程度。

通常会给vertex打标签(depth-tagging),根据标签进行不同程度的细化。

常见的依据有:

下图则为自适应camera距离的细化(b)和自适应曲度的细化(c)的示例:

CPU层面

CPU层面需要进行的工作主要是根据之前的vertex标签分配足够的index buffer然后进行绘制。绑定操作的次数因为前一步的标签处理而大大减少。

GPU层面

vertex程序包含三个阶段:tessellation、displacement、shading。

Unity Shader中的Tessellation

Unity中有内置的细分shader示例,也包含内置的细分函数。

以下是一个简单的基于camera距离的细分shader:

Shader "Tessellation" {

	Properties{
		[Header(Main)]
		[Space]
		[Header(Tesselation)]
		_MaxTessDistance("Max Tessellation Distance", Range(10,100)) = 50
		_Tess("Tessellation", Range(1,32)) = 20
	}

		SubShader{
			Tags{ "RenderType" = "Opaque" }
			LOD 200

			CGPROGRAM

			#pragma surface surf Lambert vertex:vert addshadow nolightmap tessellate:tessDistance fullforwardshadows
			#pragma target 4.0
			#pragma require tessellation tessHW
			#include "Tessellation.cginc"

			float _Tess;
			float _MaxTessDistance;

			float4 tessDistance(appdata_full v0, appdata_full v1, appdata_full v2)
			{
				float minDist = 10.0;
				float maxDist = _MaxTessDistance;

				return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
			}

			struct Input {
				float4 vertexColor : COLOR;
			};

			void vert(inout appdata_full v)
			{

			}

			void surf(Input IN, inout SurfaceOutput o) 
			{
				o.Albedo = IN.vertexColor;
			}
			ENDCG

		}

			Fallback "Diffuse"
}

几个常用方法解释:

float4 UnityCalcTriEdgeTessFactors (float3 triVertexFactors)
{
    float4 tess;
    tess.x = 0.5 * (triVertexFactors.y + triVertexFactors.z);
    tess.y = 0.5 * (triVertexFactors.x + triVertexFactors.z);
    tess.z = 0.5 * (triVertexFactors.x + triVertexFactors.y);
    tess.w = (triVertexFactors.x + triVertexFactors.y + triVertexFactors.z) / 3.0f;
    return tess;
}

// Distance based tessellation:
// Tessellation level is "tess" before "minDist" from camera, and linearly decreases to 1
// up to "maxDist" from camera.
float4 UnityDistanceBasedTess (float4 v0, float4 v1, float4 v2, float minDist, float maxDist, float tess)
{
    float3 f;
    f.x = UnityCalcDistanceTessFactor (v0,minDist,maxDist,tess);
    f.y = UnityCalcDistanceTessFactor (v1,minDist,maxDist,tess);
    f.z = UnityCalcDistanceTessFactor (v2,minDist,maxDist,tess);

    return UnityCalcTriEdgeTessFactors (f);
}

参考

1.Learning DirectX 12 – Lesson 1 – Initialize DirectX 12

2.Catlike Coding·Tessellation

3.GPU GEMS 3 Chapter 5. Generic Adaptive Mesh Refinement

4.GPU GEMS 2 Chapter 7. Adaptive Tessellation of Subdivision Surfaces with Displacement Mapping