在内置渲染管线中,表面着色器支持基于 DirectX 11 和 OpenGL Core 的 GPU 曲面细分(Tessellation),通过#pragma tessellate
指令定义细分因子计算函数。以下是不同场景下的实现示例及技术细节:
一、曲面细分基础架构
核心指令
hlsl
#pragma surface surf <光照模型> tessellate:<细分函数> [vertex:<顶点修改函数>]
#pragma target 4.6 // 要求Shader Model 4.6及以上
- 细分函数:计算三角形边缘(Edge)和内部(Inside)的细分因子。
- 顶点修改函数:在细分后对生成的顶点进行位移(如置换贴图)。
细分因子结构
细分函数需返回float4
类型,分别对应:
x
:Edge0(顶点 0 - 顶点 1 的边缘细分因子)y
:Edge1(顶点 1 - 顶点 2 的边缘细分因子)z
:Edge2(顶点 2 - 顶点 0 的边缘细分因子)w
:Inside(三角形内部细分因子)
二、无细分的顶点位移(基础示例)
效果:通过置换贴图沿法线位移顶点,未启用曲面细分。
hlsl
Shader "Tessellation/DisplacementOnly" {
Properties {
_DispTex ("置换贴图", 2D) = "gray" {}
_Displacement ("置换强度", Range(0, 1)) = 0.3
}
SubShader {
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf BlinnPhong vertex:disp nolightmap
#pragma target 4.6
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
sampler2D _DispTex;
float _Displacement;
// 顶点位移函数(无细分)
void disp (inout appdata v) {
float d = tex2Dlod(_DispTex, float4(v.uv, 0, 0)).r * _Displacement;
v.vertex.xyz += v.normal * d; // 沿法线位移
}
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = 1; // 纯色显示位移效果
}
ENDCG
}
Fallback "Diffuse"
}
要点:
- 通过
vertex:disp
关联顶点位移函数,直接修改原始顶点位置。 - 未启用细分时,模型三角面数不变,仅顶点位置改变。
三、固定细分级别(全局统一细分)
效果:对整个模型应用固定细分级别,适用于屏幕空间大小一致的模型。
hlsl
Shader "Tessellation/FixedTessellation" {
Properties {
_TessLevel ("细分级别", Range(1, 32)) = 4
_DispTex ("置换贴图", 2D) = "gray" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf BlinnPhong tessellate:tessFixed vertex:disp nolightmap
#pragma target 4.6
float _TessLevel;
// 固定细分因子函数
float4 tessFixed () {
return float4(_TessLevel, _TessLevel, _TessLevel, _TessLevel); // 四边相同细分
}
void disp (inout appdata v) {
float d = tex2Dlod(_DispTex, float4(v.uv, 0, 0)).r * 0.3;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = 1;
}
ENDCG
}
Fallback "Diffuse"
}
要点:
tessFixed()
返回固定值,控制所有三角形的细分级别。- 细分后顶点数 = 原始顶点数 × 细分级别 ²,可能导致性能问题。
四、基于距离的细分(近距高细分,远距低细分)
效果:根据模型到相机的距离动态调整细分级别,优化性能。
hlsl
Shader "Tessellation/DistanceBasedTess" {
Properties {
_MaxTess ("最大细分", Range(1, 32)) = 16
_MinTess ("最小细分", Range(1, 16)) = 4
_TransitionDist ("过渡距离", Range(10, 50)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf BlinnPhong tessellate:tessDistance vertex:disp nolightmap
#pragma target 4.6
#include "Tessellation.cginc" // 引入Unity内置细分工具
float _MaxTess, _MinTess, _TransitionDist;
// 基于距离的细分因子函数
float4 tessDistance (appdata v0, appdata v1, appdata v2) {
// 使用Unity内置函数计算距离相关的细分因子
return UnityDistanceBasedTess(
v0.vertex, v1.vertex, v2.vertex,
_TransitionDist, // 开始降细分的距离
_TransitionDist * 2, // 最低细分的距离
_MaxTess, _MinTess
);
}
void disp (inout appdata v) { /* 位移逻辑同上 */ }
void surf (Input IN, inout SurfaceOutput o) { /* 着色逻辑同上 */ }
ENDCG
}
Fallback "Diffuse"
}
要点:
UnityDistanceBasedTess
根据顶点到相机的距离自动计算细分因子。- 近距离使用高细分(如
_MaxTess=16
),远距离使用低细分(如_MinTess=4
)。
五、基于边缘长度的细分(屏幕空间自适应细分)
效果:根据三角形在屏幕上的边缘长度动态调整细分,避免小三角形过度细分。
hlsl
Shader "Tessellation/EdgeLengthTess" {
Properties {
_EdgeLength ("边缘长度阈值", Range(2, 50)) = 15 // 像素单位
}
SubShader {
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf BlinnPhong tessellate:tessEdge vertex:disp nolightmap
#pragma target 4.6
#include "Tessellation.cginc"
float _EdgeLength;
// 基于边缘长度的细分因子函数
float4 tessEdge (appdata v0, appdata v1, appdata v2) {
return UnityEdgeLengthBasedTess(
v0.vertex, v1.vertex, v2.vertex,
_EdgeLength // 屏幕空间边缘长度阈值(像素)
);
}
void disp (inout appdata v) { /* 位移逻辑同上 */ }
void surf (Input IN, inout SurfaceOutput o) { /* 着色逻辑同上 */ }
ENDCG
}
Fallback "Diffuse"
}
要点:
UnityEdgeLengthBasedTess
计算三角形边缘在屏幕上的像素长度,长边缘使用高细分。- 推荐搭配
UnityEdgeLengthBasedTessCull
函数,剔除相机视野外的细分计算,提升性能。
六、Phong 曲面细分(无置换的平滑效果)
效果:通过调整细分后顶点的位置,模拟法线平滑效果,无需置换贴图。
hlsl
Shader "Tessellation/PhongTessellation" {
Properties {
_TessLevel ("细分级别", Range(1, 32)) = 8
_PhongStrength ("平滑强度", Range(0, 1)) = 0.5
}
SubShader {
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Lambert tessellate:tessFixed tessphong:_PhongStrength
#pragma target 4.6
float _TessLevel;
float4 tessFixed () {
return float4(_TessLevel, _TessLevel, _TessLevel, _TessLevel);
}
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = 1; // 纯色显示平滑效果
}
ENDCG
}
Fallback "Diffuse"
}
要点:
tessphong:_PhongStrength
指令启用 Phong 细分,通过_PhongStrength
控制平滑程度。- 无需顶点位移,仅通过细分后的顶点位置调整实现低多边形模型平滑化。
七、性能优化与限制
-
硬件支持:
- 仅支持 DirectX 11 及以上、OpenGL Core 4.0 及以上显卡。
- 使用
#pragma target 4.6
强制编译为高版本 Shader Model,可能不兼容旧设备。
-
细分因子范围:
- 建议细分级别不超过 32,过高值会导致 GPU 负载激增。
-
混合使用细分与位移:
- 细分增加顶点数,置换贴图增强细节,两者结合可实现高保真曲面(如地形、角色衣物)。
-
减少冗余计算:
- 对不可见的三角形,使用
UnityEdgeLengthBasedTessCull
剔除细分计算。
- 对不可见的三角形,使用
总结:如何选择细分策略
场景 | 推荐细分方法 | 关键参数 |
---|---|---|
全局平滑化 | 固定细分 + Phong 细分 | _TessLevel , _PhongStrength |
远距离优化 | 基于距离的细分 | _MaxTess , _TransitionDist |
屏幕空间自适应细分 | 基于边缘长度的细分 | _EdgeLength |
高细节曲面(如岩石) | 细分 + 置换贴图 | tessellate , vertex:disp |
通过合理组合细分策略与顶点位移,可在性能与画质间取得平衡。注意在移动设备上谨慎使用曲面细分,优先考虑基于屏幕空间的自适应算法以减少过度细分。