找回密码
 立即注册
首页 业界区 科技 【渲染流水线】[几何阶段]-[曲面细分]以UnityURP为例 ...

【渲染流水线】[几何阶段]-[曲面细分]以UnityURP为例

辗振 2025-8-13 10:18:47

  • 细分着色器分为:曲面细分着色器(Unity在指定平台硬件支持)、细分计算着色器。使用片面来描述一个物体形状,并增加顶点和片面数量,使模型外观开起来更平滑。
  • 作用‌:动态细分三角面片,提升模型细节(如草地、地形渲染),优化大场景性能 ‌。
  • 这是一个‌可选‌阶段。它在顶点着色器之后运行,负责根据设定的细分因子 (Tessellation Factor) 将输入的图元(通常是三角形面片)动态细分为更小的三角面片,生成更多顶点。‌
【从UnityURP开始探索游戏渲染】专栏-直达
三个子阶段:

Hull Shader:定义每条边和内部分割因子(Tessellation Factor)。‌


  • 接收原始控制点数据并定义细分因子(Tessellation Factor)
  • 通过patch constant function计算每条边和内部的细分等级
  • 支持分数细分模式(fractional_odd/fractional_even)实现平滑过渡
目标‌:


  • 定义原始控制点数据并计算细分因子(Tessellation Factor)
  • 确定每个patch的边和内部细分等级
输入输出‌:


  • 输入:原始控制点数据(位置、法线、UV等)
  • 输出:

    • 细分因子(SV_TessFactor标记边,SV_InsideTessFactor标记内部)
    • 处理后的控制点数据(通过INTERNALTESSPOS语义标记)

实现关键‌:
  1. hlsl
  2. struct TessFactors {
  3.     float edge[3] : SV_TessFactor; // 三条边的细分因子
  4.     float inside : SV_InsideTessFactor; // 内部细分因子
  5. };
复制代码
Tessellation Primitive Generator 固定功能‌:硬件根据 Hull Shader 输出的因子实际执行细分操作。‌


  • GPU固定功能阶段,根据Hull Shader输出的细分因子生成新顶点拓扑
目标‌:


  • 根据Hull Shader输出的细分因子生成新顶点拓扑
  • 将原始patch细分为更密集的三角网格
输入输出‌:


  • 输入:Hull Shader输出的细分因子和控制点
    1. hlsl
    2. // 来自Hull Shader的输出
    3. struct TessControlPoint {
    4.     float4 positionOS : INTERNALTESSPOS;
    5.     float3 normalOS : NORMAL;
    6.     float2 uv : TEXCOORD0;
    7. };
    8. // 细分因子定义
    9. struct TessFactors {
    10.     float edge[3] : SV_TessFactor; // 每条边的细分等级
    11.     float inside : SV_InsideTessFactor; // 内部细分等级
    12. };
    复制代码
  • 输出:细分后的顶点UV坐标和拓扑关系

    • 生成的重心坐标数据:
      1. hlsl
      2. float3 baryCoords : SV_DomainLocation; // 新顶点的重心坐标
      复制代码
    • 输出拓扑类型由Hull Shader的[outputtopology]属性定义(如triangle_cw)

特性‌:


  • GPU自动执行,无需开发者编码
  • 支持分数细分模式实现平滑过渡
实现原理

细分算法


  • 对原始三角面片进行递归细分,采用Delaunay三角剖分原则
    GPU使用的Delaunay三角剖分原则是一种优化网格拓扑的数学方法

    • Delaunay三角剖分原则

      • 空圆特性‌:任意三角形的外接圆内不包含其他顶点
      • 最大化最小角‌:避免出现尖锐三角形,提高网格质量
      • 局部优化‌:通过边翻转(edge flip)逐步优化三角网格

    • URP中的实现特点‌:

      • 在Tessellation Primitive Generator阶段自动应用
      • 对细分后的新顶点进行拓扑优化
      • 保持与原网格的平滑过渡

    • 具体示例‌:
      原始三角面片(控制点A/B/C)经过细分后:

      • 计算细分因子为3时:
      1. 原始三角形: A
      2.             / \
      3.            C---B
      4. 细分后拓扑:
      5.       A
      6.      /|\
      7.     / | \
      8.    C--D--B
      9.     \ | /
      10.      \|/
      11.       E
      复制代码

      • 其中D/E是新生成的顶点,所有三角形都满足:

        • ∠ADB + ∠AEB ≈ 180°
        • 外接圆不包含其他顶点


    • URP中的实际应用‌:

      • 当使用[partitioning("fractional_odd")]时:

        • 会在过渡区域自动应用Delaunay优化
        • 确保不同细分等级间的平滑连接

      • 位移贴图处理时:

        • 新顶点根据Delaunay规则分布
        • 位移后的法线计算更准确


    • 这种剖分方式使得:

      • 细分后的网格更适应曲面变形
      • 避免渲染时的褶皱现象
      • 提升位移贴图的视觉效果


  • 根据分数细分模式(fractional_odd/even)处理过渡区域
‌坐标转换 $Pnew=P0⋅u+P1⋅v+P2⋅w$


  • 将参数空间坐标(u,v,w)转换为新的顶点位置
性能优化


  • 采用并行计算处理多个patch
  • 自动剔除屏幕空间不可见的细分结果
Domain Shader‌:

将细分后位于重心坐标系(重心坐标内容后续单开一篇讲解)中的新顶点位置转换到目标空间(如世界空间或裁剪空间)。‌

  • 将细分后的UV坐标映射到3D空间
  • 执行顶点位移等后期处理
目标‌:


  • 将细分后的UV坐标映射到3D空间
  • 执行顶点位移等后期处理
输入输出‌:


  • 输入:细分后的UV坐标和原始控制点数据
  • 输出:最终顶点位置(SV_POSITION)和其他顶点属性
实现示例‌:
  1. hlsl
  2. [domain("tri")] // 声明处理三角形patch
  3. Varyings domain(TessFactors factors, OutputPatch<DomainAttributes, 3> patch, float3 baryCoords : SV_DomainLocation) {
  4.     Varyings OUT;
  5.     // 插值计算新顶点属性
  6.     OUT.positionWS = TransformObjectToWorld(patch[0].positionOS * baryCoords.x + ...);
  7.     return OUT;
  8. }
复制代码
动态控制技巧‌:


  • 通过摄像机距离调整细分因子:
    1. hlsl
    2. float CalcTessFactor(float3 worldPos) {
    3.     return lerp(_MaxTess, _MinTess, saturate(distance(_WorldSpaceCameraPos, worldPos) / _TessRange));
    4. }
    复制代码
  • 结合高度图实现位移效果
‌配置:


  • 需显式启用(HLSL 中声明 hull 和 domain 函数)。
  • 必须声明#pragma target 4.6以启用DX11/OpenGL Core特性3
  • 需手动实现Hull/Domain Shader,URP不提供表面着色器的简化写法
  • 建议结合摄像机距离动态控制细分因子以优化性能
URP中的具体实现步骤:

声明编译目标为4.6以上:
  1. hlsl
  2. #pragma target 4.6
复制代码
定义三个关键程序:
  1. hlsl
  2. #pragma vertex BeforeTessVert
  3. #pragma hull HullProgram
  4. #pragma domain DomainProgram
复制代码
控制点数据结构需包含顶点位置、法线等基础属性

‌动态细分控制样例:


  • 通过距离或屏幕空间尺寸自动调整细分因子:
  1. hlsl
  2. // 根据摄像机距离动态计算细分因子
  3. float CalcTessFactor(float3 worldPos) {
  4.     float dist = distance(_WorldSpaceCameraPos, worldPos);
  5.     return lerp(_MaxTess, _MinTess, saturate(dist / _TessRange));
  6. }
复制代码
常见应用场景:


  • 地形动态LOD:根据视角距离细分地面网格3
  • 曲面平滑:将低模转换为高模曲面2
  • 动态位移:结合高度图实现实时凹凸效果6
‌注意事项:


  • 仅支持DX11/OpenGL Core等现代图形API6
  • 细分过度会导致性能下降,需合理设置上限5
  • URP中需手动实现Hull/Domain Shader,不同于内置管线的表面着色器简化写法
Unity URP中完整的曲面细分着色器示例,包含顶点位移效果和动态细分控制

示例实现功能:


  • 动态细分控制:根据摄像机距离自动调整细分因子
  • 顶点位移效果:通过高度图(_DispTex)驱动顶点偏移
  • 分数细分模式:使用fractional_odd实现平滑过渡
  • 完整渲染管线:包含顶点/细分/片元全阶段处理
  • URP兼容性:使用URP的ShaderLibrary核心函数
使用说明:


  • 创建材质球并应用此着色器
  • 为_DispTex指定高度图纹理
  • 调整_TessFactor控制细分密度
  • 通过_Displacement参数控制位移强度
TessellationExample.shader
  1. // HLSL
  2. Shader "Custom/TessellationExample"
  3. {
  4.     Properties
  5.     {
  6.         _MainTex ("Base Texture", 2D) = "white" {}
  7.         _TessFactor ("Tessellation Factor", Range(1, 64)) = 4
  8.         _Displacement ("Displacement", Range(0, 1.0)) = 0.3
  9.         _DispTex ("Displacement Texture", 2D) = "gray" {}
  10.     }
  11.     SubShader
  12.     {
  13.         Tags { "RenderPipeline"="UniversalPipeline" }
  14.         HLSLINCLUDE
  15.         #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
  16.         struct Attributes
  17.         {
  18.             float4 positionOS : POSITION;
  19.             float3 normalOS : NORMAL;
  20.             float2 uv : TEXCOORD0;
  21.         };
  22.         struct TessControlPoint
  23.         {
  24.             float4 positionOS : INTERNALTESSPOS;
  25.             float3 normalOS : NORMAL;
  26.             float2 uv : TEXCOORD0;
  27.         };
  28.         struct TessFactors
  29.         {
  30.             float edge[3] : SV_TessFactor;
  31.             float inside : SV_InsideTessFactor;
  32.         };
  33.         struct DomainOutput
  34.         {
  35.             float4 positionCS : SV_POSITION;
  36.             float2 uv : TEXCOORD0;
  37.             float3 normalWS : NORMAL;
  38.         };
  39.         ENDHLSL
  40.         Pass
  41.         {
  42.             Name "ForwardLit"
  43.             Tags { "LightMode"="UniversalForward" }
  44.             HLSLPROGRAM
  45.             #pragma vertex vert
  46.             #pragma hull hull
  47.             #pragma domain domain
  48.             #pragma fragment frag
  49.             #pragma target 4.6
  50.             sampler2D _MainTex;
  51.             sampler2D _DispTex;
  52.             float _TessFactor;
  53.             float _Displacement;
  54.             TessControlPoint vert(Attributes v)
  55.             {
  56.                 TessControlPoint o;
  57.                 o.positionOS = v.positionOS;
  58.                 o.normalOS = v.normalOS;
  59.                 o.uv = v.uv;
  60.                 return o;
  61.             }
  62.             [domain("tri")]
  63.             [partitioning("fractional_odd")]
  64.             [outputtopology("triangle_cw")]
  65.             [outputcontrolpoints(3)]
  66.             [patchconstantfunc("patchConstantFunc")]
  67.             TessControlPoint hull(InputPatch<TessControlPoint, 3> patch, uint id : SV_OutputControlPointID)
  68.             {
  69.                 return patch[id];
  70.             }
  71.             TessFactors patchConstantFunc(InputPatch<TessControlPoint, 3> patch)
  72.             {
  73.                 TessFactors f;
  74.                 float avgTess = _TessFactor * (1 - saturate(length(_WorldSpaceCameraPos - TransformObjectToWorld(patch[0].positionOS.xyz)) / 20));
  75.                 f.edge[0] = f.edge[1] = f.edge[2] = avgTess;
  76.                 f.inside = avgTess;
  77.                 return f;
  78.             }
  79.             [domain("tri")]
  80.             DomainOutput domain(TessFactors factors, OutputPatch<TessControlPoint, 3> patch, float3 baryCoords : SV_DomainLocation)
  81.             {
  82.                 DomainOutput o;
  83.                
  84.                 // 插值计算基础属性
  85.                 float3 positionOS = patch[0].positionOS.xyz * baryCoords.x +
  86.                                    patch[1].positionOS.xyz * baryCoords.y +
  87.                                    patch[2].positionOS.xyz * baryCoords.z;
  88.                
  89.                 float2 uv = patch[0].uv * baryCoords.x +
  90.                             patch[1].uv * baryCoords.y +
  91.                             patch[2].uv * baryCoords.z;
  92.                
  93.                 // 从高度图获取位移值
  94.                 float disp = tex2Dlod(_DispTex, float4(uv, 0, 0)).r * _Displacement;
  95.                 positionOS += normalize(patch[0].normalOS) * disp;
  96.                
  97.                 // 转换到裁剪空间
  98.                 o.positionCS = TransformObjectToHClip(positionOS);
  99.                 o.uv = uv;
  100.                 o.normalWS = TransformObjectToWorldNormal(patch[0].normalOS);
  101.                 return o;
  102.             }
  103.             half4 frag(DomainOutput i) : SV_Target
  104.             {
  105.                 half4 col = tex2D(_MainTex, i.uv);
  106.                 return col;
  107.             }
  108.             ENDHLSL
  109.         }
  110.     }
  111. }
复制代码
【从UnityURP开始探索游戏渲染】专栏-直达

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册