前言
本篇将介绍如何通过添加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); } }
效果
实现前
实现后