【URP】Unity[视差贴图]模拟[风格化地形]实践

【从UnityURP开始探索游戏渲染】专栏-直达

陡峭视差贴图(Steep Parallax Mapping)实现原理

陡峭视差贴图通过‌分层深度比较 ‌和‌动态UV偏移‌技术增强岩石表面立体感.

视角自适应分层采样

  • 根据视线与表面法线的夹角动态分配采样层数(平视视角增加至12层,俯视视角减少至5层),解决标准视差贴图在平视角度下的失真问题

深度图梯度修正

  • 引入_LayerBias参数(推荐值0.2-0.4)调整UV偏移量计算公式,避免陡峭区域出现采样断裂:

\\Delta UV=\\frac{ParallaxScale \\cdot ViewDir_{xy}}{(ViewDir_z+LayerBias) \\cdot LayerCount}

风格化深度增强

  • 在最终插值阶段使用pow(weight,2)强化轮廓对比度,配合ramp贴图实现卡通化光影过渡效果

URP HLSL完整实现代码

关键特性说明

  • 动态层数优化 ‌:通过lerp(_MaxLayers, _MinLayers, saturate(dot(float3(0,0,1), viewDirTS)))实现平视视角自动增加采样精度

  • 抗失真处理 ‌:_LayerBias参数修正陡峭表面的UV偏移计算,避免采样断裂

  • 风格化增强‌:ramp贴图控制光影过渡,边缘光强化轮廓立体感

  • StylizedRockParallax.shader

    c 复制代码
    Shader "Universal Render Pipeline/StylizedRockParallax"
    {
        Properties
        {
            [Header(Base Textures)]
            _MainTex("Albedo (RGB)", 2D) = "white" {}
            _NormalMap("Normal Map", 2D) = "bump" {}
            _HeightMap("Height Map", 2D) = "white" {}
            _RampTex("Stylized Ramp", 2D) = "white" {}
    
            [Header(Parallax Settings)]
            _ParallaxScale("Depth Scale", Range(0, 0.15)) = 0.08
            _LayerBias("Layer Bias", Range(0.1, 0.5)) = 0.3
            _MinLayers("Min Layers", Int) = 5
            _MaxLayers("Max Layers", Int) = 12
    
            [Header(Stylized Lighting)]
            _RimPower("Rim Power", Range(1, 10)) = 3
            _ShadowTint("Shadow Tint", Color) = (0.3,0.3,0.4,1)
        }
    
        SubShader
        {
            Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
    
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    
            TEXTURE2D(_MainTex);    SAMPLER(sampler_MainTex);
            TEXTURE2D(_NormalMap);  SAMPLER(sampler_NormalMap);
            TEXTURE2D(_HeightMap);  SAMPLER(sampler_HeightMap);
            TEXTURE2D(_RampTex);    SAMPLER(sampler_RampTex);
    
            float _ParallaxScale;
            float _LayerBias;
            int _MinLayers, _MaxLayers;
            float _RimPower;
            float4 _ShadowTint;
    
            // 陡峭视差映射核心算法
            float2 SteepParallaxMapping(float3 viewDirTS, float2 uv)
            {
                // 动态层数计算(平视视角增加层数)
                int numLayers = (int)lerp(_MaxLayers, _MinLayers, saturate(dot(float3(0,0,1), viewDirTS)));
                float layerHeight = 1.0 / numLayers;
                float2 deltaUV = _ParallaxScale * viewDirTS.xy / (viewDirTS.z + _LayerBias) / numLayers;
    
                // 光线步进初始化
                float currentLayerHeight = 0;
                float2 currentUV = uv;
                float currentDepth = 1 - SAMPLE_TEXTURE2D(_HeightMap, sampler_HeightMap, currentUV).r;
    
                // 分层深度检测
                [loop]
                for (int i = 0; i < _MaxLayers; ++i) {
                    if (currentLayerHeight >= currentDepth) break;
                    currentUV -= deltaUV;
                    currentDepth = 1 - SAMPLE_TEXTURE2D(_HeightMap, sampler_HeightMap, currentUV).r;
                    currentLayerHeight += layerHeight;
                }
    
                // 风格化插值修正
                float2 prevUV = currentUV + deltaUV;
                float prevDepth = currentDepth - layerHeight;
                float weight = pow((currentLayerHeight - currentDepth) / (prevDepth - currentDepth + 0.001), 2);
                return lerp(currentUV, prevUV, saturate(weight * 1.5));
            }
    
            // 风格化光照计算
            half3 StylizedShading(float3 normalWS, float3 viewDirWS, float NdotL)
            {
                float rim = pow(1 - saturate(dot(normalWS, viewDirWS)), _RimPower);
                float2 rampUV = float2(NdotL * 0.5 + 0.5, 0.5);
                half3 rampColor = SAMPLE_TEXTURE2D(_RampTex, sampler_RampTex, rampUV).rgb;
                return lerp(rampColor * _ShadowTint.rgb, rampColor, saturate(NdotL + rim));
            }
            ENDHLSL
    
            Pass
            {
                HLSLPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                struct Attributes
                {
                    float4 positionOS : POSITION;
                    float2 uv : TEXCOORD0;
                    float3 normalOS : NORMAL;
                    float4 tangentOS : TANGENT;
                };
    
                struct Varyings
                {
                    float4 positionCS : SV_POSITION;
                    float2 uv : TEXCOORD0;
                    float3 viewDirTS : TEXCOORD1;
                    float3 normalWS : TEXCOORD2;
                    float3 viewDirWS : TEXCOORD3;
                    float4 shadowCoord : TEXCOORD4;
                };
    
                Varyings vert(Attributes IN)
                {
                    Varyings OUT;
                    VertexPositionInputs posInput = GetVertexPositionInputs(IN.positionOS.xyz);
                    OUT.positionCS = posInput.positionCS;
    
                    VertexNormalInputs normInput = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);
                    float3 viewDirWS = GetWorldSpaceViewDir(posInput.positionWS);
                    OUT.viewDirTS = TransformWorldToTangent(viewDirWS, 
                        normInput.tangentWS, normInput.bitangentWS, normInput.normalWS);
                    OUT.normalWS = normInput.normalWS;
                    OUT.viewDirWS = viewDirWS;
                    OUT.shadowCoord = GetShadowCoord(posInput);
                    OUT.uv = IN.uv;
                    return OUT;
                }
    
                half4 frag(Varyings IN) : SV_Target
                {
                    // 计算陡峭视差UV
                    float2 parallaxUV = SteepParallaxMapping(normalize(IN.viewDirTS), IN.uv);
    
                    // 采样纹理
                    half4 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, parallaxUV);
                    half3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, parallaxUV));
    
                    // 转换法线到世界空间
                    float3x3 TBN = float3x3(
                        normalize(cross(IN.normalWS, IN.viewDirWS)),
                        normalize(IN.normalWS),
                        normalize(IN.viewDirWS)
                    );
                    float3 normalWS = mul(TBN, normalTS);
    
                    // 光照计算
                    Light mainLight = GetMainLight(IN.shadowCoord);
                    float NdotL = saturate(dot(normalWS, mainLight.direction));
                    half3 lighting = StylizedShading(normalWS, normalize(IN.viewDirWS), NdotL);
    
                    return half4(albedo.rgb * lighting * mainLight.color, 1);
                }
                ENDHLSL
            }
        }
    }

材质配置

参数组合 风格化效果
_ParallaxScale=0.05 + _RimPower=5 轻度凹凸+柔和边缘光
_ParallaxScale=0.1 + _LayerBias=0.4 强烈凹凸+抗失真处理
_ShadowTint=(0.4,0.2,0.6) 紫色调阴影增强风格化表现

【从UnityURP开始探索游戏渲染】专栏-直达

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)