目录:Unity Shader - 知识点目录(先占位,后续持续更新) 原文:Surface Shaders with DX11 / OpenGL Core Tessellation 版本:2019.1
Surface Shaders with DX11 / OpenGL Core Tessellation表面着色器使用DX11/OpenGL Core曲面细分
Surface Shaders 有一些支持DirectX 11 / OpenGL GPU的曲面细分。
- 指定使用曲面细分功能使用 tessellate:FunctionName 修改器。该函数计算三角形边与面积作为曲面细分的因子。
- 当使用曲面细分是,“vertex modifier”(vertex:FunctionName 将会再曲面细分后执行,每个顶点在shader执行域内生成。在这你需要处理displacement mapping(位移映射)。
- 表面着色器可以可选的使用phong tessellation(冯氏曲面细分)的平滑计算模型表面,甚至不需要displacement mapping(位移映射)的配合使用了。
当前支持曲面细分的一些限制:
- 仅支持三角形细分,不支持四边形,不支持等值线的细分。
- 当你使用曲面细分,着色器将自动编译为4.6版本的shader model,这样就可以阻止比较旧的硬件中运行。
没有曲面细分,但有顶点位移映射修改器处理
下面的例子展示位移映射,但没有曲面细分的处理。位移映射处理中仅仅是沿着法线方向移动,移动量为:从位移贴图从采样的值。
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"
}
上面的是比较标准的着色器写法:
- 顶点修改器disp采样了位移贴图(国内有些人叫:置换贴图,反正都是前人翻译留下来的坑,一代代的坑下去了,-_-!)位移贴图并沿着法线方向位移顶点。
- 使用了自定义的"vertex data input"(顶点输入数据)的数据结构(appdata)而不是使用默认的appdata_full。因为不一定之内用appdata_full,很多不需要的功能、变量,你自定义后,曲面细分处理时将效率更高,尽量使用更小的数据结构。
- 因为我们不需要第二个UV坐标,所以我们添加了nolightmap指令来禁止使用lightmaps。
下图显示了该着色的应用效果。
固定的细分量
如果你的模型渲染出来的面的大小在屏幕上显示都大概差不多,这时给网格(整个网格都将应用一样的细分)使用固定的细分量的处理是OK的。
下面是应用了固定细分量的例子。
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"
}
上面例子中,tessFixed 全面细分函数返回4因子在一个float4的值里:3个因子是给三角形三边的,1个因子是给三角形面积的。
该例子中tessellate函数是直接返回一个Material Inspector中设置的属性_Tess的值。

基于距离的曲面细分
你可以基于与相机的距离来改变曲面细分的程度。例如,你可以定义两个距离值:
- 曲面细分的最大距离(如:10米)。
- 曲面细分程度的逐渐减少的距离(如: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"
}
曲面细分函数以三角形的三个顶点坐标值前三个参数。
Unity需要这些顶点数据来计算曲面细分程度。
例子include了内置的文件,Tessellation.cginc,并从此文件调用了UnityDistanceBasedTess函数来完成所有处理。此函数计算了每个顶点与相机的距离,然后推算出最终的曲面细分因子。

基于边长的曲面细分
基于纯距离的曲面细分仅仅作用于三角形都差不多的大小(因为距离差不多)。在上图中,很小的三角形的曲面细分是相当多的,导致大三角形的曲面细分不够。
有一种方法可以改善这些曲面细分的程度,基于三角形对象屏幕上的边长。这样大块的三角形也会曲面细分了。
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"
}
在此例中,调用了Tessellation.cginc中UnityEdgeLengthBasedTess函数来处理。 为么性能考虑,调用的UnityEdgeLengthBasedTessCull增加了对相机视椎的剔除。这么做虽然看起来shader的处理内容变多了而需要更多性能似的,其实它可以节省了相机视图外的网格的处理那部分消耗处理。
冯氏曲面细分
Phong Tessellation修改了细分面的顶点位置,对应修改面的法线也调整了。这对应平滑低模来说是个很有效的方法。
Unity Surface Shader可以计算Phong tessellation使用tessphong:VeriableName编译指令。
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"
}
比较正常shader(上面那排)和使用了Phong tessellation(下面那配)。可以看到,没有及时位移映射,曲面也一样变得平滑了。