GameTech/URP 渲染实战

案例 21.6软粒子:与场景的柔和相交进阶

目标:消除粒子面片插进地面、墙壁时那条生硬的直线。

粒子是平面,场景几何体是实体;面片与地面相交,深度测试会把地面以下的部分整齐切掉,留下一条直线,立刻暴露出“这是一张纸片”。软粒子(Soft Particles)的思路是:在片元着色器里读取场景深度,比较“场景表面的距离”与“粒子自身的距离”,两者越接近(粒子越要插进物体),alpha 越小:

fade=saturate(zscene−zparticled)

两个深度都必须是线性的观察空间深度(单位:米)。场景深度来自 _CameraDepthTexture,用 SampleSceneDepth 读出的是非线性的设备深度,要经过 LinearEyeDepth(raw, _ZBufferParams) 转换;粒子自身的观察空间深度在顶点着色器里直接取 −positionVS.z 传下来(Unity 观察空间看向 −Z)。d 是过渡距离,代码里的 _SoftDistance。

作为对照,摘自 URP 14 ShaderLibrary/Particles.hlsl 的实现(注意它支持正交相机,还多了一个 near 偏移参数):

float SoftParticles(float near, float far, float4 projection)
{
    float fade = 1;
    if (near > 0.0 || far > 0.0)
    {
        float2 uv = UnityStereoTransformScreenSpaceTex(projection.xy / projection.w);
        uv = FoveatedRemapLinearToNonUniform(uv);
        float rawDepth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, uv).r;
        float sceneZ = (unity_OrthoParams.w == 0) ? LinearEyeDepth(rawDepth, _ZBufferParams) : LinearDepthToEyeDepth(rawDepth);
        float thisZ = LinearEyeDepth(projection.z / projection.w, _ZBufferParams);
        fade = saturate(far * ((sceneZ - near) - thisZ));
    }
    return fade;
}
图 21-11一团贴地的烟(大面片)与地面、箱子相交。左:普通粒子,地面上和箱子边缘都有清晰的切线;右:软粒子(过渡 0.6 米),烟雾柔和地“沉”进地面和箱子背后
软粒子需要深度图URP 只有在 URP Asset 勾选了 Depth Texture(或相机单独开启、或某个 Renderer Feature 请求了深度)时才会生成 _CameraDepthTexture;否则采到的是默认值,所有粒子要么全透明要么毫无变化。URP 的粒子材质面板会在这种情况下给出提示,自写 Shader 则不会,排查时先看 Frame Debugger 里有没有 CopyDepth 这一步。另外,在移动端生成深度图本身有带宽开销(可能需要一次额外的 Depth Prepass 或拷贝),大量使用软粒子前要评估。

同样的“深度差”还可以做近相机淡出:粒子离相机太近时会糊满整个屏幕,带来巨大的 Overdraw,按 thisZ 在近处淡出即可(代码中的 _CAMERAFADE_ON 分支)。它不需要深度图,开销几乎为零,烟雾类粒子建议默认打开。