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

news2024/12/16 11:37:20

目录

  • 碎碎念
  • 一些基础
  • CustomPostProcessing.cs
  • CustomPostProcessingFeature.cs
  • CustomPostProcessingPass.cs
  • 例子:BSC
    • 后处理shader(BSC)
    • 后处理cs脚本(BSC)
  • 例子:ColorBlit
    • PostProcessing.hlsl
    • ColorBlit2.shader
    • ColorBlit.cs文件
  • 其他一些参考

碎碎念

额,最近在看一些关于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的那个全局后处理上的基础。额,当然,这只是第一步,还没那么快呢。
在这里插入图片描述

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数量。
比如下图框出来的两个阶段就是我们自己后处理加上的:
在这里插入图片描述

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就需要自己创建了。

在这里插入图片描述
在这里插入图片描述
如果没有就创建:
在这里插入图片描述

例子: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是可以用的,如果大家优质的麻烦评论区讲解一下。

其他一些参考

  • URP RenderFeature 基础入门教学
  • 猫都能看懂的URP RenderFeature使用及自定义方法
  • 《Unity Shader 入门精要》从Bulit-in 到URP (HLSL)之后处理(Post-processing : RenderFeature + VolumeComponent)
  • Unity URP管线如何截屏,及热扰动(热扭曲)效果的实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1140668.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快捷键记录

文章目录 ctrlaltashftwinsWinRCtrlc和CtrlvCtrl -Xshell的复制粘贴ctrlalt&#xff08;鼠标跳出&#xff09;ctrl alt T ctrlalta 这是QQ/TIM的屏幕截图快捷键。截图成功后&#xff0c;会有一栏导航&#xff0c;可以对图片进行勾画、模糊、绘画、标号、撤回、翻译、提取文…

早停止(Early Stopping)-PyTorch版本(代码使用教程)

一、说明 记录自己使用早停法来解决过拟合问题的经历。 这里给出的是pytorch版本&#xff0c;需要tensorflow版本的&#xff0c;可以使用chatgpt转换&#xff0c;也可以自己转换。 二、原理 这个早停法的原理就是&#xff0c;对比你每次的验证loss&#xff0c;如果超过20epoch&…

AQS总结

详细分析地址&#xff1a;跳转 head等于NodeA&#xff0c;NodeA指向NodeB&#xff0c;NodeB指向NodeC&#xff0c; tail等于NodeC。 NodeA也就是持有线程的Node&#xff0c;阻塞队列是指NodeA后面的所有队列&#xff0c;NodeA不属于阻塞队列。 公平锁&#xff1a; A线程先进来…

广西厂家直销建筑模板,工程用木工板,多层胶合板批发

欢迎选购我们的产品&#xff1a;广西厂家直销的建筑模板。作为一家专业厂家&#xff0c;我们提供工程用木工板&#xff0c;采用标准尺寸的多层胶合板制作&#xff0c;具备出色的防潮耐磨性能和高周转次数。 1. 工程用途&#xff1a;我们的建筑模板专为各类工程使用而设计&#…

(echarts)雷达图封装相关总结及使用

(echarts)雷达图封装相关总结及使用 数据结构&#xff1a; 页面&#xff1a; <div id"chart4" style"height:420px;width: 100%;" />//雷达图 echartFour(obj) {var chartDom document.getElementById("chart4");var myChart echarts.i…

浙大做了一个可用于AI领域的学术会议问答LLMs

深度学习自然语言处理 原创作者&#xff1a;wkk 今天介绍一篇来自于浙江大学的一项研究&#xff0c;关于基于LLM进行人工智能领域内7个不同学术会议QA数据集的论文。 论文: Reliable Academic Conference Question Answering: A Study Based on Large Language Model地址: http…

网络攻击的发展

在当今数字化时代&#xff0c;网站被攻击已经成为常态&#xff0c;网络威胁愈演愈烈。这些攻击不仅威胁到企业的安全&#xff0c;还可能导致严重的商业危机。本文将探讨为什么网络流量攻击变得如此普遍和容易&#xff0c;并分析未来可能引发的商业危机。 ​ 网络流量攻击的普遍…

【博士每天一篇文献-算法】iCaRL_ Incremental Classifier and Representation Learning

阅读时间&#xff1a;2023-10-27 1 介绍 年份&#xff1a;2017 作者&#xff1a;Sylvestre-Alvise Rebuffi; Alexander Kolesnikov; Georg Sperl; Christoph H. Lampert &#xff0c;牛津大学 期刊&#xff1a;Proceedings of the IEEE conference on Computer Vision and Pa…

竞赛选题 深度学习图像修复算法 - opencv python 机器视觉

文章目录 0 前言2 什么是图像内容填充修复3 原理分析3.1 第一步&#xff1a;将图像理解为一个概率分布的样本3.2 补全图像 3.3 快速生成假图像3.4 生成对抗网络(Generative Adversarial Net, GAN) 的架构3.5 使用G(z)生成伪图像 4 在Tensorflow上构建DCGANs最后 0 前言 &#…

评比无代码低代码平台时,可以考虑以下几个方面

无代码低代码平台是近年来兴起的一种软件开发工具&#xff0c;它们旨在帮助非技术人员快速创建应用程序&#xff0c;而无需编写大量的代码。这些平台通过提供可视化的界面和预先构建的组件&#xff0c;使用户能够通过拖放和配置的方式来构建应用程序。选择无代码低代码平台时&a…

微信公众号点击打开地图导航

<h3 id"menu-location">地理位置接口</h3><span class"desc">使用微信内置地图查看位置接口</span><button class"btn btn_primary" id"openLocation">openLocation</button> <span class&quo…

发展高质量存储力,中国高科技力量聚浪成潮

中国信息通信研究院指出&#xff0c;在全球数字化转型与产业变革的浪潮下&#xff0c;算力正在成为改变全球竞争格局的关键力量。而根据最新的《算力基础设施高质量发展行动计划》&#xff0c;算力是集信息计算力、数据存储力和网络运载力于一体的新型生产力。当前&#xff0c;…

ORACLE-递归查询、树操作

1. 数据准备 -- 测试数据准备 DROP TABLE untifa_test;CREATE TABLE untifa_test(child_id NUMBER(10) NOT NULL, --子idtitle VARCHAR2(50), --标题relation_type VARCHAR(10) --关系,parent_id NUMBER(10) --父id );insert into untifa_test (CHILD_ID, TITLE, RELATION_TYP…

vite vue3 ts 使用sass 设置样式变量 和重置默认样式

1.安装scss 样式支持依赖 yarn add -D sass 2.使用sass <div><!-- 测试使用sass --><h1>测试使用sass</h1> </div><style scope lang"scss"> div {h1 {color: red;} } </style> 效果&#xff1a; 3.通过npm下载并复制…

MarkDown教程记录

什么是 Markdown? Markdown 是一款轻量级标记语言&#xff0c;不同于HTML (Hypertext Markup Language)&#xff0c;Markdown 的语法非常简单&#xff0c;且容易上手Markdown 以 纯文本格式 编写文档&#xff0c;依赖键盘而非鼠标&#xff0c;专注于写作本身&#xff0c;感受…

为什么把k8s比做操作系统:kubernetes与os的架构对比

你还在背八大件吗&#xff1f;不如把k8s的架构和os一起看&#xff0c;你会发现一些超有趣的事情&#xff01;本文旨在将k8s的架构和os做个对比&#xff0c;帮助读者理解为什么k8s要这么设计。 kubernetes架构 kubernetes架构中由master节点和minion节点组成&#xff0c;maste…

c++ deque 的使用

目录 1. deque 的介绍 2. deque 底层原理 3. deque 的迭代器 4. deque 的接口使用 5. deque 和 vector&#xff0c;list 的比较 1. deque 的介绍 下面是 deque 的介绍&#xff0c;来自于&#xff1a;deque - C Reference (cplusplus.com) 的翻译&#xff0c;您可以不用…

MobPush数智化推送,精准定位万圣节狂欢年轻一族

随着中秋十一黄金周的结束&#xff0c;2023年最后一个法定节假日也一去不复返&#xff0c;但是别急&#xff0c;今年还有另一场不放假的狂欢节日——万圣节&#xff0c;万圣节作为西方国家第四季度最为重要的营销节日之一&#xff0c;在国内年轻人群体中同样具有较大的影响力和…

字节流和处理流的对象反序列化问题

细节&#xff1a; 读写要保持一致 序列对象时&#xff0c;默认将里面的所有属性都进行序列化&#xff0c;但除了static或transient修饰的成员 要求序列化或反序列化对象&#xff0c;需要实现Serializable 序列化对象时&#xff0c;要求里面的属性也要实现序列化接口 序列化…

java-- 静态数组

1.静态初始化数组 定义数组的时候直接给数组赋值。 2.静态初始化数组的格式&#xff1a; 注意&#xff1a; 1."数据类型[] 数组名"也可以写成"数据类型 数组名[]"。 2.什么类型的数组只能存放什么类型的数据 3.数组在计算机中的基本原理 当计算机遇到…