GameTech/URP 渲染实战

案例 18.2边缘发光:让溶解有“燃烧”的前沿入门

目标:在溶解的前沿加一条发光的边,可以是纯色硬边(科幻风格的“数字化消散”),也可以是从暗红到亮黄的渐变火边(燃烧)。

原理:阈值之上再划一条带

记 d = v − p:d < 0 的像素已经被裁掉;d 刚好大于 0 的像素“即将被溶掉”,它们就是前沿。取一个宽度 w,令

edge=1−saturate(dw)

则 d=0 处 edge=1,d≥w 处 edge=0,中间线性过渡。用 edge 去查一张 Ramp 贴图(公共贴图 ramp_fire:左边暗紫,右边亮黄白),越靠近前沿越亮、越黄,往里逐渐变暗红——正好是燃烧的颜色分布。

v v < p:clip 掉 边带:0 ≤ d < w 正常着色 阈值 p p + w edge = 1 edge = 0 进度 p 增大 → 阈值右移 → 边带扫过整个表面
图 18-3沿溶解值轴看:阈值左边被裁掉,阈值右边宽度 w 的一段是发光边带,edge 从 1 线性降到 0,用来查 Ramp。

片元函数如下:

Assets/Cookbook/Ch18/Dissolve.shader(节选:frag)
half4 frag(Varyings i, FRONT_FACE_TYPE face : FRONT_FACE_SEMANTIC) : SV_Target
{
    half edge;
    DissolveClip(DissolveValue(i.uv, i.positionOS, i.positionWS), edge);

    half3 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv * _BaseMap_ST.xy + _BaseMap_ST.zw).rgb * _BaseColor.rgb;
    // 双面渲染时,背面(物体内壁)把法线翻过来,再压暗一点,看起来像“空心的内部”
    half3 normalWS = normalize(i.normalWS) * IS_FRONT_VFACE(face, 1.0h, -1.0h);
    albedo *= IS_FRONT_VFACE(face, 1.0h, 0.35h);

    // 前沿附近先“烧焦”变暗,再在最前沿发光
    albedo *= 1.0h - 0.85h * smoothstep(0.0h, 0.6h, edge);
    half3 color = SimpleLighting(albedo, i.positionWS, normalWS, _Smoothness);

    half3 glow = _EdgeColor.rgb;
    if (_UseRamp > 0.5)
        glow *= SAMPLE_TEXTURE2D(_EdgeRamp, sampler_EdgeRamp, float2(edge, 0.5)).rgb;
    // edge^2:让发光集中在最前沿,向内快速衰减
    color += glow * edge * edge;

    color = MixFog(color, i.fogFactor);
    return half4(color, 1);
}
void main()
{
    float edge;
    DissolveClip(DissolveValue(uv, posOS, positionWS), edge);

    vec3 albedo = texture(_BaseMap, uv * _BaseMap_ST.xy + _BaseMap_ST.zw).rgb * _BaseColor.rgb;
    // 双面渲染时,背面(物体内壁)把法线翻过来,再压暗一点,看起来像“空心的内部”
    vec3 N = normalize(normalWS) * (gl_FrontFacing ? 1.0 : -1.0);
    albedo *= gl_FrontFacing ? 1.0 : 0.35;

    // 前沿附近先“烧焦”变暗,再在最前沿发光
    albedo *= 1.0 - 0.85 * smoothstep(0.0, 0.6, edge);
    vec3 color = SimpleLighting(albedo, positionWS, N, _Smoothness);

    vec3 glow = _EdgeColor.rgb;
    if (_UseRamp > 0.5)
        glow *= texture(_EdgeRamp, vec2(edge, 0.5)).rgb;
    // edge^2:让发光集中在最前沿,向内快速衰减
    color += glow * edge * edge;

    color = MixFog(color, fogFactor);
    fragColor = vec4(color, 1.0);
}

两处细节值得说。第一,发光项乘的是 edge * edge 而不是 edge:平方让亮度集中在最前沿、往里快速衰减,看起来更像一条细亮线而不是一片光晕。第二,在前沿后方先把反照率压暗(albedo *= 1 - 0.85 * smoothstep(0, 0.6, edge)),模拟纸张燃烧时火线前面先“烤焦”的那一圈,这比单纯加一条亮边可信得多。

HDR 颜色与 Bloom

_EdgeColor 用 [HDR] 标记,分量可以填到 4、5。URP 默认开启 HDR 时,相机颜色缓冲是浮点格式,能存下大于 1 的值;后处理 Bloom 会把超过阈值的部分提取出来模糊、叠回画面,产生“溢光”。没有后处理时,大于 1 的颜色会在最终输出时被截断成白色,你只能看到一条刺眼的白边——这是很多人“发光颜色怎么调都发白”的原因。

图 18-4边缘发光。左:无边带(EdgeWidth=0);中:纯色硬边(青色 HDR,宽 0.04);右:Ramp 火边(宽 0.1),并开启 Bloom + Tonemapping 后处理——溢光效果来自后处理,不是 Shader 本身。
注意本书配图默认关闭后处理,以便看清 Shader 的原始输出;凡是标注“开启 Bloom”的图,都是在 Volume 里加了 Bloom(阈值约 1)和 Neutral Tonemapping。Bloom 的阈值、散射等参数见第 27 章。