Unity URP14.0 自定义后处理框架

这篇具有很好参考价值的文章主要介绍了Unity URP14.0 自定义后处理框架。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

碎碎念

额,最近在看一些关于URP的东西,挺难入门的这个东西,因为本身版本就迭代得非常快,一些代码通常你才刚接触到就已经弃用了,就很尴尬。但是新的api教程又少(微笑.jpg)。

这次得自定义后处理框架也是知乎的大神放出来的,我这边放出的代码就是简单当成我个人的学习记录,还是有一些不懂的地方,放出链接:Unity URP14.0 自定义后处理系统。

  • 这个教程应该适合刚入门的新手,但是又得对URP和Render Feature有点了解的人。
  • 给出的代码大部分都有注释。

一些基础

这个自定义框架总体分成五个部分。
分别是:

  • 后处理基类CustomPostProcessing.cs;
  • 我们自定义的Render Feature——CustomPostProcessingFeature.cs;
  • 还有Render Pass——CustomPostProcessingPass.cs。
  • 我们的后处理效果shader。
  • 最后就是我们自定义的,继承于CustomPostProcessing.cs的具体后处理类。这个类就是我们自由发挥了,通常需要跟我们的shader联系起来。

如果大家之前自己写过简单的render feature的话,通常从unity面板上创建的Render Feature都会自动创建一个CustomRenderPass,也就是类中类,但是这位大神把它们拆开了。

先来了解一下Render Feature的简单框架吧(里面有些api在urp14里是弃用的):

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class MyFirstRenderFeatureTest : ScriptableRendererFeature
{   
    // static Material blitMaterial = new Material(blitShader);
    /// <summary>
    /// 这个类是Render Feature中的主要组成部分,也就是一个render pass
    /// </summary> <summary>
    /// 
    /// </summary>
    class CustomRenderPass : ScriptableRenderPass
    {
        //找到场景中的shader中的texture,并获取该贴图的id
        static string rt_name = "_ExampleRT";
        static int rt_ID = Shader.PropertyToID(rt_name);

        static string blitShader_Name = "URP/BlitShader";
        static Shader blitShader = Shader.Find(blitShader_Name);
        static Material blitMat = new Material(blitShader);
        /// <summary>
        /// 帮助Excete() 提前准备它需要的RenderTexture或者其他变量
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="renderingData"></param>
        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            // 存储render texture一些格式标准的数据结构
             RenderTextureDescriptor descriptor = new RenderTextureDescriptor(1920, 1080,RenderTextureFormat.Default, 0);
             // 然后创建一个临时的render texture的缓存/空间
             cmd.GetTemporaryRT(rt_ID, descriptor);

            // 想画其他东西到rt上的话就需要下面这句
             ConfigureTarget(rt_ID);
             ConfigureClear(ClearFlag.Color, Color.black);
        }

        /// <summary>
        /// 实现这个render pass做什么事情
        /// </summary>
        /// <param name="context"></param>
        /// <param name="renderingData"></param>
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            // 到命令缓存池中get一个
            CommandBuffer cmd = CommandBufferPool.Get("tmpCmd");
            cmd.Blit(renderingData.cameraData.renderer.cameraColorTarget, rt_ID, blitMat);       //添加一个命令:将像素数据从A复制到B
            context.ExecuteCommandBuffer(cmd);      //因为是自己创建的cmd,所以需要手动地将renderingData提交到context里去
            cmd.Clear();
            cmd.Release();
        }

        /// <summary>
        /// 释放在OnCameraSetup() 里声明的变量,尤其是Temporary Render Texture
        /// </summary>
        /// <param name="cmd"></param>
        public override void OnCameraCleanup(CommandBuffer cmd)
        {
            cmd.ReleaseTemporaryRT(rt_ID);
        }
    }

    /// <summary>
    /// 声明一个render pass的变量
    /// </summary>
    CustomRenderPass m_ScriptablePass;

    /// <summary>
    /// 这个方法是render feature中用来给上面声明的render pass赋值,并决定这个render pass什么使用会被调用(不一定每帧都被执行)
    /// </summary>
    public override void Create()
    {
        m_ScriptablePass = new CustomRenderPass();

        // renderPassEvent定义什么时候去执行m_ScriptablePass 这个render pass
        m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
    }

    /// <summary>
    /// 将create函数里实例化的render pass加入到渲染管线中(每帧都执行)
    /// </summary>
    /// <param name="renderer"></param>
    /// <param name="renderingData"></param>
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(m_ScriptablePass);
    }
}

可以看到这个脚本的最外层是MyFirstRenderFeatureTest类,继承于ScriptableRendererFeature类。

  • MyFirstRenderFeatureTest类包含一个CustomRenderPass类和该pass的变量。
    • CustomRenderPass类则继承于ScriptableRenderPass类,他有OnCameraSetup、Execute、OnCameraCleanup三个抽象方法,这在pass类中必须实现,具体解释看代码。
  • 包含Create方法,这个方法是render feature中用来给声明的render pass赋值,并决定这个render pass什么使用会被调用(不一定每帧都被执行)。
  • 包含AddRenderPasses方法,这个方法将create函数里实例化的render pass加入到渲染管线中(每帧都执行)。

CustomPostProcessing.cs

是所有我们自定义后处理的基类,也就说如果你要创建自己的后处理cs脚本,就需要继承这个脚本。

因为默认的unity后处理是不支持拓展的,但是我们这里这个CustomPostProcessing.cs就为了能实现拓展做了一些操作,也就是继承VolumeComponent类和IPostProcessComponent类,这两个类能将我们自己写的后处理添加到unity的那个全局后处理上的基础。额,当然,这只是第一步,还没那么快呢。
unity custom urp,Unity URP,unity,游戏引擎

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

//后处理效果的注入点,这里先分四个
public enum CustomPostProcessingInjectionPoint{
    AfterOpaque,
    AfterSkybox,
    BeforePostProcess,
    AfterPostProcess
}

//这个类其实是自定义后处理的基类,应该改名为 MyVolumePostProcessing 比较合适
//自定义后处理的基类 (由于渲染时会生成临时RT,所以还需要继承IDisposable)
public abstract class CustomPostProcessing : VolumeComponent, IPostProcessComponent, IDisposable
{
    //注入点
    public virtual CustomPostProcessingInjectionPoint InjectionPoint => CustomPostProcessingInjectionPoint.AfterPostProcess;

    //在注入点的顺序
    public virtual int OrderInInjectionPoint => 0;    

    #region IPostProcessComponent
    //用来返回当前后处理是否active
    public abstract bool IsActive();
    
    //不知道用来干嘛的,但Bloom.cs里get值false,抄下来就行了
    public virtual bool IsTileCompatible() => false;

    //配置当前后处理
    public abstract void Setup();

    // 当相机初始化时执行(自己取的函数名,跟renderfeature里的OnCameraSetup没什么关系其实)
    public virtual void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData){

    }
    #endregion    
    
    //执行渲染
    public abstract void Render(CommandBuffer cmd, ref RenderingData renderingData, RTHandle source, RTHandle destination);

    #region IDisposable
    public void Dispose(){
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public virtual void Dispose(bool disposing){

    }
    #endregion    
}

在这个类里我们需要定义一些属性和方法,这是为了我们后面的后处理做准备。

上面还定义了一个枚举类:CustomPostProcessingInjectionPoint。这个枚举是简单的列出我们想要在渲染管线的哪个阶段插入我们的后处理,这里就先简单地列了四个阶段。

这个注入点的顺序我其实还没太明白。

CustomPostProcessingFeature.cs

这个脚本就是我们心心念念的Render Feature。主要的作用是创建并初始化render pass。

这里我们定义了一个列表mCustomPostProcessings,这个列表是存我们所有的后处理类的实例的列表,我们之后要得到这些实例里指定注入点的实例,比如说我们要得到AfterOpaque, AfterSkybox, BeforePostProcess, AfterPostProcess这四个注入点的AfterOpaque注入点的实例。然后我们就能为它创建pass。

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

//自定义Render Feature,实现后会自动在RenderFeature面板上可供添加
public class CustomPostProcessingFeature : ScriptableRendererFeature
{    
    private CustomPostProcessingPass mAfterOpaquePass;
    private CustomPostProcessingPass mAfterSkyboxPass;
    private CustomPostProcessingPass mBeforePostProcessPass;
    private CustomPostProcessingPass mAfterPostProcessPass;

    //所有后处理基类列表
    private List<CustomPostProcessing> mCustomPostProcessings;    

    //最重要的方法,用来生成RenderPass
    //获取所有CustomPostProcessing实例,并且根据插入点顺序,放入到对应Render Pass中,并且指定Pass Event
    public override void Create()
    {
        //获取VolumeStack
        var stack = VolumeManager.instance.stack;

        //获取所有的CustomPostProcessing实例
        mCustomPostProcessings = VolumeManager.instance.baseComponentTypeArray
            .Where(t => t.IsSubclassOf(typeof(CustomPostProcessing)))  //筛选出VolumeComponent派生类类型中所有的CustomPostProcessing类型元素,不论是否在Volume中,不论是否激活
            .Select(t => stack.GetComponent(t) as CustomPostProcessing) //将类型元素转化为实例
            .ToList();  //转化为List

        #region 初始化不同插入点的render pass

        #region 初始化在不透明物体渲染之后的pass
        //找到在不透明物后渲染的CustomPostProcessing
        var afterOpaqueCPPs = mCustomPostProcessings
            .Where(c => c.InjectionPoint == CustomPostProcessingInjectionPoint.AfterOpaque)   // 筛选出所有CustomPostProcessing类中注入点为透明物体和天空后的实例
            .OrderBy(c => c.OrderInInjectionPoint)  //按顺序排序
            .ToList();  //转化为List

        // 创建CustomPostProcessingPass类
        mAfterOpaquePass = new CustomPostProcessingPass("Custom Post-Process after Opaque", afterOpaqueCPPs);
        //设置pass执行时间
        mAfterOpaquePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
        #endregion

        #region 初始化在透明物体和天空渲染后的pass
        var afterTransAndSkyboxCPPs = mCustomPostProcessings
            .Where(c => c.InjectionPoint == CustomPostProcessingInjectionPoint.AfterSkybox)
            .OrderBy(c => c.OrderInInjectionPoint)
            .ToList();

        mAfterSkyboxPass = new CustomPostProcessingPass("Custom Post-Process after transparent and skybox", afterTransAndSkyboxCPPs);
        mAfterSkyboxPass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox;
        #endregion

        #region 初始化在后处理效果渲染之前的pass
        var beforePostProcessCPPs = mCustomPostProcessings
            .Where(c => c.InjectionPoint == CustomPostProcessingInjectionPoint.BeforePostProcess)
            .OrderBy(c => c.OrderInInjectionPoint)
            .ToList();

        mBeforePostProcessPass = new CustomPostProcessingPass("Custom Post-Process before PostProcess", beforePostProcessCPPs);
        mBeforePostProcessPass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
        #endregion

        #region 初始化在后处理效果渲染之后的pass
        var afterPostProcessCPPs = mCustomPostProcessings
            .Where(c => c.InjectionPoint == CustomPostProcessingInjectionPoint.AfterPostProcess)
            .OrderBy(c => c.OrderInInjectionPoint)
            .ToList();

        mAfterPostProcessPass = new CustomPostProcessingPass("Custom Post-Process after PostProcess", afterPostProcessCPPs);
        mAfterPostProcessPass.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
        #endregion

        #endregion
    }

    // 当为每个摄像机设置一个渲染器时,调用此方法
    // 将不同注入点的RenderPass注入到renderer中(添加Pass到渲染队列)
    //网上有些资料在这个函数里配置RenderPass的源RT和目标RT,具体来说使用类似RenderPass.Setup(renderer.cameraColorTargetHandle, renderer.cameraColorTargetHandle)的方式.
    //但是这在URP14.0中会报错,提示renderer.cameraColorTargetHandle只能在ScriptableRenderPass子类里调用。具体细节可以查看最后的参考连接。
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //当前渲染的游戏相机支持后处理
        if(renderingData.cameraData.postProcessEnabled){
            //为每个render pass设置RT
            //并且将pass列表加到renderer中
            if(mAfterOpaquePass.SetupCustomPostProcessing()){
                mAfterOpaquePass.ConfigureInput(ScriptableRenderPassInput.Color);
                renderer.EnqueuePass(mAfterOpaquePass);
            }

            if(mAfterSkyboxPass.SetupCustomPostProcessing()){
                mAfterSkyboxPass.ConfigureInput(ScriptableRenderPassInput.Color);
                renderer.EnqueuePass(mAfterSkyboxPass);
            }

            if(mBeforePostProcessPass.SetupCustomPostProcessing()){
                mBeforePostProcessPass.ConfigureInput(ScriptableRenderPassInput.Color);
                renderer.EnqueuePass(mBeforePostProcessPass);
            }
            
            if(mAfterPostProcessPass.SetupCustomPostProcessing()){
                mAfterPostProcessPass.ConfigureInput(ScriptableRenderPassInput.Color);
                renderer.EnqueuePass(mAfterPostProcessPass);
            }
        }
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        //mAfterSkyboxPass.Dispose();
        //mBeforePostProcessPass.Dispose();
        //mAfterPostProcessPass.Dispose();

        if(disposing && mCustomPostProcessings != null){
            foreach(var item in mCustomPostProcessings){
                item.Dispose();
            }
        }
    }
}

CustomPostProcessingPass.cs

最后就是我们的render pass类。它同样也需要创建一个自定义后处理的列表。为了我们后面获取到当前已激活的后处理。根据我们当前已激活的后处理组件的数量,我们就能决定我们需要添加的pass数量。
比如下图框出来的两个阶段就是我们自己后处理加上的:
unity custom urp,Unity URP,unity,游戏引擎

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomPostProcessingPass : ScriptableRenderPass
{        
    //所有自定义后处理基类 列表
    private List<CustomPostProcessing> mCustomPostProcessings;

    //当前active组件下标
    private List<int> mActiveCustomPostProcessingIndex;

    //每个组件对应的ProfilingSampler,就是frameDebug上显示的
    private string mProfilerTag;
    private List<ProfilingSampler> mProfilingSamplers;

    //声明
    private RTHandle mSourceRT;
    private RTHandle mDesRT;
    private RTHandle mTempRT0;
    private RTHandle mTempRT1;

    private string mTempRT0Name => "_TemporaryRenderTexture0";
    private string mTempRT1Name => "_TemporaryRenderTexture1";

    //Pass的构造方法,参数都由Feature传入
    public CustomPostProcessingPass(string profilerTag, List<CustomPostProcessing> customPostProcessings){
        mProfilerTag = profilerTag;     //这个profilerTag就是在frame dbugger中我们自己额外创建的渲染通道的名字        
        mCustomPostProcessings = customPostProcessings;
        mActiveCustomPostProcessingIndex = new List<int>(customPostProcessings.Count);
        //将自定义后处理对象列表转换成一个性能采样器对象列表
        mProfilingSamplers = customPostProcessings.Select(c => new ProfilingSampler(c.ToString())).ToList();

        //在URP14.0(或者在这之前)中,抛弃了原有RenderTargetHandle,而通通使用RTHandle。原来的Init也变成了RTHandles.Alloc
        //mTempRT0 = RTHandles.Alloc(mTempRT0Name, name:mTempRT0Name);
        //mTempRT1 = RTHandles.Alloc(mTempRT1Name, name:mTempRT1Name);
    }

    // 相机初始化时执行
    public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData){
        var descriptor = renderingData.cameraData.cameraTargetDescriptor;
        descriptor.msaaSamples = 1;
        descriptor.depthBufferBits = 0;

        //分配临时纹理  TODO:还有疑问,关于_CameraColorAttachmentA和_CameraColorAttachmentB
        RenderingUtils.ReAllocateIfNeeded(ref mTempRT0, descriptor, name:mTempRT0Name);
        RenderingUtils.ReAllocateIfNeeded(ref mTempRT1, descriptor, name:mTempRT1Name);

        foreach(var i in mActiveCustomPostProcessingIndex){
            mCustomPostProcessings[i].OnCameraSetup(cmd, ref renderingData);
        }
    }          

    //获取active的CPPs下标,并返回是否存在有效组件
    public bool SetupCustomPostProcessing(){
        mActiveCustomPostProcessingIndex.Clear();  //mActiveCustomPostProcessingIndex的数量和mCustomPostProcessings.Count是相等的
        for(int i = 0; i < mCustomPostProcessings.Count; i++){
            mCustomPostProcessings[i].Setup();
            if(mCustomPostProcessings[i].IsActive()){
                mActiveCustomPostProcessingIndex.Add(i);
            }
        }
        return (mActiveCustomPostProcessingIndex.Count != 0);
    }      
        
    //实现渲染逻辑
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        //初始化CommandBuffer
        CommandBuffer cmd = CommandBufferPool.Get(mProfilerTag);
        context.ExecuteCommandBuffer(cmd);
        cmd.Clear();

        //获取相机Descriptor
        var descriptor = renderingData.cameraData.cameraTargetDescriptor;
        descriptor.msaaSamples = 1;
        descriptor.depthBufferBits = 0;

        //初始化临时RT
        bool rt1Used = false;        

        //1.声明temp0临时纹理
        //cmd.GetTemporaryRT(Shader.PropertyToID(mTempRT0.name), descriptor);
        // mTempRT0 = RTHandles.Alloc(mTempRT0.name);
        RenderingUtils.ReAllocateIfNeeded(ref mTempRT0, descriptor, name:mTempRT0Name);

        //2.设置源和目标RT为本次渲染的RT,在Execute里进行,特殊处理 后处理 后注入点
        mDesRT = renderingData.cameraData.renderer.cameraColorTargetHandle;
        mSourceRT = renderingData.cameraData.renderer.cameraColorTargetHandle;

        //执行每个组件的Render方法
        //3.如果只有一个后处理效果,则直接将这个后处理效果从mSourceRT渲染到mTempRT0(临时纹理)
        if(mActiveCustomPostProcessingIndex.Count == 1){   
            int index = mActiveCustomPostProcessingIndex[0];
            using(new ProfilingScope(cmd, mProfilingSamplers[index])){
                mCustomPostProcessings[index].Render(cmd, ref renderingData,mSourceRT, mTempRT0);
            }
        }
        else{
            //如果有多个组件,则在两个RT上来回blit。由于每次循环结束交换它们,所以最终纹理依然存在mTempRT0
            RenderingUtils.ReAllocateIfNeeded(ref mTempRT1, descriptor, name:mTempRT1Name);
            //Blitter.BlitCameraTexture(cmd, mSourceRT, mTempRT0);
            rt1Used = true;
            Blit(cmd, mSourceRT, mTempRT0);
            for(int i = 0; i < mActiveCustomPostProcessingIndex.Count; i++){
                int index = mActiveCustomPostProcessingIndex[i];
                var customProcessing = mCustomPostProcessings[index];
                using(new ProfilingScope(cmd, mProfilingSamplers[index])){
                    customProcessing.Render(cmd, ref renderingData, mTempRT0, mTempRT1);
                }

                CoreUtils.Swap(ref mTempRT0, ref mTempRT1);
            }
        }
        Blitter.BlitCameraTexture(cmd,mTempRT0, mDesRT);

        //释放
        cmd.ReleaseTemporaryRT(Shader.PropertyToID(mTempRT0.name));
        if(rt1Used){
            cmd.ReleaseTemporaryRT(Shader.PropertyToID(mTempRT1.name));
        }

        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }
    
    //相机清除时执行
    public override void OnCameraCleanup(CommandBuffer cmd)
    {
        mDesRT = null;
        mSourceRT = null;
    }    

    public void Dispose(){
        mTempRT0?.Release();
        mTempRT1?.Release();
    }
}

render pass主要也是分成几个部分:

  • 构造函数,构造pass,参数都由之前的render feature传入。
  • OnCameraSetup函数,相机初始化时执行的函数,我们在这个函数里初始化一些东西。这在那里面打的一个注释TODO关于_CameraColorAttachmentA和_CameraColorAttachmentB,其实是原来的那位大大说如果我们把自定义后处理的注入点设为AfterPostProcessing之后,就会多出来一个Final Blit阶段,这个阶段的输入源RT为_CameraColorAttachmentB而不是_CameraColorAttachmentA。具体怎么操作的我没太明白。
  • Execute函数,实现渲染逻辑,也就是我们要在这个render pass中做什么事情。搬一下原文的解释:
    1. 声明临时纹理
    2. 设置源渲染纹理mSourceRT目标渲染纹理mDesRT为渲染数据的相机颜色目标处理。(区分有无finalBlit)
    3. 如果只有一个后处理效果,则直接将这个后处理效果从mSourceRT渲染到mTempRT0。
    4. 如果有多个后处理效果,则逐后处理的在mTempRT0和mTempRT1之间渲染。由于每次循环结束交换它们,所以最终纹理依然存在mTempRT0。
    5. 使用Blitter.BlitCameraTexture函数将mTempRT0中的结果复制到目标渲染纹理mDesRT中。
  • OnCameraCleanup函数,相机清除时执行。
  • Dispose函数,释放资源。

在写完render feature和render pass后就能上unity面板上挂载了。如果没有Universal Render Pipeline Asset就需要自己创建了。

unity custom urp,Unity URP,unity,游戏引擎
unity custom urp,Unity URP,unity,游戏引擎
如果没有就创建:
unity custom urp,Unity URP,unity,游戏引擎

例子:BSC

后处理shader(BSC)

Shader "URP/12_BrightnessSaturationAndContrast"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Brightness("Brightness",Float)=1.5
        _Saturation("Saturation", Float) = 1.5
        _Contrast("Contrast", Float) = 1.5
    }
    SubShader
    {
        Tags {
        "RenderPipeline" = "UniversalRenderPipeline"
        "RenderType"="Opaque" 
        }        

        //基本是后处理shader的必备设置,放置场景中的透明物体渲染错误
        //注意进行该设置后,shader将在完成透明物体的渲染后起作用,即RenderPassEvent.AfterRenderingTransparents后
        ZTest Always
        Cull Off
        ZWrite Off

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
        
        CBUFFER_START(UnityPerMaterial)
        float4 _MainTex_ST;
        half _Brightness;
        half _Saturation;
        half _Contrast;
        CBUFFER_END

        // 下面两句类似于 sampler2D _MainTex;
        TEXTURE2D(_MainTex);
        SAMPLER(sampler_MainTex);

        struct a2v{
            float4 positionOS:POSITION;            
            float2 texcoord:TEXCOORD;
        };

        struct v2f{
            float4 positionCS:SV_POSITION;
            float2 texcoord:TEXCOORD;
        };
        ENDHLSL

        Pass
        {
            Name "BSC_Pass"
            Tags{
                "LightMode" = "UniversalForward"
            }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
         
            v2f vert (a2v v)
            {
                v2f o;
                //o.vertex = UnityObjectToClipPos(v.vertex);
                o.positionCS = TransformObjectToHClip(v.positionOS.xyz);    // 类似于上面那句
                o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);                
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                // sample the texture
                //fixed4 col = tex2D(_MainTex, i.uv);
                half4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);   //SAMPLE_TEXTURE2D类似上面那句采样的tex2D
                
                //应用亮度,亮度的调整非常简单,只需要把原颜色乘以亮度系数_Brightness即可
                half3 finalColor = tex.rgb * _Brightness;

                //应用饱和度,通过对每个颜色分量乘以一个特定的系数再相加得到一个饱和度为0的颜色值
                half luminance = 0.2125 * tex.r + 0.7154 * tex.g + 0.0721 * tex.b;
                half3 luminanceColor = half3(luminance,luminance,luminance);
                //用_Saturation属性和上一步得到的颜色之间进行插值
                finalColor = lerp(luminanceColor, finalColor, _Saturation);

                //应用对比度,创建一个对比度为0的颜色值(各分量为0.5)
                half3 avgColor = half3(0.5, 0.5, 0.5);
                //使用_Contrast属性和上一步得到的颜色之间进行插值
                finalColor = lerp(avgColor, finalColor, _Contrast);
                
                return half4(finalColor, 1.0);                
            }
            ENDHLSL
        }
    }
    FallBack "Packages/com.unity.render-pipelines.universal/FallbackError"
}

后处理cs脚本(BSC)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

[VolumeComponentMenu("My Post-Processing/BSC_Blit")]
public class BSC_Blit : CustomPostProcessing
{
    public ClampedFloatParameter brightness = new ClampedFloatParameter(1.5f, 0.0f, 10.0f);
    public ClampedFloatParameter saturation = new ClampedFloatParameter(1.5f, 0.0f, 10.0f);
    public ClampedFloatParameter contrast = new ClampedFloatParameter(1.5f, 0.0f, 10.0f);

    private Material material;
    private const string mShaderName = "URP/12_BrightnessSaturationAndContrast";

    public override CustomPostProcessingInjectionPoint InjectionPoint => CustomPostProcessingInjectionPoint.AfterOpaque;
    public override int OrderInInjectionPoint => 0;

    public override bool IsActive()
    {
        return (material != null);
    }

    //配置当前后处理,创建材质
    public override void Setup()
    {
        if(material == null){
            material = CoreUtils.CreateEngineMaterial(mShaderName);
        }
    }

    //渲染,设置材质的各种参数
    public override void Render(CommandBuffer cmd, ref RenderingData renderingData, RTHandle source, RTHandle destination)
    {
        if(material == null)
        {
            Debug.LogWarning("材质不存在");
            return;
        }

        material.SetFloat("_Brightness", brightness.value);
        material.SetFloat("_Saturation", saturation.value);
        material.SetFloat("_Contrast", contrast.value);

        cmd.Blit(source, destination,material, 0);
    }

    public override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        CoreUtils.Destroy(material);
    }
}

例子:ColorBlit

这个是大大的列子,还加了hlsl文件。

PostProcessing.hlsl

#ifndef POSTPROCESSING_INCLUDED
#define POSTPROCESSING_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);

TEXTURE2D(_CameraDepthTexture);
SAMPLER(sampler_CameraDepthTexture);

struct Attributes {
    float4 positionOS : POSITION;
    float2 uv : TEXCOORD0;
};

struct Varyings {
    float2 uv : TEXCOORD0;
    float4 vertex : SV_POSITION;
    UNITY_VERTEX_OUTPUT_STEREO
};

half4 SampleSourceTexture(float2 uv) {
    return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
}

half4 SampleSourceTexture(Varyings input) {
    return SampleSourceTexture(input.uv);
}

Varyings Vert(Attributes input) {
    Varyings output = (Varyings)0;
    // 分配instance id
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

    VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
    output.vertex = vertexInput.positionCS;
    output.uv = input.uv;

    return output;
}

#endif

ColorBlit2.shader

Shader "URP/ColorBlit2"
{
    Properties {
        // 显式声明出来_MainTex
        [HideInInspector]_MainTex ("Base (RGB)", 2D) = "white" {}
    }
    SubShader {

        Tags {
            "RenderType"="Opaque"
            "RenderPipeline" = "UniversalPipeline"
        }
        LOD 200
        Pass {
            Name "ColorBlitPass"

            HLSLPROGRAM
            #include "PostProcessing.hlsl"
 
            #pragma vertex Vert
            #pragma fragment frag

            float _Intensity;

            half4 frag(Varyings input) : SV_Target {
                float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                return color * float4(0, _Intensity, 0, 1);
            }
            ENDHLSL
        }
    }
}

ColorBlit.cs文件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

[VolumeComponentMenu("My Post-processing/Color Blit2")]
public class ColorBlit : CustomPostProcessing
{
    public ClampedFloatParameter intensity = new ClampedFloatParameter(0.0f, 0.0f, 2.0f);

    private Material material;
    private const string mShaderName = "URP/ColorBlit2";    

    public override bool IsActive()
    {
        return (material != null && intensity.value > 0);
    }

    public override CustomPostProcessingInjectionPoint InjectionPoint => CustomPostProcessingInjectionPoint.BeforePostProcess;
    public override int OrderInInjectionPoint => 0;

    public override void Setup()
    {
        if(material == null){
            material = CoreUtils.CreateEngineMaterial(mShaderName);
        }
    }

    public override void Render(CommandBuffer cmd, ref RenderingData renderingData, RTHandle source, RTHandle destination)
    {
        if(material == null){
            Debug.LogWarning("材质不存在,请检查");
            return;
        }

        material.SetFloat("_Intensity", intensity.value);
        cmd.Blit(source, destination, material, 0);
    }

    public override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        CoreUtils.Destroy(material);
    }
}

其实不太明白这个hlsl文件的意义,因为我自己写的那个BSC是可以用的,如果大家优质的麻烦评论区讲解一下。文章来源地址https://www.toymoban.com/news/detail-859443.html

其他一些参考

  • URP RenderFeature 基础入门教学
  • 猫都能看懂的URP RenderFeature使用及自定义方法
  • 《Unity Shader 入门精要》从Bulit-in 到URP (HLSL)之后处理(Post-processing : RenderFeature + VolumeComponent)
  • Unity URP管线如何截屏,及热扰动(热扭曲)效果的实现
  • 最后放上我个人的github仓库地址,这个仓库是基于Unity Shader入门精要这本书,使用URP14.0实现的:仓库地址 。

到了这里,关于Unity URP14.0 自定义后处理框架的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《Unity的URP项目中使用自定义shader导致材质消失的解决办法》

            在Unity中使用URP时,会有需求使用自定义的一些shader来实现特殊效果,这时如果我们直接使用新建材质与无光照着色器(Unlit shader),可能会发生一个对于新手而言意料之外的问题—— 物体!消失了!         打开你正在使用的的 通用渲染器(Universal Rendere

    2024年02月06日
    浏览(81)
  • 【Unity Shader Graph URP渲染管线下的自定义半透明效果_半透明案例分享】

    URP的渲染管线下 在项目设置里找到“Graphic” 找到URP Asset文件 索引到Renderer List文件——“ForwardRenderer” 在这个“ForwardRenderer”文件里找到“Add Renderer Feature” 添加一个渲染对象,类似下图:Render Object (Experimental) 如图设置,将“Event”设置成 AfterRenderingSkybox ,然后“Layer M

    2024年02月09日
    浏览(59)
  • Unity Shader 学习笔记(4)URP渲染管线带阴影PBR-Shader模板 -- 新增可自定义阴影颜色

    材质面板截图 功能实现(URP渲染管线下): 1、进一步优化Shader结构和算法; 2、包含PBR材质; 3、投射和接收阴影,并升级 支持自定义阴影颜色 ; 4、支持点光源照射(但不支持点光源阴影)。 通用渲染截图 自定义阴影颜色截图 完整代码: 写在最后: 1、在我的上一篇文

    2024年02月12日
    浏览(50)
  • 【Unity URP】URP Camera使用及动态设置

    官方文档:   摄像机 | Universal RP | 12.1.1 https://docs.unity3d.com/cn/Packages/com.unity.render-pipelines.universal@12.1/manual/cameras.html 相机类型: Render Type(渲染类型) Base(基础相机):渲染到特定渲染目标的通用相机,场景中必须至少有一个。 Overlay(叠加相机):将其视图渲染在另一个摄

    2024年02月12日
    浏览(46)
  • Unity 基础之 URP 项目创建\项目转URP Pipline

    目录 Unity 基础之 URP 项目创建项目转URP Pipline 一、简单介绍 二、创建 URP 项目 三、工程项目转 URP Unity中的一些基础知识点,方便日后查阅。 Unity游戏开发中,这里简单介绍如何创建 URP 工程项目,和把已有项目转为 URP 项目的过程,这里做简单记录,如果有不对,欢迎指出。

    2024年02月16日
    浏览(48)
  • Unity——URP相机详解

    2021版本URP项目下的相机,一般新建一个相机有如下组件 1:Render Type( 渲染类型 ) 有Base和Overlay两种选项,默认是Base选项         Base:主相机使用该种渲染方式,负责渲染场景中的主要图形元素         Overlay(叠加):使用了Oveylay的相机会把它渲染出的图形叠加到其他相机之上

    2024年02月05日
    浏览(53)
  • 8、URP自定义屏幕后处理

    回到目录 大家好,我是阿赵。这次来说一下URP渲染管线里面怎样使用后处理效果,还有怎样去自定义后处理效果。 要使用URP自带的后处理效果,方法很简单,和Unity内置渲染管线的PostProcessing后处理很类似的,在一个物体上面添加一个Volume脚本。 这个脚本的整体用法和PostP

    2024年02月06日
    浏览(27)
  • 【Unity URP】手写PBR:从build-in转到URP

    写在前面 后续要在URP下实现PBR+NPR的风格化渲染,所以这里要赶紧把之前手写的PBR挪到URP管线下。由于URP各个版本更新换代太快了,贴一下项目环境,给后面看到这篇文章的小伙伴提个醒,我的项目环境: URP12.1.7 Unity2021.3.8f1 整体框架几乎一样吧,目前先实现主光源的部分,

    2024年02月14日
    浏览(81)
  • Unity URP下阴影锯齿

            在Unity开发的URP项目中出现阴影有明显锯齿。如下图所示:         并且在主光源的Shadow Type已经是Soft Shadows模式了。         阴影出现锯齿说明阴影质量不高,所以要先找到URP Asset文件进行阴影质量参数的设置。 1.打开PlayerSetting找到 Graphics(也可以Quality),然

    2024年01月23日
    浏览(58)
  • Unity URP相机使用简介

    使用URP后,相机组件的面板发生了变化,如下图。接下来简单的介绍下URP下的相机使用 RenderType 有2个选项,Base和Overlay。如下图 Overlay的相机无法单独显示,可以看到Game窗口一片黑 Base类型的相机可以单独显示,把RenderType模式修改为Base,可以看到Game窗口显示出来了 前面说了

    2024年02月10日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包