【Unity/URP学习】建立一个屏幕后处理系统以及实现一个调整亮度、饱和度和对比度的效果

思维导图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

c#代码和shader代码

注释都写得很详细了,这里就不细讲具体过程,直接放代码:

c#代码:

主要是两个脚本的代码:
VolumnComponent模块:

using UnityEngine;
using UnityEngine.Rendering; 
using UnityEngine.Rendering.Universal;
//首先第一步,我们要创建volumnComponent类,这个类命名可以自己定义,这个名字即使后面选择后处理类型的名字    
//只需继承VolumeComponent类并且添加VolumeComponentMenu特性即可,而VolumeComponent本质上是一个ScriptableObject
namespace UnityEngine.Rendering.Universal//在UnityEngine.Rendering.Universal的命名空间下添加代码,是把我们自定义的类加入volumn组件
{
    
    
    [System.Serializable, VolumeComponentMenu("HL/BrightnessSaturationAndContrast")] //把我们自定义的类加入volumn组件,位置地址"HL/BrightnessSaturationAndContrast"
    //下面VolumeComponent类是Volumn组件的抽象父类,里面定义了Volumn的一些方法等,要继承VolumeComponent和IPostProcessComponent才能融入官方的后处理系统,即继承了volumn的框架
    //在继承了Volumn框架后在此框架上自定义一个
    public sealed class BrightnessSaturationAndContrast : VolumeComponent, IPostProcessComponent
    {
    
    
        //下面先定义一个小标签,当鼠标放在EnableEffect上会显示“是否开启效果”
        [Tooltip("是否开启效果")] 
        public BoolParameter enableEffect = new BoolParameter(true);//这里是定义一个bool参数值,BoolParameter(true)是指如果前面的框勾选,那么启用此功能,此时默认值是打勾true

        //下面可以自定义面板上要显示出来的可调整的参数
        public ClampedFloatParameter Brightness = new ClampedFloatParameter(1f, 0, 3);
        public ClampedFloatParameter Saturation = new ClampedFloatParameter(1f, 0, 3);
        public ClampedFloatParameter Contrast = new ClampedFloatParameter(1f, 0, 3); 
        
        
        //下面是实现接口
        public bool IsActive() => enableEffect == true;
        public bool IsTileCompatible() => false;
    }
}    

RenderFearture模块:

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

//第二步是定义我们的ScriptableRendererFeature,即是要定义渲染脚本类,在上一个脚本我们只是写了volumn的一个界面
//下面的自定义类首先要继承ScriptableRendererFeature类,ScriptableRendererFeature类是URP开放给用户来拓展自己自定义渲染功能的一个入口,我们自己新建的类要继承它,然后才可以在新建类上进行操作
//BrightnessSaturationAndContrastRender即是自定义的一个用来渲染的类
//在RendererFeature处理Pass 的属性,设置三个参数,公开属性Shader、私有属性AdditionPostProcessPass对象和材质
//前执行RendererFeature模块—调用Create方法初始化—AddRenderPasses方法(每帧执行)-—调用Pass里Setup方法——调用Pass增加到渲染队列里
public class BrightnessSaturationAndContrastRenderFeature : ScriptableRendererFeature
{
    
    
    [System.Serializable]
    public class Setting
    {
    
    
        public Shader shader;//定义用于后处理计算的shader
        public Material _material = null;//定义用于后处理的材质,初始化为null
        public RenderPassEvent renderpassevent = RenderPassEvent.AfterRenderingTransparents;//可以自定义渲染事件
    }
    
    

    public Setting setting = new Setting();//不要忘记申请新的内存了
    //在这个类里面,我们首先要实例化BrightnessSaturationAndContrastRenderPass类
    public BrightnessSaturationAndContrastRenderPass PostPass;//后处理的Pass,这里只是定义,还没有具体给PostPass分配内存
    
    
    //Create方法中初始化内容,我们定义的Pass初始化到PostPass,再设置渲染层级 
    public override void Create()
    {
    
    
        this.name = "BSC";//初始化名字,是外部显示的名字
        PostPass = new BrightnessSaturationAndContrastRenderPass();
        //设置渲染的层级
        PostPass.renderPassEvent = setting.renderpassevent;//这是设置渲染事件,RenderPassEvent在UnityEngine.Rendering.Universal命名空间有定义,继承于ScriptableRenderPass
    }
    
    //AddRenderPasses传入渲染器脚本类创立的对象和渲染信息
    //AddRenderPasses方法中通过Shader创建材质,然后获取当前渲染结果,然后在把当前渲染的结果和我们的材质传入 Pass中,渲染结果输出
    public override void AddRenderPasses(ScriptableRenderer render, ref RenderingData renderingData)
    {
    
    
        if (!setting.shader) return;//如果没有shader,直接返回
        if (setting._material == null)//没有材质则创建材质
        {
    
    
            setting._material = CoreUtils.CreateEngineMaterial(setting.shader);//CoreUtils是UnityEngine.Rendering里面的一个静态类,可以调用其函数创建一个材质,传入的是shader路径
                                                                               //public static class CoreUtils是静态类
                                                                               //public static Material CreateEngineMaterial(string shaderPath)
        }
        
        //下面要先获取现有的渲染图像
        var source = render.cameraColorTarget;//通过渲染器类对象里面获取的cameraColorTarget,获取表示符
        //调用后处理的PostPass里的setup函数进行初始化,传进当前的渲染图像和材质
        PostPass.Setup(source, setting._material);
        render.EnqueuePass(PostPass);//将后处理Pass加入到渲染队列中
    }
}


//创建一个自定义自定义Pass类,用在上面的BrightnessSaturationAndContrastRenderFeature渲染功能模块,要继承ScriptableRenderPass类
//RenderPass 是计算渲染的内容,RenderFeature模块是输入Shader,调用Pass去执行结果。
//方法主要有3个 Setup(初始化),Execute(执行)和Render(渲染)
public class BrightnessSaturationAndContrastRenderPass : ScriptableRenderPass
{
    
    
    private string k_Tag = "BrightnessSaturationAndContrastRender Effect";//定义一个标签,用于后面将渲染标签作为指令放入CommandBuffer(要执行的图形命令的列表) 
    //在这个自定义的Pass类里面,我们先定义一些下面构造函数需要用到的变量
    public Material m_Material;//定义一个材质变量,用于后处理的材质,cmd.Blit()方法需要调用的参数 
    public BrightnessSaturationAndContrast brightnessSaturationAndContrast;//上一个脚本已经定义了BrightnessSaturationAndContrast类,这里用这个类定义一个对象,对象内容只是定义的类的内容,没有任何修改
                                                                            //使用这个效果来调整最终渲染图像的整体色调、亮度和对比度。属性参数组件
    public RenderTargetIdentifier m_source;//渲染输入的原图!!(原图保存在这里),RenderTargetIdentifier标识CommandBuffer的RenderTexture,创建渲染目标标识符!
    RenderTargetHandle m_TemporaryColorTexture;//临时渲染的结果(临时RT) 
    
    public static readonly int MainTexId = Shader.PropertyToID("_MainTex");//Shader.PropertyToID()获取着色器属性名称的唯一标识符,在 Unity 中,着色器属性的每个名称(例如_MainTex或_Color)均分配有唯一整数
    public static readonly int TemporaryColorTextureId = Shader.PropertyToID("m_TemporaryColorTexture");

    //先进行初始化Setup(),对输入的纹理(即是渲染输入的原图),材质进行初始化 
    public void Setup(RenderTargetIdentifier _Source, Material material)
    {
    
    
        this.m_source = _Source;//定义类成员m_source的初始值为输入的图像
        this.m_Material = material;//定义类成员m_Material的初始值为输入的material
    }
    
    //下面进行执行(里面就用到了Render()渲染函数),定义我们的ScriptableRenderPass,在Override的Execute方法里做我们的具体的后处理
    //Excute是后处理的核心函数,在这里来写后处理的逻辑和图像混合,传入参数是context命令和RenderingData的一个对象
    //定义自定义RenderPipeline时,可使用ScriptableRenderContext向GPU调度和提交状态更新和绘制命令。
    //ref参数的使用:将一个变量传入一个函数中进行"处理","处理"完成后,再将"处理"后的值带出函数。
    //RenderingData是各种渲染信息
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
    
    
        //既然是执行模块,我们首先要判断是否执行
        //如果摄像机关闭了后处理,则直接返回,不进行执行操作,判断要访问渲染信息数据中的摄像机的后处理数据
        if (!renderingData.cameraData.postProcessEnabled) return;
        
        //判断材质是否为空
        if (!m_Material) Debug.LogError("材质初始化失败");
        
        var stack = VolumeManager.instance.stack;//VolumnManager是一个类,这里通过VolumeManager.instance.stack单例获取Volume框架中所有的堆栈,即获取所有的Volumn组件的数据
        brightnessSaturationAndContrast = stack.GetComponent<BrightnessSaturationAndContrast>();//获取我们定义的BrightnessSaturationAndContrast组件信息,已经在外面经过一些设置的
        if (!brightnessSaturationAndContrast) return;//如果组件获取失败就直接返回,到这里获取组件的目的是为了下面执行渲染的时候:1.判断组件是否开启2.用于和材质的shader进行绑定!!!
        
        //下面要通过之前定义的标签来作为指令放入CommandBuffer(要执行的图形命令的列表),以便用这个指令来告诉GPU执行渲染
        //Class CommandBufferPool里面有public static CommandBuffer Get(string name)这个函数,利用此函数就可以得到返回类型为CommandBuffer的变量
        var cmd = CommandBufferPool.Get(k_Tag);//此时cmd相当于一个图形指令列表,但此列表只有一个k_Tagz指令
        Render(cmd, ref renderingData);//这里只是设置渲染的一个函数,还要通过下面的执行才能渲染
        context.ExecuteCommandBuffer(cmd);//传入的是CommandBuffer类型,即图形命令列表,这里是告诉GPU可以执行命令列表中的命令了
        CommandBufferPool.Release(cmd);//最后此列表的命令已经执行完了,就直接释放掉此列表的空间
        
        cmd.ReleaseTemporaryRT(TemporaryColorTextureId);//释放临时的储存RT的纹理图像   
    }
    
    //最后这里要来设置一下渲染函数,Shader和Volume组件的属性是在Render 渲染里关联起来的,使用SetFloat方法,因为我们调整亮度、饱和度、对比度都是调整Float类型!
    
    public void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
    
    
        if (brightnessSaturationAndContrast.IsActive() && !renderingData.cameraData.isSceneViewCamera)
        {
    
    
            //使用SetFloat将到时候写在材质球属性的变量和在自定义volumnComponent类的属性变量联系起来
            m_Material.SetFloat("_Brightness", brightnessSaturationAndContrast.Brightness.value);
            m_Material.SetFloat("_Saturation", brightnessSaturationAndContrast.Saturation.value);
            m_Material.SetFloat("_Contrast", brightnessSaturationAndContrast.Contrast.value);
            
            cmd.SetGlobalTexture(MainTexId, m_source);//这一步是将用来存储当前纹理的m_ColorAttachment设置为_MainTex并赋予其Id,SetGlobalTexture为所有着色器设置全局纹理属性(设置纹理Id、纹理对象)

            //因为我们要通过原来摄像机创建的RT对象进行后处理,需要新建立一个缓冲区用来计算并保存选然后的结果,所以有以下的操作
            //调用cmd里面的GetTemporaryRT()函数来创建的临时缓冲区
            //CommandBuffer.GetTemporaryRT:添加“获取临时渲染纹理”命令。
            //可使用给定参数创建临时渲染纹理,并使用 nameID 将其设置为全局着色器属性。使用Shader.PropertyToID创建整数名称
            cmd.GetTemporaryRT(TemporaryColorTextureId, renderingData.cameraData.camera.scaledPixelWidth, renderingData.cameraData.camera.scaledPixelHeight, 0, FilterMode.Trilinear, RenderTextureFormat.Default); 
           
            cmd.Blit(MainTexId, TemporaryColorTextureId);
            //这里输入为source、destination、material
            //RenderTargetHandle.Identifier()相当于RenderTargetIdentifier,转换为描述的RT的类型
            //最后从缓冲区输出主纹理
            cmd.Blit(TemporaryColorTextureId, m_source, this.m_Material, -1);
        }
    }

}

shader代码:

Shader "URP/BrightnessSaturationAndContrast_shader"
{
    
    
    Properties
    {
    
    
        [HideInInspector]_MainTex ("Texture", 2D) = "white" {
    
    }//用来储存屏幕采样结果
        _Brightness("Brightness", Range(0, 3)) = 1.0
        _Saturation("Saturation", Range(0, 3)) = 1.0
        _Contrast("Contrast", Range(0, 3)) = 1.0
    }
    //通常屏幕后处理的shader只用进行正常的顶点变换,但最重要的是要传递屏幕纹理的uv,以便进行正确的采样
    SubShader
    {
    
    
        Tags {
    
     "RenderPipeline" = "UniversalPipeline"}
        Pass
        {
    
    
            ZTest Always Cull off ZWrite off
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            CBUFFER_START(UnityPerMaterial)
            Texture2D _MainTex;
            float4 _MainTex_ST;
            SamplerState sampler_MainTex;//采样采样设置
            float _Brightness;
            float _Saturation;
            float _Contrast;

            struct appdata
            {
    
    
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
    
    
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            
            CBUFFER_END

            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.uv = v.texcoord;
                o.positionCS = TransformObjectToHClip(v.vertex);
                return o;
            }

            //片元着色器主要进行调整亮度、饱和度和对比度的调整
            half4 frag (v2f i) : SV_Target
            {
    
    
                half4 renderTex = _MainTex.Sample(sampler_MainTex, i.uv);//采样屏幕图像

                //调整屏幕亮度
                half3 finalColor = renderTex.rgb * _Brightness;

                //调整屏幕饱和度
                half luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;//创建一个亮度
                half3 luminanceColor = half3(luminance, luminance, luminance);//用亮度来创建一个饱和度为0的颜色值
                finalColor = lerp(luminanceColor, finalColor, _Saturation);//用平滑函数来进行线性插值,调整饱和度

                //调整对比度
                half3 avgColor = half3(0.5, 0.5, 0.5);//先建立一个对比度为0的颜色(各分量都为0.5)
                finalColor = lerp(avgColor, finalColor, _Contrast);

                return half4(finalColor, renderTex.a);
               
            }
            ENDHLSL
        }
    }
    Fallback off
}

效果

原图

在这里插入图片描述

调整亮度、饱和度和对比度的一些图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Phantom1516/article/details/128681034