URP 渲染实战 / 第 2 章 URP 架构与一帧的流程 / 案例 2.2
案例 2.2Depth Texture 与 Opaque Texture 的开关入门
目标:写一个极简水面,同时用到深度纹理(深浅颜色、岸边白沫)和不透明纹理(折射),然后分别关掉 URP Asset 里的两个开关,看看会发生什么。
原理:水面自己的观察深度就是 positionNDC.w(第 1 章:clip.w 即观察深度);从深度纹理里读出同一像素“水底”的观察深度,两者相减就是视线穿过的水的厚度。厚度越大越接近深水色,厚度接近 0 的地方就是水面与物体相交处,叠一圈白沫。折射则是用扰动过的屏幕 UV 采样 Opaque Texture。完整的水在第 19 章。
Assets/Cookbook/Ch02/SceneTextures.shader(节选:frag)
half4 frag(Varyings input) : SV_Target { float2 screenUV = input.positionNDC.xy / input.positionNDC.w; // 1) 深度纹理:水底(不透明物体)离相机多远 float sceneEye = LinearEyeDepth(SampleSceneDepth(screenUV), _ZBufferParams); float surfaceEye = input.positionNDC.w; // 透视投影下 clip.w 就是水面自己的观察深度 float thickness = max(sceneEye - surfaceEye, 0); // 视线穿过的水的厚度(米) // 2) 不透明纹理:水面背后的画面,加一点随世界坐标变化的扰动 float2 p = input.positionWS.xz * _WaveScale; float2 wave = float2(sin(p.x + p.y * 0.7), cos(p.y * 1.3 - p.x * 0.4)); float2 refrUV = screenUV + wave * _Distortion * saturate(thickness); half3 behind = SampleSceneColor(refrUV); // 越深越接近深水色;离岸(厚度接近 0)的地方叠一圈白沫 half t = saturate(thickness / _DepthFade); half3 col = lerp(behind * _ShallowColor.rgb, _DeepColor.rgb, t); half foam = 1.0 - saturate(thickness / _FoamWidth); col = lerp(col, half3(0.95, 0.98, 1.0), foam); return half4(col, 1); }
#ifdef FRAGMENT void main() { vec2 screenUV = vPositionNDC.xy / vPositionNDC.w; // 1) 深度纹理:水底(不透明物体)离相机多远 float sceneEye = LinearEyeDepth(SampleSceneDepth(screenUV), _ZBufferParams); float surfaceEye = vPositionNDC.w; // 透视投影下 clip.w 就是水面自己的观察深度 float thickness = max(sceneEye - surfaceEye, 0.0); // 视线穿过的水的厚度(米) // 2) 不透明纹理:水面背后的画面,加一点随世界坐标变化的扰动 vec2 p = vPositionWS.xz * _WaveScale; vec2 wave = vec2(sin(p.x + p.y * 0.7), cos(p.y * 1.3 - p.x * 0.4)); vec2 refrUV = screenUV + wave * _Distortion * saturate(thickness); vec3 behind = SampleSceneColor(refrUV); // 越深越接近深水色;离岸(厚度接近 0)的地方叠一圈白沫 float t = saturate(thickness / _DepthFade); vec3 col = mix(behind * _ShallowColor.rgb, _DeepColor.rgb, t); float foam = 1.0 - saturate(thickness / _FoamWidth); col = mix(col, vec3(0.95, 0.98, 1.0), foam); fragColor = vec4(col, 1); } #endif

中图最能说明问题:深度纹理关闭后,URP 把它设成了“远平面”,Shader 算出的厚度永远很大,于是浅滩、白沫、水下方块的轮廓统统消失。右图里,深度效果都还在,但透过水面看到的不再是水底,而是一片灰色——关掉 Opaque Texture 后这一帧不会写入 _CameraOpaqueTexture,Shader 读到什么取决于之前谁设置过这个全局纹理,可能是上一帧、上一台相机留下的旧图,也可能是引擎的默认纹理。为了让配图结果确定,拍摄脚本显式放了一张灰色占位图。实际项目里这种“读到旧图”的 bug 更隐蔽:画面看起来似乎对,换个机位或分辨率就出错。
提示如果只是某台相机需要深度/不透明纹理(比如只有主相机看得到水),不必在 URP Asset 里全局打开:每台相机的 Rendering 面板里有 Depth Texture / Opaque Texture 的覆盖选项(Use Pipeline Settings / On / Off)。Renderer Feature 则应在 Pass 里调用
ConfigureInput(ScriptableRenderPassInput.Depth) 等方式声明需求,URP 会按需生成。本案例出自《URP 渲染实战》第 2 章「URP 架构与一帧的流程」。在章节中阅读上下文 →