细分曲面(Tessellation)
渲染中的Tesselation阶段
渲染流程中细分曲面一直是个固定步骤,这个步骤是非可编程阶段,由GPU自动完成,位于vertex和geometry之间。这一阶段又细分为Hull,Tessellation和Domain三个子步骤。
Hull阶段会告诉接下来的细分阶段一些必要的数据信息,比如需要细分的面,细分的程度等。
细分阶段会根据Hull阶段传入的Patch(包含顶点信息)和细分程度参数将它们分割为更小的面。
Domain阶段是个可选阶段(optional),它会根据hull阶段和细分阶段的输出结果计算最终的图元(primitive)属性。
网格细化/Mesh Refinement
网格细化通常包含两个流程:
- Tessellation。这一步将mesh细分成一系列更小的三角图元,但几何体不会变形。
- Displacement。通过displacement方程计算上一步生成的vertex的最终坐标。
渲染流程
渲染流程分三步——Depth-tagging、CPU层面渲染、GPU层面细分。
深度标签/Depth-Tagging
细分mesh会带来更多的vertex,造成CPU和GPU(主要)压力,因此有了自适应网格细化技术,根据一些因素决定网格的细分程度。
通常会给vertex打标签(depth-tagging),根据标签进行不同程度的细化。
常见的依据有:
- 通过比较相邻网格的平滑程度(flatness)决定是否进行细分。曲度大则进行细分,平滑区域则不细分或少细分。
- 根据vertex距离camera的距离给顶点打标签。距离camera近则细分程度高,距离camera远则细分程度低。
下图则为自适应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"
}
几个常用方法解释:
- UnityDistanceBasedTess和UnityCalcTriEdgeTessFactors
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
3.GPU GEMS 3 Chapter 5. Generic Adaptive Mesh Refinement
4.GPU GEMS 2 Chapter 7. Adaptive Tessellation of Subdivision Surfaces with Displacement Mapping