用裁剪空间坐标除以 w 后重映射到 [0,1],将屏幕纹理"投影"到任意几何体上------ 无需 UV 展开,轻松实现扫描线、水波纹与受击扭曲。
1原理:从裁剪空间到屏幕坐标
在 GPU 管线中,顶点着色器最终输出的是裁剪空间坐标 clipPos (x, y, z, w)。 GPU 在光栅化前会自动执行透视除法 :将 xyz 各分量除以 w, 得到范围在 [-1, 1] 的 NDC(标准化设备坐标)。 屏幕空间 UV 就是在这一步之后,把 NDC 的 xy 重映射到 [0, 1] 的结果。

核心公式只有两行:
cs
// 顶点着色器中o.screenPos = ComputeScreenPos(clipPos);
// 片元着色器中float2 screenUV = i.screenPos.xy / i.screenPos.w;
ComputeScreenPos 是 URP 提供的工具函数, 它在顶点阶段把 clipPos 转换为 透视正确 的屏幕坐标(尚未做透视除法), 片元阶段再用 screenPos.xy / screenPos.w 完成透视除法, 得到真正的 [0,1] UV。
为什么要分两步? 在顶点阶段就除以 w 会破坏插值的透视正确性。 把 screenPos.w(即原始 clipPos.w)随 varying 传到片元阶段, 由 GPU 做透视校正插值后,再在片元中相除,结果才是正确的。

2URP 中获取屏幕空间 UV
URP 提供了一张 _CameraOpaqueTexture(需在 URP Asset 中开启 Opaque Texture ),以及深度纹理 _CameraDepthTexture。 下面是完整的 URP Shader 模板,展示如何在不透明物体上读取屏幕颜色。
cs
Shader "Custom/URP_ScreenSpaceUV_Base"{ Properties { [HideInInspector] _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" "RenderPipeline"="UniversalPipeline" }
Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// URP 提供的屏幕颜色纹理(需在 URP Asset 中开启 Opaque Texture) TEXTURE2D(_CameraOpaqueTexture); SAMPLER(sampler_CameraOpaqueTexture);
struct Attributes { float4 positionOS : POSITION; };
struct Varyings { float4 positionCS : SV_POSITION; float4 screenPos : TEXCOORD0; };
Varyings vert(Attributes IN) { Varyings OUT; OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); // 关键:在顶点阶段存储带 w 的屏幕坐标 OUT.screenPos = ComputeScreenPos(OUT.positionCS); return OUT; }
half4 frag(Varyings IN) : SV_Target { // 透视除法:得到真正的 [0,1] 屏幕 UV float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
// 直接读取屏幕颜色 half4 sceneColor = SAMPLE_TEXTURE2D(_CameraOpaqueTexture, sampler_CameraOpaqueTexture, screenUV); return sceneColor; } ENDHLSL } }}
URP Asset 配置检查: Project Settings → Graphics → URP Asset,确保 Opaque Texture 已勾选,否则 _CameraOpaqueTexture 采样结果为黑色。
Shader Graph 等价节点
如果你使用 Shader Graph,只需在片元中添加 Screen Position 节点(模式选 Default ), 其输出的 xyzw 等价于 HLSL 中 ComputeScreenPos(clipPos) 的结果。 再接一个 Scene Color 节点即可采样 Opaque 纹理。

3实战一 --- 扫描线(Scan Line)
扫描线效果让物体表面叠加一条沿 Y 轴滚动的亮带,常用于科幻 HUD、 能量盾、传送特效。核心思路:对屏幕 UV 的 y 分量做正弦或取模运算, 生成周期性条纹,再乘以遮罩颜色叠加到 BaseColor 上。

参数说明: _ScanDensity 控制条纹数量(越大越密); _ScanSpeed 控制亮带滚动速度; _ScanBandWidth 控制亮带宽度(0.02~0.15 为宜)。
无需 UV 展开的优势
由于 screenUV 完全由屏幕坐标决定, 哪怕是一个没有任何展开 UV 的胶囊体或程序化网格,扫描线也能正确覆盖整个表面, 且条纹始终平行于屏幕水平线,不会随物体旋转而倾斜。
4实战二 --- 全屏水波纹(Screen Ripple)
全屏水波纹常用于水面、镜面折射或技能特效。做法是用正弦波对 screenUV 做偏移采样 : 把原始 UV 加上一个随时间变化的正弦偏移量,再去采样 _CameraOpaqueTexture, 就得到扭曲的背景投影。

双轴叠加: 将 x 和 y 方向分别用不同频率和相位的正弦波偏移, 可以得到更自然的"随机"水波效果,而非规整条纹。
与透明渲染队列配合
水波纹 Shader 需要读取 Opaque 纹理,因此 Render Queue 必须设为 Transparent (3000) 或更靠后, 确保场景中不透明物体已经渲染完成并写入了 _CameraOpaqueTexture。
5实战三 --- 角色受击屏幕扭曲(Hit Distortion)
受击扭曲是一种全屏后处理风格效果:当角色受到攻击时, 屏幕画面短暂发生径向扭曲(以角色为中心向外扩散波纹)。 做法是在一个铺满屏幕的 Quad 上,根据距"冲击中心"的距离, 对 screenUV 施加径向偏移。

6常见问题与注意事项
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
| 采样结果全黑 | URP Asset 未开启 Opaque Texture | Project Settings → URP Asset → 勾选 Opaque Texture |
| Y 轴上下颠倒 | 不同平台 UV 原点不同(DX vs GL) | 使用 _ProjectionParams.x:若 < 0 则翻转 y 轴 |
| 扭曲效果在边缘拉伸 | 径向偏移未做边缘衰减 | 乘以 1 - saturate(dist / _Radius) 衰减因子 |
| 在移动端帧率下降 | 全屏采样带宽开销高 | 降低效果分辨率,或限制触发频率(节流 throttle) |
| VR 双眼错位 | 屏幕空间 UV 在单目渲染下各自独立 | 使用 unity_StereoEyeIndex 区分左右眼做偏移修正 |