• Unity URP 如何写基础的曲面细分着色器


    左边是默认Cube在网格模式下经过曲面细分的结果,右边是原状态。

    曲面细分着色器在顶点着色器、几何着色器之后,像素着色器之前。

    它的作用时根据配置信息生成额外的顶点以切割原本的面片。

    关于这部分有一个详细的英文教程,感兴趣可以看一下。

    https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/

    以下是完整代码

    1. Shader "Kerzh/KerzhCgShaderTemplate"
    2. {
    3. Properties
    4. {
    5. _Color("Color", Color) = (1,1,1,1)
    6. _TessellationUniform ("Tessellation Uniform", Vector) = (1,1,1,1)
    7. }
    8. SubShader
    9. {
    10. Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" }
    11. Pass
    12. {
    13. CGPROGRAM
    14. #pragma vertex vert
    15. #pragma hull hull
    16. #pragma domain domain
    17. #pragma geometry geom
    18. #pragma fragment frag
    19. #pragma target 4.6
    20. #include "UnityCG.cginc"
    21. #include "Assets/TA/ShaderLib/CgincInclude//CommonCgInc.cginc"
    22. #include "Assets/TA/ShaderLib/CgincInclude//CustomTessellation.cginc"
    23. MeshData vert (MeshData input)
    24. {
    25. return input;
    26. }
    27. //如果不正确配置会报错
    28. [UNITY_domain("tri")] // 正在处理三角形 "tri", "quad", or "isoline"
    29. [UNITY_outputcontrolpoints(3)] // 每个面片输出的顶点为3
    30. [UNITY_outputtopology("triangle_cw")] // 当创建三角形时应是顺时针还是逆时针,这里应是顺时针 "point", "line", "triangle_cw", or "triangle_ccw"
    31. [UNITY_partitioning("integer")] // 如何细分切割面片 "integer", "pow2", "fractional_even", or "fractional_odd"
    32. [UNITY_patchconstantfunc("patchConstantFunction")] // 细分切割部分还必须提供函数处理,每个面片调用一次
    33. MeshData hull (InputPatch<MeshData, 3> patch, uint id : SV_OutputControlPointID) // 每个顶点调用一次,如果是处理三角形就是调用三次
    34. {
    35. // 如果_TessellationUniform输入值为1,不产生任何变化,但当输入值为2时,具体发生了什么
    36. // 对于一个三角形(因为我们配置的是处理三角形),根据三条边的二等分位置添加额外的一个顶点,如果是3就是三等分位置添加两个顶点
    37. // 对于一个三角形(因为我们配置的是处理三角形),根据三条个顶点添加一个中心顶点
    38. // 根据patchConstantFunction赋值分配生成顶点的插值权重
    39. return patch[id];
    40. }
    41. // barycentricCoordinates分别代表各个点的插值权重,每个面片调用一次,对细分后的三角顶点形进行处理(也就是说原顶点不经过此处理?)
    42. [UNITY_domain("tri")] // 正在处理三角形
    43. MeshData domain(TessellationFactors factors, OutputPatch<MeshData, 3> patch, float3 barycentricCoordinates : SV_DomainLocation)
    44. {
    45. MeshData data; // 新的插值权重顶点
    46. #define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) data.fieldName = \
    47. patch[0].fieldName * barycentricCoordinates.x + \
    48. patch[1].fieldName * barycentricCoordinates.y + \
    49. patch[2].fieldName * barycentricCoordinates.z;
    50. //对所有信息利用插值权重进行插值计算
    51. MY_DOMAIN_PROGRAM_INTERPOLATE(vertex)
    52. MY_DOMAIN_PROGRAM_INTERPOLATE(normalOS)
    53. MY_DOMAIN_PROGRAM_INTERPOLATE(tangentOS)
    54. MY_DOMAIN_PROGRAM_INTERPOLATE(uv1)
    55. MY_DOMAIN_PROGRAM_INTERPOLATE(uv2)
    56. MY_DOMAIN_PROGRAM_INTERPOLATE(vertexColor)
    57. return data;
    58. }
    59. //最大的顶点数,这里比如你要生成三个三角形面片,那么一个面片需要三个顶点,就是9个顶点,一般来讲这里直接填多一点即可,不过可能填太多了会导致内存占用?
    60. [maxvertexcount(9)]
    61. void geom (triangle MeshData input[3],inout TriangleStream<VOutData> triStream)
    62. {
    63. //原样转换过去
    64. VOutData output[3];
    65. for(int i=0;i<3;i++)
    66. {
    67. VOutData p0;
    68. p0 = FillBaseV2FData(input[i]);
    69. output[i] = p0;
    70. }
    71. triStream.RestartStrip(); // 重新开始一个新的三角形
    72. triStream.Append(output[0]);
    73. triStream.Append(output[1]);
    74. triStream.Append(output[2]);
    75. return;
    76. //验证准确性用 勿删
    77. MeshData centerMeshData;
    78. centerMeshData.vertex = (input[0].vertex + input[1].vertex + input[2].vertex)/3.0;
    79. centerMeshData.uv1 = (input[0].uv1 + input[1].uv1 + input[2].uv1)/3.0;
    80. centerMeshData.uv2 = (input[0].uv2 + input[1].uv2 + input[2].uv2)/3.0;
    81. centerMeshData.tangentOS = (input[0].tangentOS + input[1].tangentOS + input[2].tangentOS)/3.0;
    82. centerMeshData.normalOS = (input[0].normalOS + input[1].normalOS + input[2].normalOS)/3.0;
    83. centerMeshData.vertexColor = (input[0].vertexColor + input[1].vertexColor + input[2].vertexColor)/3.0;
    84. centerMeshData.vertex += float4((centerMeshData.normalOS * 0.35), 0);
    85. VOutData center = FillBaseV2FData(centerMeshData);
    86. // 根据这三个点分别和中心点制造三角形输出
    87. triStream.RestartStrip(); // 重新开始一个新的三角形
    88. triStream.Append(output[1]);
    89. triStream.Append(center);
    90. triStream.Append(output[0]);
    91. triStream.RestartStrip(); // 重新开始一个新的三角形
    92. triStream.Append(output[2]);
    93. triStream.Append(center);
    94. triStream.Append(output[1]);
    95. triStream.RestartStrip(); // 重新开始一个新的三角形
    96. triStream.Append(output[0]);
    97. triStream.Append(center);
    98. triStream.Append(output[2]);
    99. }
    100. float4 _Color;
    101. float4 frag (VOutData i) : SV_Target
    102. {
    103. return _Color;
    104. }
    105. ENDCG
    106. }
    107. }
    108. }

    依赖文件CommonCgInc.cginc:

    1. #ifndef CommonCgInc
    2. #define CommonCgInc
    3. float3 FromScript_LocalPositionWS;
    4. float3 FromScript_LocalRotationWS;
    5. float3 FromScript_LocalScaleWS;
    6. //输入结构
    7. struct MeshData
    8. {
    9. float4 vertex : POSITION;
    10. float2 uv1 : TEXCOORD0;
    11. float2 uv2 : TEXCOORD1;
    12. float4 tangentOS :TANGENT;
    13. float3 normalOS : NORMAL;
    14. float4 vertexColor : COLOR;
    15. };
    16. //传递结构
    17. struct VOutData
    18. {
    19. float4 pos : SV_POSITION; // 必须命名为pos ,因为 TRANSFER_VERTEX_TO_FRAGMENT 是这么命名的,为了正确地获取到Shadow
    20. float2 uv1 : TEXCOORD0;
    21. float3 tangentWS : TEXCOORD1;
    22. float3 bitangentWS : TEXCOORD2;
    23. float3 normalWS : TEXCOORD3;
    24. float3 posWS : TEXCOORD4;
    25. float3 posOS : TEXCOORD5;
    26. float3 normalOS : TEXCOORD6;
    27. float4 vertexColor : TEXCOORD7;
    28. float2 uv2 : TEXCOORD8;
    29. };
    30. //传递结构赋值
    31. VOutData FillBaseV2FData(MeshData input)
    32. {
    33. VOutData output;
    34. output.pos = UnityObjectToClipPos(input.vertex);
    35. output.uv1 = input.uv1;
    36. output.uv2 = input.uv2;
    37. output.normalWS = normalize(UnityObjectToWorldNormal(input.normalOS));
    38. output.posWS = mul(unity_ObjectToWorld, input.vertex);
    39. output.posOS = input.vertex.xyz;
    40. output.tangentWS = normalize(UnityObjectToWorldDir(input.tangentOS));
    41. output.bitangentWS = cross(output.normalWS, output.tangentWS) * input.tangentOS.w; //乘上input.tangentOS.w 是unity引擎的bug,有的模型是 1 有的模型是 -1,必须这么写
    42. output.normalOS = input.normalOS;
    43. output.vertexColor = input.vertexColor;
    44. return output;
    45. }
    46. // Hue, Saturation, Value
    47. // Ranges:
    48. // Hue [0.0, 1.0]
    49. // Sat [0.0, 1.0]
    50. // Lum [0.0, HALF_MAX]
    51. // //HSV色彩模型转为RGB色彩模型 FROM::color.hlsl
    52. float3 HSV2RGB( float3 hsv ){
    53. const float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    54. float3 p = abs(frac(hsv.xxx + K.xyz) * 6.0 - K.www);
    55. return hsv.z * lerp(K.xxx, saturate(p - K.xxx), hsv.y);
    56. }
    57. //RGB色彩模型转为HSV色彩模型 FROM::color.hlsl
    58. float3 RGB2HSV(float3 rgb)
    59. {
    60. float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    61. float4 p = lerp(float4(rgb.bg, K.wz), float4(rgb.gb, K.xy), step(rgb.b, rgb.g));
    62. float4 q = lerp(float4(p.xyw, rgb.r), float4(rgb.r, p.yzx), step(p.x, rgb.r));
    63. float d = q.x - min(q.w, q.y);
    64. float e = 1.0e-10;
    65. return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
    66. }
    67. //Gooch光照模型 ⻛格化的着⾊模型 强调冷暖色调 FROM::render4th P146
    68. float3 GoochModel(float3 baseColor, float3 highLightColor, float3 normalWs, float3 lightDirWs, float3 viewDirWs){
    69. normalWs = normalize(normalWs);
    70. lightDirWs = normalize(lightDirWs);
    71. float3 coolColor = float3(0,0,0.55) + 0.25*baseColor;
    72. float3 warmColor = float3(0.3,0.3,0) + 0.25*baseColor;
    73. float size = clamp(100*(dot(reflect(lightDirWs, normalWs), viewDirWs) - 97), 0, 1);
    74. float4 halfLambert = dot(normalWs, lightDirWs) * 0.5 + 0.5;
    75. return size*highLightColor + (1-size)*(halfLambert*warmColor + (1 - halfLambert)* coolColor);
    76. }
    77. //添加虚拟点光源 _VirtualLightFade越大,衰减越快
    78. float3 CalculatePointVirtualLight(float3 _VirtualLightPos, float3 positionOS, float _VirtualLightFade, float3 _VirtualLightColor)
    79. {
    80. float virtualLightDis = distance(_VirtualLightPos,positionOS);
    81. float3 virtualLight = exp(-_VirtualLightFade*virtualLightDis)*_VirtualLightColor;
    82. //TODO:也许补充方向衰减
    83. return virtualLight;
    84. }
    85. //切线空间计算视差uv 根据视角方向以深度反追命中点uv
    86. float2 CalculateRealUVAfterDepth(float2 originUV, float3 viewDirTS, float depth)
    87. {
    88. //计算视角方向和深度方向的cos值
    89. float cosTheta = dot(normalize(viewDirTS), float3(0,0,-1)); // 一般来讲unity是左手坐标系,但在切线和观察空间较为特殊是右手坐标系,不过这并不影响z轴方向的判断
    90. //根据深度差算出两点间距离
    91. float dis = depth / cosTheta;
    92. //算出应用深度差后对应点位
    93. float3 originUVPoint = float3(originUV, 0);
    94. float3 afrerDepthUVPoint = originUVPoint + normalize(viewDirTS) * dis;
    95. //返回应用深度差后对应UV
    96. return afrerDepthUVPoint.xy;
    97. }
    98. #endif

    依赖文件CustomTessellation.cginc:

    1. // Tessellation programs based on this article by Catlike Coding:
    2. // https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/
    3. // 关于参数的详细说明
    4. // https://zhuanlan.zhihu.com/p/479792793
    5. #include "Assets/TA/ShaderLib/CgincInclude//CommonCgInc.cginc"
    6. //细分切割函数对于每个面片调用一次,对于每一个面片,比如三角形:每条边需要一个切割因子,内部需要一个切割因子
    7. struct TessellationFactors {
    8. float edge[3] : SV_TessFactor; // 对应三条边的切割因子
    9. float inside : SV_InsideTessFactor; // 对应内部的切割因子
    10. };
    11. float4 _TessellationUniform; // 细分因子,当值为1时不发生细分切割。因子可以分别设置,比如边因子是1内部因子是5,那就不会在边上生成顶点,会在中心生成五个顶点
    12. //自定义的细分切割方法,每个面片调用一次
    13. TessellationFactors patchConstantFunction (InputPatch<MeshData, 3> patch)
    14. {
    15. TessellationFactors f;
    16. f.edge[0] = _TessellationUniform.x;
    17. f.edge[1] = _TessellationUniform.y;
    18. f.edge[2] = _TessellationUniform.z;
    19. f.inside = _TessellationUniform.w;
    20. return f;
    21. }

    其中,通过

    #pragma hull hull
    #pragma domain domain

    定义hull和domain着色器,其中hull负责切割,domain用于根据权重处理细分后的顶点数据。

    hull部分:

    1. //细分切割函数对于每个面片调用一次,对于每一个面片,比如三角形:每条边需要一个切割因子,内部需要一个切割因子
    2. struct TessellationFactors {
    3. float edge[3] : SV_TessFactor; // 对应三条边的切割因子
    4. float inside : SV_InsideTessFactor; // 对应内部的切割因子
    5. };
    6. float4 _TessellationUniform; // 细分因子,当值为1时不发生细分切割。因子可以分别设置,比如边因子是1内部因子是5,那就不会在边上生成顶点,会在中心生成五个顶点
    7. //自定义的细分切割方法,每个面片调用一次
    8. TessellationFactors patchConstantFunction (InputPatch<MeshData, 3> patch)
    9. {
    10. TessellationFactors f;
    11. f.edge[0] = _TessellationUniform.x;
    12. f.edge[1] = _TessellationUniform.y;
    13. f.edge[2] = _TessellationUniform.z;
    14. f.inside = _TessellationUniform.w;
    15. return f;
    16. }
    17. //如果不正确配置会报错
    18. [UNITY_domain("tri")] // 正在处理三角形 "tri", "quad", or "isoline"
    19. [UNITY_outputcontrolpoints(3)] // 每个面片输出的顶点为3
    20. [UNITY_outputtopology("triangle_cw")] // 当创建三角形时应是顺时针还是逆时针,这里应是顺时针 "point", "line", "triangle_cw", or "triangle_ccw"
    21. [UNITY_partitioning("integer")] // 如何细分切割面片 "integer", "pow2", "fractional_even", or "fractional_odd"
    22. [UNITY_patchconstantfunc("patchConstantFunction")] // 细分切割部分还必须提供函数处理,每个面片调用一次
    23. MeshData hull (InputPatch<MeshData, 3> patch, uint id : SV_OutputControlPointID) // 每个顶点调用一次,如果是处理三角形就是调用三次
    24. {
    25. // 如果_TessellationUniform输入值为1,不产生任何变化,但当输入值为2时,具体发生了什么
    26. // 对于一个三角形(因为我们配置的是处理三角形),根据三条边的二等分位置添加额外的一个顶点,如果是3就是三等分位置添加两个顶点
    27. // 对于一个三角形(因为我们配置的是处理三角形),根据三条个顶点添加一个中心顶点
    28. // 根据patchConstantFunction赋值分配生成顶点的插值权重
    29. return patch[id];
    30. }

    这里做的操作比较少,只是传入每个面片的切割因子,一个面片(三角形)的每条边需要一个切割因子,内部需要一个切割因子,一共四个切割因子。

    domain部分:

    1. // barycentricCoordinates分别代表各个点的插值权重,每个面片调用一次,对细分后的三角顶点形进行处理(也就是说原顶点不经过此处理?)
    2. [UNITY_domain("tri")] // 正在处理三角形
    3. MeshData domain(TessellationFactors factors, OutputPatch<MeshData, 3> patch, float3 barycentricCoordinates : SV_DomainLocation)
    4. {
    5. MeshData data; // 新的插值权重顶点
    6. #define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) data.fieldName = \
    7. patch[0].fieldName * barycentricCoordinates.x + \
    8. patch[1].fieldName * barycentricCoordinates.y + \
    9. patch[2].fieldName * barycentricCoordinates.z;
    10. //对所有信息利用插值权重进行插值计算
    11. MY_DOMAIN_PROGRAM_INTERPOLATE(vertex)
    12. MY_DOMAIN_PROGRAM_INTERPOLATE(normalOS)
    13. MY_DOMAIN_PROGRAM_INTERPOLATE(tangentOS)
    14. MY_DOMAIN_PROGRAM_INTERPOLATE(uv1)
    15. MY_DOMAIN_PROGRAM_INTERPOLATE(uv2)
    16. MY_DOMAIN_PROGRAM_INTERPOLATE(vertexColor)
    17. return data;
    18. }

    这里则是根据传入的边和内部的切割因子的权重,对于新增加的顶点,使用这个方法中的规则填充顶点的对应信息,以完成切割。

    MY_DOMAIN_PROGRAM_INTERPOLATE

    是定义了一个宏用于重复处理所有的属性,还是要根据实际情况对应处理方法。

  • 相关阅读:
    Python版A股选股软件源代码,选股系统源代码,实现多种选股策略
    一文带你快速入门【哈希表】
    Flutter 从源码扒一扒Stream机制
    OS·03包管理器
    【强化学习】day1 强化学习基础、马尔可夫决策过程、表格型方法
    金仓数据库 KingbaseGIS 使用手册(8.12. 栅格运算符、8.13. 栅格和栅格波段空间关系函数)
    ES6的代理模式-Proxy
    Vue3 - watch 侦听器(超详细使用教程)
    C语言内功修炼【整型在内存中的存储】
    从永远到永远-吉他和弦替代原理
  • 原文地址:https://blog.csdn.net/qq_55895529/article/details/136667203