溶解、扭曲与屏幕空间效果:噪声、UV 扭曲、GrabPass

Unity 知识 · Shader 区域 · 常见效果 · 第 4 篇 更新于 2026-10-15

溶解消失、热浪扭曲、全息闪烁——这一族效果的共同点是「用一张噪声图改 UV 或改 alpha」,而它们的共同陷阱是「什么时候需要读屏幕」。这篇按配方拆三个效果,重点讲清屏幕空间数据的来源与代价。

配方一:溶解(dissolve)

原理:噪声图当「烧蚀进度场」:阈值以下的区域丢弃,阈值附近叠一条发光边。

// 片元核心
float noise = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, i.uv).r;

// 1) 烧蚀判定:noise < progress 的部分消失
clip(noise - _Progress);            // 内部是 discard

// 2) 发光边缘:距阈值一定带宽内叠色
float edge = 1.0 - smoothstep(0.0, _EdgeWidth, noise - _Progress);
col.rgb = lerp(col.rgb, _EdgeColor.rgb, edge);
col.a  *= step(0.5, noise - _Progress + edge * 0.5); // 边缘半透明可选

坑:clip/discard 破坏 early-Z(见 /notes/unity/principles-3/),大面积溶解材质在移动端明显更贵(见 /notes/unity/performance-1/);噪声 UV 用模型 UV 还是世界坐标(XZ 投影)决定「烧的是贴图位置还是世界位置的一块」;边缘色要配加法混合才「发光」,见 /notes/unity/structure-3/。

配方二:扭曲(distortion)

原理:用滚动的噪声图偏移采样 UV——热浪、水面折射、能量护盾都是这一招。

// 扭曲量:[-1,1] 的双通道噪声随时间滚动
float2 distort = SAMPLE_TEXTURE2D(_DistortTex, sampler, i.uv * _Tiling + _Time.xy * _Speed).rg;
distort = (distort - 0.5) * _Strength;

// 扭曲作用于自身贴图
col = SAMPLE_TEXTURE2D(_MainTex, sampler, i.uv + distort);
// 或作用于「背后的画面」(见配方三)

坑:扭曲强度过大会把采样拉到贴图边界外——Clamp/Repeat 模式要看清;两层不同速度的扭曲叠加观感立刻高级一档,代价只是多一次采样。

配方三:屏幕空间数据——GrabPass 与 _CameraOpaqueTexture

扭曲要「背后画面的颜色」才成立,这就是屏幕空间效果的核心问题:片元着色器不能读邻居像素(见 /notes/unity/principles-4/),只能读引擎预先拷出来的屏幕纹理。

屏幕 UV 的来路:

// 顶点程序:把裁剪空间位置投到 [0,1] 屏幕坐标
o.screenUV = ComputeScreenPos(o.pos);   // Built-in;URP 有 GetNormalizedScreenSpaceUV

// 片元程序:
float2 suv = i.screenUV.xy / i.screenUV.w;   // 透视除法,别漏
suv += distort;                               // 扭曲屏幕采样
col = lerp(col, SAMPLE_TEXTURE2D(_GrabTex, sampler, suv), _RefractAmount);

两条数据来源:

Built-in GrabPass {}URP _CameraOpaqueTexture
机制每个使用该材质的 Draw Call 前拷一次屏幕不透明队列结束后每帧拷一次
代价每物体一次全屏拷贝,带宽灾难每帧一次,可控
前提无URP Renderer 勾选 Opaque Texture

坑:屏幕纹理只有不透明队列的颜色——扭曲效果永远画在透明物体之前,玻璃后面的粒子不会被折射;拷贝纹理是全屏分辨率,移动端降分辨率或慎用;GrabPass 在 URP 不可用。

组合:全息闪烁

溶解(做扫描缺口)+ 扭曲(折射背景)+ 菲涅尔边缘(见 /notes/unity/effects-2/)+ 加法混合——四个配方叠出一个效果,每个都只几行。

自检三问

  • 溶解材质为什么在手机上比同面积的普通材质贵?
  • ComputeScreenPos 的结果为什么还要除以 w?
  • URP 里 GrabPass 换成什么?两者代价差在哪?