Surface Shaders with DX11 / OpenGL Core Tessellation
DX11/OpenGL核心下表面着色器的曲面细分
本文档主要是对Unity官方手册的个人理解与总结(其实以翻译记录为主:>)
仅作为个人学习使用,不得作为商业用途,欢迎转载,并请注明出处。
文章中涉及到的操作都是基于Unity2018.2版本
参考链接:https://docs.unity3d.com/Manual/SL-SurfaceShaderTessellation.html
Surface Shaders have some support for DirectX 11 / OpenGL Core GPU Tessellation. Idea is:
表面着色器对DirectX 11/OpenGL核心GPU曲面细分作了一些支持。想法是:
- Tessellation is indicated by tessellate:FunctionName modifier. That function computes triangle edge and inside tessellation factors. Tessellation 由tessellate:FunctionName 调节器声明。这个函数计算三角形边和曲面细分因子。
- When tessellation is used, “vertex modifier” (vertex:FunctionName) is invoked after tessellation, for each generated vertex in the domain shader. Here you’d typically to displacement mapping.当使用tessellation时,“顶点调节器”(vertex:FunctionName)在曲面细分之后被调用,对着色器中的每个生成的顶点起作用。这里通常是位移映射。
- Surface shaders can optionally compute phong tessellation to smooth model surface even without any displacement mapping. 即使没有任何位移映射,表面着色器也可以选择计算phong曲面细分到光滑的模型表面。
Current limitations of tessellation support:
目前的曲面细分支持限制:
- Only triangle domain - no quads, no isoline tessellation. 只有三角形域-没有四分,没有像素线曲面细分。
- When you use tessellation, the shader is automatically compiled into the Shader Model 4.6 target, which prevents support for running on older graphics targets. 当您使用曲面细分时,着色器会自动被编译成着色器模型4.6目标,这将阻止对旧图形目标的支持。
**No GPU tessellation, displacement in the vertex modifier **
This next example shows a surface shader that does some displacement mapping without using tessellation. It just moves vertices along their normals based on the amount coming from a displacement map:
下一个例子展示了一个表面着色器,它可以在不使用曲面细分的情况下进行一些位移映射。它只是根据位移贴图的数量来沿着法线移动顶点位置。
Shader "Tessellation Sample" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp nolightmap
#pragma target 4.6
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
The above shader is fairly standard:
上面的着色器是相当标准的:
- Vertex modifier disp samples the displacement map and moves vertices along their normals. 顶点调节器对disp采样位移映射并移动顶点沿着法线的方向。
- It uses custom “vertex data input” structure (appdata) instead of default appdata_full. This is not needed yet, but it’s more efficient for tessellation to use as small structure as possible.它使用自定义的“顶点数据输入”结构体(appdata)而不是默认的appdatafull。这不是必要的,但是对于曲面细分来说,它使用尽可能小的结构将更有效率。
- Since our vertex data does not have 2nd UV coordinate, we add nolightmap directive to exclude lightmaps.由于我们的顶点数据没有第二个UV坐标,所以我们添加nolightmap指令来排除lightmaps。
The image below displays some simple GameObjects with this shader applied.

Fixed amount of tessellation 固定数量的曲面细分
If your model’s faces are roughly the same size on screen, add a fixed amount of tesselation to the Mesh
(the same tessellation level over the whole Mesh).
The following example script applies a fixed amount of tessellation.
如果你的模型的面在屏幕上的大小大致相同,那么在网格中添加一个固定数量的tesselation
(同样的曲面细分水平在整个网格上)。
下面的示例脚本应用了固定数量的曲面细分。
Shader "Tessellation Sample" {
Properties {
_Tess ("Tessellation", Range(1,32)) = 4
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessFixed nolightmap
#pragma target 4.6
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _Tess;
float4 tessFixed()
{
return _Tess;
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
In the example above, the tessFixed tessellation function returns four tessellation factors as a single float4 value: three factors for each edge of the triangle, and one factor for the inside of the triangle.
在上面的例子中,tessFixed tessellation函数返回四个tessellation 因子作为一个单独的float4 值:三个因子为三角形的每边,以及一个因子为三角形内。
The example returns a constant value that is set in the Material properties.
这个例子返回一个固定值,它被设置在材质属性中。

Distance-based tessellation 基于距离的曲面细分
You can also change tessellation level based on distance from the camera. For example, you could define two distance values:
你也可以根据相机的距离来改变曲面细分水平。例如,你可以定义两个距离值:
- The distance when tessellation is at maximum (for example, 10 meters). 曲面细分最大时的距离。比如10米
- The distance when the tessellation level gradually decreases (for example, 20 meters). 当曲面细分水平逐渐下降时的距离。比如20米
Shader "Tessellation Sample" {
Properties {
_Tess ("Tessellation", Range(1,32)) = 4
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessDistance nolightmap
#pragma target 4.6
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _Tess;
float4 tessDistance (appdata v0, appdata v1, appdata v2) {
float minDist = 10.0;
float maxDist = 25.0;
return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
Here, the tessellation function takes the vertex data of the three triangle corners before tessellation as its three parameters.在此,tessellation 函数将三个三角形角的顶点数据作为它的三个参数。
Unity needs this to compute tessellation levels, which depend on vertex positions. Unity需要这个来计算tessellation 水平,这取决于顶点的位置。
The example includes a built-in helper file, Tessellation.cginc, and calls the UnityDistanceBasedTess function from the file to do all the work. This function computes the distance of each vertex to the camera and derives the final tessellation factors. 这个例子包括一个内置的助手文件,Tessellation.cginc,并调用UnityDistanceBasedTess 函数来完成所有的工作。这个函数计算每个顶点到相机的距离,得出最终的tessellation 因子。

Edge length based tessellation 基于边缘长度的曲面细分
Purely distance based tessellation is effective only when triangle sizes are quite similar. In the image above, the GameObjects that have small triangles are tessellated too much, while GameObjects that have large triangles aren’t tessellated enough.
只有当三角形大小非常相似时,纯距离的曲面细分才会有效。在上面的图像中,有小三角形的配子被曲面细分得太多,而拥有大三角形的配子则没有足够的曲面细分。
One way to improve this is to compute tessellation levels based on triangle edge length on the screen. Unity should apply a larger tessellation factor to longer edges.
改进这个方法的一种方法是根据屏幕上的三角形边缘长度来计算曲面细分水平。Unity应该将一个更大的曲面细分因子应用到更长的边缘。
Shader "Tessellation Sample" {
Properties {
_EdgeLength ("Edge length", Range(2,50)) = 15
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessEdge nolightmap
#pragma target 4.6
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _EdgeLength;
float4 tessEdge (appdata v0, appdata v1, appdata v2)
{
return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
In this example, you call the UnityEdgeLengthBasedTess function from Tessellation.cginc to do all the work.
在这个例子中,您可以从Tessellation.cginc 中调用UnityEdgeLengthBasedTess 函数去做所有的工作。

For performance reasons, call the UnityEdgeLengthBasedTessCull function instead, which performs patch frustum culling. This makes the shader a bit more expensive, but saves a lot of GPU work for parts of meshes that are outside of the Camera’s view.
出于性能原因,UnityEdgeLengthBasedTessCull 该函数执行补面片在截锥体内。这使得着色器的价格更加昂贵,但在摄像头的视图之外,为网格的某些部分节省了大量的GPU。
Phong Tessellation
Phong Tessellation modifies positions of the subdivided faces so that the resulting surface follows the mesh normals a bit. It’s quite an effective way of making low-poly meshes become more smooth.
Phong Tessellation修改了被细分的面片的位置,这样得到的表面就会跟随网格法线。这是一种非常有效的方法,可以使低多边形变得更加平滑。
Unity’s surface shaders can compute Phong tessellation automatically using tessphong:VariableName compilation directive. Here’s an example shader: Unity的表面着色器可以使用tessphong:VariableName的编译指令自动计算Phong tessellation。这里有一个 shader:
Shader "Phong Tessellation" {
Properties {
_EdgeLength ("Edge length", Range(2,50)) = 5
_Phong ("Phong Strengh", Range(0,1)) = 0.5
_MainTex ("Base (RGB)", 2D) = "white" {}
_Color ("Color", color) = (1,1,1,0)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf Lambert vertex:dispNone tessellate:tessEdge tessphong:_Phong nolightmap
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
void dispNone (inout appdata v) { }
float _Phong;
float _EdgeLength;
float4 tessEdge (appdata v0, appdata v1, appdata v2)
{
return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
struct Input {
float2 uv_MainTex;
};
fixed4 _Color;
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
Here is a comparison between a regular shader (top row) and one that uses Phong tessellation (bottom row). See that even without any displacement mapping, the surface becomes more round.
下面是一个常规着色器(上一行)和一个使用Phong tessellation(下一行)的比较。即使没有任何位移映射,曲面也会变得更圆滑。


这篇博客探讨了如何在Unity中利用DX11/OpenGL核心曲面细分(Tessellation)技术,用于Surface Shaders。内容包括固定数量的曲面细分、基于距离的曲面细分、基于边缘长度的曲面细分以及Phong Tessellation,以实现模型表面的平滑效果。同时,提到了曲面细分的限制和如何在材质中设置固定曲面细分级别。

被折叠的 条评论
为什么被折叠?



