辗振 发表于 2025-8-13 10:18:47

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


[*]细分着色器分为:曲面细分着色器(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语义标记)

‌实现关键‌:

hlsl
struct TessFactors {
    float edge : SV_TessFactor; // 三条边的细分因子
    float inside : SV_InsideTessFactor; // 内部细分因子
};‌Tessellation Primitive Generator 固定功能‌:硬件根据 Hull Shader 输出的因子实际执行细分操作。‌


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


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


[*]输入:Hull Shader输出的细分因子和控制点
hlsl

// 来自Hull Shader的输出
struct TessControlPoint {
    float4 positionOS : INTERNALTESSPOS;
    float3 normalOS : NORMAL;
    float2 uv : TEXCOORD0;
};

// 细分因子定义
struct TessFactors {
    float edge : SV_TessFactor; // 每条边的细分等级
    float inside : SV_InsideTessFactor; // 内部细分等级
};
[*]输出:细分后的顶点UV坐标和拓扑关系

[*]生成的重心坐标数据:
hlsl
float3 baryCoords : SV_DomainLocation; // 新顶点的重心坐标
[*]输出拓扑类型由Hull Shader的属性定义(如triangle_cw)

‌特性‌:


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

‌细分算法‌


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

[*]‌Delaunay三角剖分原则‌

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

[*]‌URP中的实现特点‌:

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

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

[*]计算细分因子为3时:
原始三角形: A
            / \
         C---B

细分后拓扑:
      A
   /|\
    / | \
   C--D--B
    \ | /
   \|/
      E

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

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


[*]‌URP中的实际应用‌:

[*]当使用时:

[*]会在过渡区域自动应用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)和其他顶点属性
‌实现示例‌:

hlsl
// 声明处理三角形patch
Varyings domain(TessFactors factors, OutputPatch<DomainAttributes, 3> patch, float3 baryCoords : SV_DomainLocation) {
    Varyings OUT;
    // 插值计算新顶点属性
    OUT.positionWS = TransformObjectToWorld(patch.positionOS * baryCoords.x + ...);
    return OUT;
}‌动态控制技巧‌:


[*]通过摄像机距离调整细分因子:
hlsl
float CalcTessFactor(float3 worldPos) {
    return lerp(_MaxTess, _MinTess, saturate(distance(_WorldSpaceCameraPos, worldPos) / _TessRange));
}
[*]结合高度图实现位移效果
‌配置:


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

声明编译目标为4.6以上:

hlsl
#pragma target 4.6定义三个关键程序:

hlsl
#pragma vertex BeforeTessVert
#pragma hull HullProgram
#pragma domain DomainProgram控制点数据结构需包含顶点位置、法线等基础属性

‌动态细分控制样例:


[*]通过距离或屏幕空间尺寸自动调整细分因子:
hlsl
// 根据摄像机距离动态计算细分因子
float CalcTessFactor(float3 worldPos) {
    float dist = distance(_WorldSpaceCameraPos, worldPos);
    return lerp(_MaxTess, _MinTess, saturate(dist / _TessRange));
}常见应用场景:


[*]地形动态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

// HLSL
Shader "Custom/TessellationExample"
{
    Properties
    {
      _MainTex ("Base Texture", 2D) = "white" {}
      _TessFactor ("Tessellation Factor", Range(1, 64)) = 4
      _Displacement ("Displacement", Range(0, 1.0)) = 0.3
      _DispTex ("Displacement Texture", 2D) = "gray" {}
    }

    SubShader
    {
      Tags { "RenderPipeline"="UniversalPipeline" }

      HLSLINCLUDE
      #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

      struct Attributes
      {
            float4 positionOS : POSITION;
            float3 normalOS : NORMAL;
            float2 uv : TEXCOORD0;
      };

      struct TessControlPoint
      {
            float4 positionOS : INTERNALTESSPOS;
            float3 normalOS : NORMAL;
            float2 uv : TEXCOORD0;
      };

      struct TessFactors
      {
            float edge : SV_TessFactor;
            float inside : SV_InsideTessFactor;
      };

      struct DomainOutput
      {
            float4 positionCS : SV_POSITION;
            float2 uv : TEXCOORD0;
            float3 normalWS : NORMAL;
      };
      ENDHLSL

      Pass
      {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForward" }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma hull hull
            #pragma domain domain
            #pragma fragment frag
            #pragma target 4.6

            sampler2D _MainTex;
            sampler2D _DispTex;
            float _TessFactor;
            float _Displacement;

            TessControlPoint vert(Attributes v)
            {
                TessControlPoint o;
                o.positionOS = v.positionOS;
                o.normalOS = v.normalOS;
                o.uv = v.uv;
                return o;
            }

            
            
            
            
            
            TessControlPoint hull(InputPatch<TessControlPoint, 3> patch, uint id : SV_OutputControlPointID)
            {
                return patch;
            }

            TessFactors patchConstantFunc(InputPatch<TessControlPoint, 3> patch)
            {
                TessFactors f;
                float avgTess = _TessFactor * (1 - saturate(length(_WorldSpaceCameraPos - TransformObjectToWorld(patch.positionOS.xyz)) / 20));
                f.edge = f.edge = f.edge = avgTess;
                f.inside = avgTess;
                return f;
            }

            
            DomainOutput domain(TessFactors factors, OutputPatch<TessControlPoint, 3> patch, float3 baryCoords : SV_DomainLocation)
            {
                DomainOutput o;
               
                // 插值计算基础属性
                float3 positionOS = patch.positionOS.xyz * baryCoords.x +
                                 patch.positionOS.xyz * baryCoords.y +
                                 patch.positionOS.xyz * baryCoords.z;
               
                float2 uv = patch.uv * baryCoords.x +
                            patch.uv * baryCoords.y +
                            patch.uv * baryCoords.z;
               
                // 从高度图获取位移值
                float disp = tex2Dlod(_DispTex, float4(uv, 0, 0)).r * _Displacement;
                positionOS += normalize(patch.normalOS) * disp;
               
                // 转换到裁剪空间
                o.positionCS = TransformObjectToHClip(positionOS);
                o.uv = uv;
                o.normalWS = TransformObjectToWorldNormal(patch.normalOS);
                return o;
            }

            half4 frag(DomainOutput i) : SV_Target
            {
                half4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDHLSL
      }
    }
}【从UnityURP开始探索游戏渲染】专栏-直达

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 【渲染流水线】[几何阶段]-[曲面细分]以UnityURP为例