时间:2025-08-13 12:07
人气:
作者:admin
【从UnityURP开始探索游戏渲染】专栏-直达
patch constant function计算每条边和内部的细分等级SV_TessFactor标记边,SV_InsideTessFactor标记内部)INTERNALTESSPOS语义标记)hlsl
struct TessFactors {
float edge[3] : SV_TessFactor; // 三条边的细分因子
float inside : SV_InsideTessFactor; // 内部细分因子
};
输入:Hull Shader输出的细分因子和控制点
hlsl
// 来自Hull Shader的输出
struct TessControlPoint {
float4 positionOS : INTERNALTESSPOS;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
// 细分因子定义
struct TessFactors {
float edge[3] : SV_TessFactor; // 每条边的细分等级
float inside : SV_InsideTessFactor; // 内部细分等级
};
输出:细分后的顶点UV坐标和拓扑关系
生成的重心坐标数据:
hlsl
float3 baryCoords : SV_DomainLocation; // 新顶点的重心坐标
输出拓扑类型由Hull Shader的[outputtopology]属性定义(如triangle_cw)
对原始三角面片进行递归细分,采用Delaunay三角剖分原则
GPU使用的Delaunay三角剖分原则是一种优化网格拓扑的数学方法
Delaunay三角剖分原则
URP中的实现特点:
具体示例:
原始三角面片(控制点A/B/C)经过细分后:
原始三角形: A
/ \
C---B
细分后拓扑:
A
/|\
/ | \
C--D--B
\ | /
\|/
E
URP中的实际应用:
[partitioning("fractional_odd")]时:
这种剖分方式使得:
根据分数细分模式(fractional_odd/even)处理过渡区域
将细分后位于重心坐标系(重心坐标内容后续单开一篇讲解)中的新顶点位置转换到目标空间(如世界空间或裁剪空间)。
SV_POSITION)和其他顶点属性hlsl
[domain("tri")] // 声明处理三角形patch
Varyings domain(TessFactors factors, OutputPatch<DomainAttributes, 3> patch, float3 baryCoords : SV_DomainLocation) {
Varyings OUT;
// 插值计算新顶点属性
OUT.positionWS = TransformObjectToWorld(patch[0].positionOS * baryCoords.x + ...);
return OUT;
}
通过摄像机距离调整细分因子:
hlsl
float CalcTessFactor(float3 worldPos) {
return lerp(_MaxTess, _MinTess, saturate(distance(_WorldSpaceCameraPos, worldPos) / _TessRange));
}
结合高度图实现位移效果
hull 和 domain 函数)。#pragma target 4.6以启用DX11/OpenGL Core特性3hlsl
#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));
}
// 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[3] : 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;
}
[domain("tri")]
[partitioning("fractional_odd")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("patchConstantFunc")]
TessControlPoint hull(InputPatch<TessControlPoint, 3> patch, uint id : SV_OutputControlPointID)
{
return patch[id];
}
TessFactors patchConstantFunc(InputPatch<TessControlPoint, 3> patch)
{
TessFactors f;
float avgTess = _TessFactor * (1 - saturate(length(_WorldSpaceCameraPos - TransformObjectToWorld(patch[0].positionOS.xyz)) / 20));
f.edge[0] = f.edge[1] = f.edge[2] = avgTess;
f.inside = avgTess;
return f;
}
[domain("tri")]
DomainOutput domain(TessFactors factors, OutputPatch<TessControlPoint, 3> patch, float3 baryCoords : SV_DomainLocation)
{
DomainOutput o;
// 插值计算基础属性
float3 positionOS = patch[0].positionOS.xyz * baryCoords.x +
patch[1].positionOS.xyz * baryCoords.y +
patch[2].positionOS.xyz * baryCoords.z;
float2 uv = patch[0].uv * baryCoords.x +
patch[1].uv * baryCoords.y +
patch[2].uv * baryCoords.z;
// 从高度图获取位移值
float disp = tex2Dlod(_DispTex, float4(uv, 0, 0)).r * _Displacement;
positionOS += normalize(patch[0].normalOS) * disp;
// 转换到裁剪空间
o.positionCS = TransformObjectToHClip(positionOS);
o.uv = uv;
o.normalWS = TransformObjectToWorldNormal(patch[0].normalOS);
return o;
}
half4 frag(DomainOutput i) : SV_Target
{
half4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDHLSL
}
}
}
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,????)