返回
Featured image of post URP后处理框架

URP后处理框架

该章节对上一章节的屏幕后处理框架进行调整,以方便后续扩展其他的后处理效果。请务必将上一章节内容看懂,再来理解本章。这里还是以亮度饱和度对比度效果来作为案例。

代码部分修改

AdditionPostProcessRendererFeature

namespace UnityEngine.Rendering.Universal
{
    /// <summary>
    /// 可编程渲染功能
    /// 必须要继承ScriptableRendererFeature抽象类,
    /// 并且实现AddRenderPasses跟Create函数
    /// </summary>
    public class AdditionPostProcessRendererFeature : ScriptableRendererFeature
    {
        // 后处理Pass
        AdditionPostProcessPass postPass;
        // 保存Shader的对象引用
        public AdditionalPostProcessData postData;

        //在这里,您可以在渲染器中注入一个或多个渲染通道。
        //每个摄像机设置一次渲染器时,将调用此方法。
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            if (postPass == null)
            {
                return;
            }
            // 设置调用后处理Pass
            postPass.Setup(renderingData.cameraData.cameraTargetDescriptor, renderer.cameraColorTarget, renderer.cameraDepth, RenderTargetHandle.CameraTarget);
            
            // 添加该Pass到渲染管线中
            renderer.EnqueuePass(postPass);
        }


        // 对象初始化时会调用该函数
        public override void Create()
        {
            postPass = new AdditionPostProcessPass(RenderPassEvent.AfterRenderingTransparents, postData);
        }
    }
}

在该类中,我们将Shader的获取交由一个新的对象AdditionalPostProcessData去保存。

AdditionalPostProcessData

  • AdditionalPostProcessData

    using System;
    
    namespace UnityEngine.Rendering.Universal
    {
        /// <summary>
        /// 附加后处理数据
        /// </summary>
        [Serializable]
        public class AdditionalPostProcessData : ScriptableObject
        {
            [Serializable]
            public sealed class Shaders
            {
                public Shader brightnessSaturationContrast;
                //在这里扩展后续其他后处理Shader引用
            }
            public Shaders shaders;
        }   
    }
    
  • AdditionalPostProcessDataEditor

    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    
    namespace UnityEngine.Rendering.Universal
    {
        public class AdditionalPostProcessDataEditor : ScriptableObject
        {
    #if UNITY_EDITOR
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812")]
    
            [MenuItem("Assets/Create/Rendering/Universal Render Pipeline/Additional Post-process Data", priority = CoreUtils.assetCreateMenuPriority3 + 1)]
            static void CreateAdditionalPostProcessData()
            {
                var instance = CreateInstance<AdditionalPostProcessData>();
                AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(AdditionalPostProcessData).Name));
                Selection.activeObject = instance;
            }
    #endif
        }   
    }
    

我们需要将AdditionalPostProcessDataEditor类放入Editor文件夹中,然后就可以在工程面板右击创建我们的Shader引用对象。接下来就可以在AdditionalPostProcessDataInspector面板中添加我们扩展的后处理Shader。

后续扩展需要我们在AdditionalPostProcessData类的Shaders子类中添加对应Shader的公开属性

AdditionalMaterialLibrary

namespace UnityEngine.Rendering.Universal
{
    /// <summary>
    /// 材质列表
    /// </summary>
    public class AdditionalMaterialLibrary
    {
        public readonly Material brightnessSaturationContrast;
        // 这里扩展后处理材质属性

        /// <summary>
        /// 初始化时从配置文件中获取材质
        /// </summary>
        /// <param name="data"></param>
        public AdditionalMaterialLibrary(AdditionalPostProcessData data)
        {
            brightnessSaturationContrast = Load(data.shaders.brightnessSaturationContrast);
            // 这里扩展后处理材质的加载
        }

        Material Load(Shader shader)
        {
            if (shader == null)
            {
                Debug.LogErrorFormat($"丢失 shader. {GetType().DeclaringType.Name} 渲染通道将不会执行。检查渲染器资源中是否缺少引用。");
                return null;
            }
            else if (!shader.isSupported)
            {
                return null;
            }
            return CoreUtils.CreateEngineMaterial(shader);
        }

        internal void Cleanup()
        {
            CoreUtils.Destroy(brightnessSaturationContrast);
        }
    }
}

该类用于我们从上一步创建的Shader引用对象中获取Shader和创建材质。后续要扩展后处理材质,需要在两步:

  1. 在属性部分添加对应材质的引用
  2. AdditionalMaterialLibrary方法中添加对应的材质加载方法

AdditionPostProcessPass

using UnityEngine.Experimental.Rendering;

namespace UnityEngine.Rendering.Universal
{
    /// <summary>
    /// 附加的后处理Pass
    /// </summary>
    public class AdditionPostProcessPass : ScriptableRenderPass
    {
        //标签名,用于续帧调试器中显示缓冲区名称
        const string CommandBufferTag = "AdditionalPostProcessing Pass";

        // 用于后处理的材质
        Material m_BlitMaterial;
        AdditionalMaterialLibrary m_Materials;
        AdditionalPostProcessData m_Data;

        // 主纹理信息
        RenderTargetIdentifier m_Source;
        // 深度信息
        RenderTargetIdentifier m_Depth;
        // 当前帧的渲染纹理描述
        RenderTextureDescriptor m_Descriptor;
        // 目标相机信息
        RenderTargetHandle m_Destination;

        // 临时的渲染目标
        RenderTargetHandle m_TemporaryColorTexture01;


        // 属性参数组件
        BrightnessSaturationContrast m_BrightnessSaturationContrast;

        /// 这里扩展后续的属性参数组件引用


        public AdditionPostProcessPass(RenderPassEvent evt, AdditionalPostProcessData data, Material blitMaterial = null)
        {
            renderPassEvent = evt;
            m_Data = data;
            m_Materials = new AdditionalMaterialLibrary(data);
            m_BlitMaterial = blitMaterial;
        }

        public void Setup(in RenderTextureDescriptor baseDescriptor, in RenderTargetIdentifier source, in RenderTargetIdentifier depth, in RenderTargetHandle destination)
        {
            m_Descriptor = baseDescriptor;
            m_Source = source;

            m_Depth = depth;
            m_Destination = destination;
        }


        /// <summary>
        /// URP会自动调用该执行方法
        /// </summary>
        /// <param name="context"></param>
        /// <param name="renderingData"></param>
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            // 从Volume框架中获取所有堆栈
            var stack = VolumeManager.instance.stack;
            // 从堆栈中查找对应的属性参数组件
            m_BrightnessSaturationContrast = stack.GetComponent<BrightnessSaturationContrast>();

            /// 这里扩展后续的属性参数组件获取
 
            // 从命令缓冲区池中获取一个带标签的渲染命令,该标签名可以在后续帧调试器中见到
            var cmd = CommandBufferPool.Get(CommandBufferTag);

            // 调用渲染函数
            Render(cmd, ref renderingData);

            // 执行命令缓冲区
            context.ExecuteCommandBuffer(cmd);
            // 释放命令缓存
            CommandBufferPool.Release(cmd);
        }

        // 渲染
        void Render(CommandBuffer cmd, ref RenderingData renderingData)
        {
            ref var cameraData = ref renderingData.cameraData;
            bool m_IsStereo = renderingData.cameraData.isStereoEnabled;
            bool isSceneViewCamera = cameraData.isSceneViewCamera;

            // VolumeComponent是否开启,且非Scene视图摄像机
            // 亮度、对比度、饱和度
            if (m_BrightnessSaturationContrast.IsActive() && !isSceneViewCamera)
            {
                SetBrightnessSaturationContrast(cmd, m_Materials.brightnessSaturationContrast);
            }

            /// 这里扩展后续的后处理方法的开关校验

        }

        RenderTextureDescriptor GetStereoCompatibleDescriptor(int width, int height, int depthBufferBits = 0)
        {
            var desc = m_Descriptor;
            desc.depthBufferBits = depthBufferBits;
            desc.msaaSamples = 1;
            desc.width = width;
            desc.height = height;
            return desc;
        }



        #region 处理材质渲染
        // 亮度、饱和度、对比度渲染
        void SetBrightnessSaturationContrast(CommandBuffer cmd, Material uberMaterial)
        {
            // 写入参数
            uberMaterial.SetFloat("_Brightness", m_BrightnessSaturationContrast.brightness.value);
            uberMaterial.SetFloat("_Saturation", m_BrightnessSaturationContrast.saturation.value);
            uberMaterial.SetFloat("_Contrast", m_BrightnessSaturationContrast.contrast.value);

            // 通过目标相机的渲染信息创建临时缓冲区
            //RenderTextureDescriptor opaqueDesc = m_Descriptor;
            //opaqueDesc.depthBufferBits = 0;
            //cmd.GetTemporaryRT(m_TemporaryColorTexture01.id, opaqueDesc);
            //or
            int tw = m_Descriptor.width;
            int th = m_Descriptor.height;
            var desc = GetStereoCompatibleDescriptor(tw, th);
            cmd.GetTemporaryRT(m_TemporaryColorTexture01.id, desc, FilterMode.Bilinear);

            // 通过材质,将计算结果存入临时缓冲区
            cmd.Blit(m_Source, m_TemporaryColorTexture01.Identifier(), uberMaterial);
            // 再从临时缓冲区存入主纹理
            cmd.Blit(m_TemporaryColorTexture01.Identifier(), m_Source);

            // 释放临时RT
            cmd.ReleaseTemporaryRT(m_TemporaryColorTexture01.id);
        }

        /// 这里扩展后处理对材质填充方法

        #endregion
    }
}

后续添加后处理效果需要在代码中四个部分进行扩展:

  1. 首先要在属性部分添加对对应属性参数组件类的引用属性
  2. Execute方法中添加从Volume框架中获取对应属性参数类的方法。
  3. Render方法中添加对后处理效果是否开启的校验
  4. 在后续 #region代码块中添加我们对材质填充调用方法

属性参数组件

using System;

// 通用渲染管线程序集
namespace UnityEngine.Rendering.Universal
{
    // 实例化类     添加到Volume组件菜单中
    [Serializable, VolumeComponentMenu("Addition-Post-processing/BrightnessSaturationContrast")]
    // 集成VolumeComponent组件和IPostProcessComponent接口,用以继承Volume框架
    public class BrightnessSaturationContrast : VolumeComponent, IPostProcessComponent
    {
        [Tooltip("开关")]
        public BoolParameter _Switch = new BoolParameter(false);

        [Tooltip("亮度")]
        public ClampedFloatParameter brightness = new ClampedFloatParameter(1f, 0, 3);
        [Tooltip("饱和度")]
        public ClampedFloatParameter saturation = new ClampedFloatParameter(1f, 0, 3);
        [Tooltip("对比度")]
        public ClampedFloatParameter contrast = new ClampedFloatParameter(1f, 0, 3);

        // 实现接口
        public bool IsActive() => _Switch.value;

        public bool IsTileCompatible()
        {
            return false;
        }
    }
}

这一部分没有太大修改,后续扩展其他效果需要仿照写对应的属性参数类。

需要注意的是,原本对效果开关的校验是通过组件的active属性来判断的,但在实际运用中,我发现就算关闭了组件在面板上的开关,获取到的active属性也依旧是开启状态。所以为了准确开关,我在面板中添加了上面代码中的开关选项。

        public bool IsActive()
        {
            return active;
        }

Shader

Shader "URP/Brightness Saturation And Contrast"
{
    Properties
    {
        // 基础纹理
        _MainTex ("Base (RGB)", 2D) = "white" { }
        // 亮度
        _Brightness ("Brightness", Float) = 1
        // 饱和度
        _Saturation ("Saturation", Float) = 1
        // 对比度
        _Contrast ("Contrast", Float) = 1
    }
    SubShader
    {
        Tags { "RenderPipeline" = "UniversalPipeline" }
        
        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        
        CBUFFER_START(UnityPerMaterial)
        float4 _MainTex_ST;
        half _Brightness;
        half _Saturation;
        half _Contrast;
        CBUFFER_END
        
        ENDHLSL
        
        Pass
        {
            // 开启深度测试 关闭剔除 关闭深度写入
            ZTest Always Cull Off ZWrite Off
            
            HLSLPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            
            // 声明纹理
            TEXTURE2D(_MainTex);
            // 声明采样器
            SAMPLER(sampler_MainTex);
            
            struct a2v
            {
                float4 vertex: POSITION;                
                float4 texcoord: TEXCOORD0;
            };
            
            struct v2f
            {
                float4 pos: SV_POSITION;
                half2 uv: TEXCOORD0;
            };
            
            v2f vert(a2v v)
            {
                v2f o;
                
                o.pos = TransformObjectToHClip(v.vertex.xyz);
                
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                
                return o;
            }
            
            half4 frag(v2f i): SV_Target
            {
                // 纹理采样
                half4 renderTex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
                
                // 调整亮度 = 原颜色 * 亮度值
                half3 finalColor = renderTex.rgb * _Brightness;
                
                // 调整饱和度
                // 亮度值(饱和度为0的颜色) = 每个颜色分量 * 特定系数
                half luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
                half3 luminanceColor = half3(luminance, luminance, luminance);
                // 插值亮度值和原图
                finalColor = lerp(luminanceColor, finalColor, _Saturation);
                
                // 调整对比度
                // 对比度为0的颜色
                half3 avgColor = half3(0.5, 0.5, 0.5);
                finalColor = lerp(avgColor, finalColor, _Contrast);
                
                return half4(finalColor, renderTex.a);
            }            
            ENDHLSL            
        }
    }
    
    Fallback Off
}

这部分我就真没什么好说的了,要什么效果写什么Shader,记得拖到AdditionalPostProcessData对象中就行。

实操部分

  1. 在工程面板中右键创建我们的Shader引用对象(不知道在哪个位置,看上节的截图),然后拖动赋值对应Shader

  2. 在URP管线配置对象中添加我们的扩展功能组件,然后再组件中赋值上一步创建Shader引用对象

  3. 在场景中田间全局Volume组件,然后再组件中新建一个Profile对象,接下来添加对应效果的属性参数组件,最后开启后处理效果的开关并调整参数

Licensed under CC BY-NC-SA 4.0
0