【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
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開始探索游戲渲染】專欄-直達
(歡迎點贊留言探討,更多人加入進來能更加完善這個探索的過程,??)

陡峭視差貼圖(Steep Parallax Mapping)實現原理 陡峭視差貼圖通過?分層深度比較?和?動態UV偏移?技術增強巖石表面立體感. ?視角自適應分層采樣? 根據視線與表面法線的夾角動態分
浙公網安備 33010602011771號