Unity

「Unity」URP管线学习 后处理组件

by Ayse, 2022-06-27


PostPossessing in URP

这是一篇写了很久但是没有放上来的文章,最近正好可以整理一下下。 查了半天,都找不到很方便可以在urp管线的volume上挂shader的直接有效的方法,后面发现是通过render feature实现的,虽然灵活(可以控制绘制层以实现遮挡的效果)但是终究会很麻烦,本文主要实现两部分,urp的后处理效果标准做法(renderfeature)和urp的后处理工具扩展


首先写一个目录,在当下URP的后处理的方法可以总结为 1、建立一个volume,挂上一个component,其中的profile就是一个集成我们后处理效果的东西,当然别忘了开相机的postpossessing 2、建立后处理面板控制器,继承VolumeComponent,IPostProcessComponent类 3、建立renderfeature类实现和管线结合的类方法,ScriptableRendererFeature.建立PostProcessPass类,ScriptableRenderPass,一切后处理就是一个pass的事儿 4、Shader链接上去 5、配置管线,建立一个URP的instance然后加上renderfeature 未命名图片.png


Render feature

Renderer Feature可让我们向URP Renderer添加额外的渲染通道,支持我们进行Asset资产配置来重写从而可以自定义渲染的顺序、渲染的对象、材质等等。很显然,就是让它的管线变得一点点不一样呗。具体在哪我们可以看到我们只需要右键新建render feature然后在管线加入就行

我们新建一个renderfeature,可以看到我们的 Scriptable Renderer Feature 由两个类 CustomRenderPassFeature 与 CustomRenderPass 组成,CustomRenderPassFeature 类继承自 ScriptableRendererFeature,CustomRenderPass 类继承自 ScriptableRenderPass

如果我们需要对renderfeature在我们的URP实例设置中暴露出调参数的接口,就写一个renderfeature的setting类,打上[System.Serializable]的tag并实例化(但是感觉并不是很方便,so没什么必要)


CustomRenderPassFeature

Render Feature 可以在渲染管线的某个时间点增加一个 Pass 或者多个 Pass 我们打开scriptable render feature的定义,里面的功能主要就是初始化我们的feature用的,就不过多赘述,我们主要实现的是creat()和addrenderpass方法,Creat函数初始化feature的资源,然后设置renderer(每一个相机设置一次)还决定了feature的插入位置(默认after opaue)后者则负责 Render Pass 加到 Renderer 里面。下面就是创建过程。 2.png

public class ZoomBlurRenderFeature : ScriptableRendererFeature
{
    ZoomBlurPass zoomBlurPass;

    public override void Create()
    {
         zoomBlurPass = new ZoomBlurPass(RenderPassEvent.BeforeRenderingPostProcessing);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        zoomBlurPass.Setup(renderer.cameraColorTarget);
        renderer.EnqueuePass(zoomBlurPass);
    }
}

CustomRenderPass

我们回到scriptableevent的父类,看到大概有这些渲染顺序可以让我们插入。 3.png

而在renderpass里面定义的几个重要的方法后续要用到我们的feature上 Configure() 在执行渲染过程之前,Renderer 将调用此方法。如果需要配置渲染目标及其清除状态,并创建临时渲染目标纹理,那就要重写这个方法。如果渲染过程未重写这个方法,则该渲染过程将渲染到激活状态下 Camera 的渲染目标。我们这里是不需要重写这个方法的,因为我们就拿的原始Camera的target Execute() 是这个类的核心方法,定义我们的执行规则;包含渲染逻辑,设置渲染状态,绘制渲染器或绘制程序网格,调度计算等等。可以参考下面的代码,里面就是最经典的Graphic类里的代码了。 FrameCleanup() 可用于释放通过此过程创建的分配资源。完成渲染相机后调用。就可以使用此回调释放此渲染过程创建的所有资源。 Setup()方法则是为了在RenderFeature中初始化调用,Renderer可以拿到RT。


Start Coding

首先创建一个volumecomponent的脚本,继承volomecomponent类和IPostProcessComponent类,是一个非常框架化的写法,共同实现面板功能(至于后者tile兼容的函数到底是什么意思我还没查到)【btw,c#里面的=>是lambda运算符,相当于委托右边产生返回值给左边】这里的参数在完成写法之后就可以暴露在面板上。 4.png

然后像是和在上面的那个一样,建立Render Feature类,主要是为RenderPass埋下基础。写好了这个之后写上就加上一个RenderPass的类,第一个Tag上用在CommandBufferPool中的,FrameDebugger就能看到这个效果,后面就是一些构造函数 5.png

public ZoomBlurPass(RenderPassEvent evt)
    {
        renderPassEvent = evt;
        var shader = Shader.Find("PostEffect/ZoomBlur");
        if (shader == null)
        {
            Debug.LogError("Shader not found.");
            return;
        }
        zoomBlurMaterial = CoreUtils.CreateEngineMaterial(shader);
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (zoomBlurMaterial == null)
        {
            Debug.LogError("Material not created.");
            return;
        }

        if (!renderingData.cameraData.postProcessEnabled) return;

        var stack = VolumeManager.instance.stack;
        zoomBlur = stack.GetComponent<ZoomBlur>();
        if (zoomBlur == null) { return; }
        if (!zoomBlur.IsActive()) { return; }

        var cmd = CommandBufferPool.Get(k_RenderTag);
        Render(cmd, ref renderingData);
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }

    public void Setup(in RenderTargetIdentifier currentTarget)
    {
        this.currentTarget = currentTarget;
    }

    void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        ref var cameraData = ref renderingData.cameraData;
        var source = currentTarget;
        int destination = TempTargetId;

        var w = cameraData.camera.scaledPixelWidth;
        var h = cameraData.camera.scaledPixelHeight;
        zoomBlurMaterial.SetFloat(FocusPowerId, zoomBlur.focusPower.value);
        zoomBlurMaterial.SetInt(FocusDetailId, zoomBlur.focusDetail.value);
        zoomBlurMaterial.SetVector(FocusScreenPositionId, zoomBlur.focusScreenPosition.value);
        zoomBlurMaterial.SetInt(ReferenceResolutionXId, zoomBlur.referenceResolutionX.value);

        int shaderPass = 0;
        cmd.SetGlobalTexture(MainTexId, source);
        cmd.GetTemporaryRT(destination, w, h, 0, FilterMode.Point, RenderTextureFormat.Default);
        cmd.Blit(source, destination);
        cmd.Blit(destination, source, zoomBlurMaterial, shaderPass);
    }

然后我们总结:这也太麻烦了,如果要做两个后处理暴露面板还需要再加一个renderfeature,这也意味着,如果我要开RT,都是独立的RT,没有办法互用,需要blit很多次,消耗也是大的,但是我们知道,URP自带的后处理是很方便的,那么如果我们让我们写的shader可以被那里直接很方便的调用呢? 6.png

扩展参考 https://www.jianshu.com/p/b9cd6bb4c4aa?ivk_sa=1024320u rf参考 https://zhuanlan.zhihu.com/p/373273390

作者: Ayse

2024 © typecho & elise