起因

最近在搞 URP,需要用到 UI 背景 Blur的效果,突然想起 PostProcessing 中本身可以实现这个效果

基础配置

Unity

我们使用的版本为 2019.4+

Universal RP

Package

目前 2019.4 稳定版 URP 版本号为 7.3.1,但是这个版本没有 Camera Stack 功能

该功能用于实现多个相机叠加画面的效果,我们项目中希望可以实现不同部分参与或不参与 PostProcessing 过程

目前我们用的版本为 7.4.1

2D Renderer 配置

参考视频

视频中关于如何设置 URP 2D Renderer 的描述很详细,这里仅简单介绍一下

  • 创建 UniversalRenderPipelineAsse文件
  • 创建 2D Renderer 文件
  • URP 文件中 Renderer List 属性设置为刚刚创建好的 2D Renderer
  • Project Setting -> Graphics -> Scriptable Render Pipeline Setting 设置为 URP 文件

需要注意的是,URP 只建议在考虑兼容性上使用 Post Processing V2,我们还是要老老实实选第一个

思路及实现方式

我希望区分部分场景物体参与 PostProcessing 部分不参与,且 UI 部分可以正常显示在游戏画面的最上方

这里我们通过 Culling Mask + Camera Stack 功能来实现

Layer

首先我们为参与 PostProcessing 的场景物体创建一个单独的 Layer,名字就叫 PostProcessing

Camera

我们创建一个 PostProcessing 相机,这个相机是我们游戏中的场景相机,且为主摄像机,它的职责是用来接收后处理,且仅拍摄参与后处理的场景物体

这里 Render Type 一定要选 Base

接着在这个相机下面创建一个 NonePostProcessing 相机,如下图所示

这个相机的职责是绘制所有不参与后处理的场景物体,所以 Culling Mask 不要勾选 UI LayerPostProcessing Layer

在我们项目中使用到了 TimelineCinemachine 功能,很多时候需要运镜,我们要让两次绘制的画面一致,所以要在第二个相机下面加上同步主摄像机 Orthographic Size 的功能

[ExecuteInEditMode]
public class NoPPCameraSync : MonoBehaviour {
    [SerializeField]
    private Camera _mainCamera;

    [SerializeField]
    private Camera _noPPCamera;

    private void Update() { _noPPCamera.orthographicSize = _mainCamera.orthographicSize; }
}

代码也非常简单,因为主相机在被 Virtual Camera 控制时,Size是一个动态变化的过程,我们干脆就直接在 Update 函数中,每帧同步主相机的大小

最后我们需要给这些相机放到 Stack 里面,这样画面才可以正常显示

Volume

我们可以在 Hierarchy 窗口邮件创建一个 Volume 用于设置游戏中的后处理

我使用的是 Global Volume,可以根据情况使用,具体功能这里不做更详细的介绍

通过对 Depth of Field 的调整,我们可以实现背景虚化的效果

这里要注意的是,挂载 Volume 物体的 Layer 必须是 Default,不知道这个是 Bug 还是设计如此,我当前使用的版本,不可以根据当前后处理的 Layer 分配给不同的相机,选择非 Default Layer 则不生效

UI Blur

最终和 UI 实现背景虚化的效果,我想做成一个组件的形式,当开启这个 UI 时,自动开启上述 Volume,当关闭时,自动关掉当前效果

public class UIBlur : MonoBehaviour {
    private void OnEnable() {
        PostProcessingManager.SetEffectTween(
            PostProcessingType.Bokeh,
            0,
            1,
            1.5f
        );
    }

    private void OnDisable() { PostProcessingManager.SetEffect(PostProcessingType.Bokeh, 0); }
}

我们通过 PostProcessing 提供外部调用的接口,这里只存放通用类型的后处理,平时不用时默认关掉

层级关系如下,当需要创建更多通用的后处理,可以统一放在这里管理

public class PostProcessingManager : MonoBehaviour {

    private static Dictionary<PostProcessingType, Tween> _tweens = new Dictionary<PostProcessingType, Tween>();

    private static Dictionary<PostProcessingType, Volume> _volumes = new Dictionary<PostProcessingType, Volume>();

    private void Awake() {
        Volume component;

        foreach(PostProcessingType v in Enum.GetValues(typeof(PostProcessingType))){
            if(_volumes.ContainsKey(v)){ continue; }

            component = transform.Find(v.ToString()).GetComponent<Volume>();

            if(component == null){
                Log.Error($"{v} effect doesn't exist!");
                continue;
            }

            _volumes.Add(v, component);
        }
    }

    public static void SetEffect(PostProcessingType type, float weight) {
        if(!_volumes.ContainsKey(type)){ return; }

        if(_tweens.ContainsKey(type) && _tweens[type] != null){ _tweens[type].Kill(); }

        _volumes[type].weight = weight;
    }

    public static void SetEffectTween(PostProcessingType type,
                                      float              from,
                                      float              to,
                                      float              time) {
        if(!_volumes.ContainsKey(type)){ return; }

        if(_tweens.ContainsKey(type) && _tweens[type] != null){ _tweens[type].Kill(); }

        if(!_tweens.ContainsKey(type)){ _tweens.Add(type, null); }

        _volumes[type].weight = from;

        _tweens[type] = DOTween.To(
            () => _volumes[type].weight,
            value => _volumes[type].weight = value,
            to,
            time
        );
    }
}

public enum PostProcessingType {
    Bokeh
}

其他效果

如果需要其他触发后处理的方式,比如特定地点使用的特殊后处理,我们可以通过开关当前 Volume 物体的方式实现,或者其他非 Global Volume 通过碰撞实现

已知的坑

Cinemachine 不会切换 VCam

我被这个问题困扰了很久,设置完后处理之后,之前设置的所有虚拟相机不会自动切换,Blend 效果也消失了

最后发现,用于切换相机的 CinemachineBrain 会根据当前相机的 Culling Mask 是否能看到 Virtual Camera 来进行切换

!!!

有道理,但仍然花了很多时间才找到原因 T^T

如果同样适用 Cinemachine 的同学,记得把虚拟相机的 Layer 设置为 PostProcessing 和主相机的 Culling Mask 一致


What doesn’t kill you makes you stronger.