GameTech/URP 渲染实战

案例 27.1受击反馈与低血量去色入门

目标:游戏逻辑调用 OnHit() 时,屏幕边缘闪一下暗红色暗角并带一点色差,随后逐渐恢复;血量低于 30% 时画面逐渐去色。所有效果都放在一个专用的高优先级全局 Volume 上,和场景的美术 Volume 互不干扰。

Assets/Cookbook/Ch27/PostFxController.cs(节选:setup)
void OnEnable()
{
    m_Volume = GetComponent<Volume>();
    // profile(而不是 sharedProfile)会在第一次访问时克隆一份运行时实例,改它不会污染磁盘上的资源
    var profile = m_Volume.profile;
    m_Vignette = GetOrAdd<Vignette>(profile);
    m_CA = GetOrAdd<ChromaticAberration>(profile);
    m_Color = GetOrAdd<ColorAdjustments>(profile);
}

static T GetOrAdd<T>(VolumeProfile profile) where T : VolumeComponent
{
    if (!profile.TryGet(out T comp)) comp = profile.Add<T>();
    comp.active = true;
    return comp;
}
Assets/Cookbook/Ch27/PostFxController.cs(节选:update)
void Update()
{
    m_Hit = Mathf.Max(0f, m_Hit - hitDecay * Time.deltaTime);

    // Override(x) 同时做两件事:overrideState = true,value = x。
    // 只改 .value 而 overrideState 为 false 时,这个参数不参与混合,改了等于没改。
    m_Vignette.color.Override(hitColor);
    m_Vignette.intensity.Override(Mathf.Lerp(0.2f, 0.55f, m_Hit));
    m_Vignette.smoothness.Override(0.6f);
    m_CA.intensity.Override(m_Hit * 0.8f);

    // 低血量:血量低于 30% 后逐渐去色
    float lowHp = Mathf.InverseLerp(0.3f, 0f, health);
    m_Color.saturation.Override(Mathf.Lerp(0f, -80f, lowHp));
}

几个关键点:volume.profile 会克隆一份运行时 Profile,修改它不会弄脏资源文件,但要记得在销毁时释放;修改参数用 Override(value),它同时把 overrideState 设为 true——只赋 .value 而没勾 override 的参数不参与混合,这是“脚本改了没效果”的头号原因;TryGet 找不到组件时用 Add 添加,并确认 active 为 true。

如果想知道所有 Volume 混合之后的最终值(例如让 UI 随 Bloom 强度变化),读 VolumeManager.instance.stack:

Assets/Cookbook/Ch27/PostFxController.cs(节选:read)
/// 读取“混合之后”的最终值(所有 Volume 按权重、优先级叠加的结果)。
/// 注意 VolumeManager.instance.stack 是默认栈,由当前渲染的相机在每帧更新。
public static float CurrentBloomIntensity()
{
    var bloom = VolumeManager.instance.stack.GetComponent<Bloom>();
    return bloom != null && bloom.IsActive() ? bloom.intensity.value : 0f;
}

默认栈由正在渲染的相机每帧更新,多相机时要注意读取时机;也可以用 VolumeManager.instance.CreateStack() 建立自己的栈,再 VolumeManager.instance.Update(stack, trigger, layerMask) 手动更新。URP Asset 的 Volume Update Mode 设为 Via Scripting 时,相机不会每帧自动更新 Volume,需要调用 UniversalAdditionalCameraData.UpdateVolumeStack(),在 Volume 很多、参数很少变化的项目里可以省下 CPU。

本案例出自《URP 渲染实战》第 27 章「URP 内置后处理全解析」。在章节中阅读上下文 →

本章全部案例

  1. 案例 27.1 受击反馈与低血量去色

全书 234 个案例:案例目录 · 完整工程:源码与下载