Unity 自定义Postprocess BSC明度饱和度对比度

前言

本篇将介绍如何通过添加RenderFeature实现自定义的postprocess——调整屏幕的明度、饱和度、对比度(以下统称BSC)

关于RenderFeature的基础可以看这篇https://www.cnblogs.com/chenglixue/p/17816447.html

Shader

  • Brightness : 很简单乘以render target texture即可
  • Saturation:一个经验公式,饱和度为0:half luminance = 0.2125 * texAlbedo.r + 0.7154 * texAlbedo.g + 0.0721 * texAlbedo.b
  • Contrast:基于half3(0.5, 0.5, 0.5)的颜色值(对比度为0)和render target texture进行lerp
  • 最后对他们依次进行lerp
Shader "Custom/BSC"
{
    Properties
    {
        _MainTex("Main Tex", 2D) = "white"{}
        _Brightness("Brightness", Range(0, 3)) = 1
        _Saturation("Saturation", Range(0, 3)) = 1
        _Contrast("Contrast", Range(0, 3)) = 1
    }
    
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
        }
        
        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

        CBUFFER_START(UnityPerMaterial)
        half4 _MainTex_ST;
        half _Brightness;
        half _Saturation;
        half _Contrast;
        CBUFFER_END
        
        TEXTURE2D(_MainTex);
        SAMPLER(sampler_MainTex);
        
        ENDHLSL

        Pass
        {
            ZTest Always 
            Cull Off
            ZWrite Off
            
            HLSLPROGRAM
            #pragma vertex VS
            #pragma fragment PS

            struct VSInput
            {
                float4 positionL : position;
                float2 uv : TEXCOORD0;
            };

            struct PSInput
            {
                float4 positionH : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            PSInput VS(VSInput vsInput)
            {
                PSInput vsOutput;

                vsOutput.positionH = TransformObjectToHClip(vsInput.positionL);
                vsOutput.uv = TRANSFORM_TEX(vsInput.uv, _MainTex);

                return vsOutput;
            }

            float4 PS(PSInput psInput) : SV_TARGET
            {
                float3 resultColor;

                // Brightness
                half4 texAlbedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv);
                resultColor = texAlbedo * _Brightness;

                // Saturate
                // 饱和度为0 = 对应颜色分量 * 对应特定系数
                half luminance = 0.2125 * texAlbedo.r + 0.7154 * texAlbedo.g + 0.0721 * texAlbedo.b;
                half3 luminanceColor = half3(luminance , luminance, luminance);
                resultColor = lerp(luminanceColor, resultColor, _Saturation);

                // Contrast
                // 对比度为0
                half3 avgColor = half3(0.5, 0.5, 0.5);
                resultColor = lerp(avgColor, resultColor, _Contrast);

                return float4(resultColor, texAlbedo.a);
            }
            ENDHLSL
        }
    }
    Fallback Off
}

RenderFeature

  • 创建自定义RenderFeature,继承ScriptableRendererFeature

    • 创建一个名为Setting的 class,用于存储Pass需要的数据
    • 复写Create(),用于初始化Pass
    • 复写AddRenderPasses(),用于添加Pass
    public class BSCPassFeature : ScriptableRendererFeature
    {
        // render feature 显示内容
        [System.Serializable]
        public class PassSetting
        {
            // 安插位置
            public RenderPassEvent m_passEvent = RenderPassEvent.AfterRenderingTransparents;
            // 控制分辨率
            //[Range(1, 4)] 
            //public int m_sampleWeaken = 1;
    
            // 明度控制
            [Range(0, 3)] 
            public float m_Brightness = 1;
            
            // 饱和度控制
            [Range(0, 3)]
            public float m_Saturation = 1;
            
            // 对比度控制
            [Range(0, 3)]
            public float m_Contrast = 1;
        }
        
        public PassSetting m_Setting = new PassSetting();
        BSCRenderPass m_BSCPass;
        
        /// <inheritdoc/>
        public override void Create()
        {
            m_BSCPass = new BSCRenderPass(m_Setting);
        }
    
        // Here you can inject one or multiple render passes in the renderer.
        // This method is called when setting up the renderer once per-camera.
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            // can queue up multiple passes after each other
            renderer.EnqueuePass(m_BSCPass);
        }
    }
    

Render Pass

  • 创建一个自定义Pass,继承于ScriptableRenderPass

    • 创建一系列数据,这些数据用于后续的实现
    • 构造函数:设置setting,Pass安插位置,以及material Properties
    • OnCameraSetup():获取camera render target和创建临时的渲染纹理,及控制depth buffer的精度
    • Execute():用于定义PASS的实现,后处理实现最重要的部分。将需要执行的pass放置于profiling scope,Blit()用于将源纹理计算后复制到目标渲染纹理,最后执行并释放命令缓冲区
    • OnCameraCleanup():执行完pass后,释放render target占用的内存
    class BSCRenderPass : ScriptableRenderPass
    {
        // profiler tag will show up in frame debugger
        private const string m_ProfilerTag = "BSC Pass";
    
        // 用于存储pass setting
        private BSCPassFeature.PassSetting m_passSetting;
    
        // Render Target Texture and Temp Render Target Texture
        private RenderTargetIdentifier m_TargetBuffer, m_TempBuffer;
        private int m_TempBufferID = Shader.PropertyToID("_TemporaryBuffer");
    
        private Material m_Material;
    
        // int 相较于 string可以获得更好的性能,因为这是预处理的
        private static readonly int m_BrightnessProperty = Shader.PropertyToID("_Brightness");
        private static readonly int m_SaturationProperty = Shader.PropertyToID("_Saturation");
        private static readonly int m_ContrastProperty = Shader.PropertyToID("_Contrast");
    
        // 用于设置material 属性
        public BSCRenderPass(BSCPassFeature.PassSetting passSetting) 
        {
            this.m_passSetting = passSetting;
    
            renderPassEvent = m_passSetting.m_passEvent;
    
            if (m_Material == null) m_Material = CoreUtils.CreateEngineMaterial("Custom/BSC");
    
            // 基于pass setting设置material Properties
            m_Material.SetFloat(m_BrightnessProperty, m_passSetting.m_Brightness);
            m_Material.SetFloat(m_SaturationProperty, m_passSetting.m_Saturation);
            m_Material.SetFloat(m_ContrastProperty, m_passSetting.m_Contrast);
        }
    
        // Gets called by the renderer before executing the pass.
        // Can be used to configure render targets and their clearing state.
        // Can be used to create temporary render target textures.
        // If this method is not overriden, the render pass will render to the active camera render target.
        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            // camera target descriptor will be used when creating a temporary render texture
            RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
    
            // Downsample original camera target descriptor
            //descriptor.width /= m_passSetting.m_sampleWeaken;
            //descriptor.height /= m_passSetting.m_sampleWeaken;
    
            // Set the number of depth bits we need for temporary render texture
            descriptor.depthBufferBits = 0;
    
            // Enable these if pass requires access to the CameraDepthTexture or the CameraNormalsTexture.
            // ConfigureInput(ScriptableRenderPassInput.Depth);
            // ConfigureInput(ScriptableRenderPassInput.Normal);
    
            // Grab the color buffer from the renderer camera color target
            m_TargetBuffer = renderingData.cameraData.renderer.cameraColorTarget;
    
            // Create a temporary render texture using the descriptor from above
            cmd.GetTemporaryRT(m_TempBufferID, descriptor, FilterMode.Bilinear);
            m_TempBuffer = new RenderTargetIdentifier(m_TempBufferID);
    
        }
    
        // The actual execution of the pass. This is where custom rendering occurs
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            // Grab a command buffer. We put the actual execution of the pass inside of a profiling scope
            CommandBuffer cmd = CommandBufferPool.Get();
    
            using (new ProfilingScope(cmd, new ProfilingSampler(m_ProfilerTag)))
            {
                // Blit from the color buffer to a temporary buffer and back
                Blit(cmd, m_TargetBuffer, m_TempBuffer, m_Material, 0);
                Blit(cmd, m_TempBuffer, m_TargetBuffer);
            }
    
            // Execute the command buffer and release it
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    
        // Called when the camera has finished rendering
        // release/cleanup any allocated resources that were created by this pass
        public override void OnCameraCleanup(CommandBuffer cmd)
        {
            if(cmd == null) throw new ArgumentNullException("cmd");
    
            // Since created a temporary render texture in OnCameraSetup, we need to release the memory here to avoid a leak
            cmd.ReleaseTemporaryRT(m_TempBufferID);
        }
    }
    

效果

image-20231108144613779

实现前

image-20231108144650630实现后
image-20231108144636312

posted @ 2023-11-08 14:49  爱莉希雅  阅读(16)  评论(0编辑  收藏  举报