前言
本篇将介绍如何通过添加RenderFeature实现自定义的postprocess——KawaseBlur
关于RenderFeature的基础可以看这篇https://www.cnblogs.com/chenglixue/p/17816447.html
KawaseBlur介绍
- 因为毛神对于十大模糊算法的介绍已经整理得十分详细了,所以这里不会深入,但会大致讲讲它的思想
- 思想:对距离目标pixel越来越远的地方的四个点进行sample,且在两个Render Target Texture间进行Blit。与高斯模糊不同的是,KawaseBlur采样随循环次数变化的Blur Kernel
随循环次数增加,Blur Kernel逐渐变大
Shader
-
Shader很简单,重点在于Render Pass中的循环迭代
-
实现
Shader "Custom/PP_KawaseBlur" { Properties { _MainTex("Main Tex", 2D) = "white" {} _BlurIntensity("Blur Intensity", Range(0, 10)) = 1 } SubShader { Tags { "RenderPipeline" = "UniversalPipeline" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) half _BlurIntensity; float4 _MainTex_TexelSize; CBUFFER_END TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); ENDHLSL Pass { Cull Off ZWrite Off ZTest Always HLSLPROGRAM #pragma vertex VS #pragma fragment PS struct VSInput { float4 postionL : POSITION; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float2 uv : TEXCOORD0; }; PSInput VS(VSInput vsInput) { PSInput vsOutput; vsOutput.positionH = TransformObjectToHClip(vsInput.postionL); vsOutput.uv = vsInput.uv; return vsOutput; } float4 PS(PSInput psInput) : SV_TARGET { float4 outputColor = 0; // 经典卷积 outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv); outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv + float2(-1, -1) * _MainTex_TexelSize.xy * _BlurIntensity); outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv + float2(-1, 1) * _MainTex_TexelSize.xy * _BlurIntensity); outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv + float2(1, -1) * _MainTex_TexelSize.xy * _BlurIntensity); outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv + float2(1, 1) * _MainTex_TexelSize.xy * _BlurIntensity); return outputColor / 5.f; } ENDHLSL } } }
Render Feature
-
整体实现
public class KawaseBlurRenderFeature : ScriptableRendererFeature { // render feature Inspector 显示内容 [System.Serializable] public class PassSetting { // profiler tag will show up in frame debugger public readonly string m_ProfilerTag = "Kawase Blur Pass"; // 安插位置 public RenderPassEvent m_passEvent = RenderPassEvent.AfterRenderingTransparents; // 控制分辨率 [Range(1, 5)] public int m_sampleWeaken = 1; [Range(0, 5)] public int m_PassLoop = 2; // 模糊强度 [Range(0, 10)] public float m_BlurIntensity = 5; } public PassSetting m_Setting = new PassSetting(); KawaseBlurRenderPass m_KawaseBlurPass; // 初始化 Pass public override void Create() { m_KawaseBlurPass = new KawaseBlurRenderPass(m_Setting); } // 添加Pass // 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_KawaseBlurPass); } }
-
PassSetting:创建一个PassSetting class,用于存储后续计算必要的变量
-
Create():初始化Pass
-
AddRenderPasses():添加一个或多个Pass
Render Pass
-
整体实现
class KawaseBlurRenderPass : ScriptableRenderPass { // 用于存储pass setting private KawaseBlurRenderFeature.PassSetting m_passSetting; // render target texture private RenderTargetIdentifier m_TargetBuffer, m_TempBuffer1, m_TempBuffer2; private Material m_Material; static class ShaderIDs { // int 相较于 string可以获得更好的性能,因为这是预处理的 internal static readonly int m_BlurIntensityProperty = Shader.PropertyToID("_BlurIntensity"); // 关于这里的_BufferRT,应该是shader自带的内容,笔者google也未查到,还望知道的大佬指点迷津 internal static readonly int m_TempBufferRT1Property = Shader.PropertyToID("_BufferRT1"); internal static readonly int m_TempBufferRT2Property = Shader.PropertyToID("_BufferRT2"); } // 用于设置material 属性 public KawaseBlurRenderPass(KawaseBlurRenderFeature.PassSetting passSetting) { this.m_passSetting = passSetting; renderPassEvent = m_passSetting.m_passEvent; if (m_Material == null) m_Material = CoreUtils.CreateEngineMaterial("Custom/PP_KawaseBlur"); // 基于pass setting设置material Properties m_Material.SetFloat(ShaderIDs.m_BlurIntensityProperty, m_passSetting.m_BlurIntensity); } // 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(ShaderIDs.m_TempBufferRT1Property, descriptor, FilterMode.Bilinear); cmd.GetTemporaryRT(ShaderIDs.m_TempBufferRT2Property, descriptor, FilterMode.Bilinear); m_TempBuffer1 = new RenderTargetIdentifier(ShaderIDs.m_TempBufferRT1Property); m_TempBuffer2 = new RenderTargetIdentifier(ShaderIDs.m_TempBufferRT2Property); } // 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_passSetting.m_ProfilerTag))) { // Blit from the color buffer to a temporary buffer and back Blit(cmd, m_TargetBuffer, m_TempBuffer1, m_Material, 0); for (uint i = 1; i <= m_passSetting.m_PassLoop; ++i) { // 增大Blur Kernel.从而影响shader cmd.SetGlobalFloat(ShaderIDs.m_BlurIntensityProperty, i * m_passSetting.m_BlurIntensity + 1); Blit(cmd, m_TempBuffer1, m_TempBuffer2, m_Material, 0); // 交换 tempbuffer1 和 tempbuffer2,使得下个循环继续使用tempbuffer1进行计算 var tempRT = m_TempBuffer1; m_TempBuffer1 = m_TempBuffer2; m_TempBuffer2 = tempRT; } // 最后将最终的模糊图像传给要输出的Render Target cmd.SetGlobalFloat(ShaderIDs.m_BlurIntensityProperty, m_passSetting.m_PassLoop * m_passSetting.m_BlurIntensity + 1); Blit(cmd, m_TempBuffer1, m_TargetBuffer, m_Material, 0); } // 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(ShaderIDs.m_TempBufferRT1Property); cmd.ReleaseTemporaryRT(ShaderIDs.m_TempBufferRT2Property); } }
-
ShaderIDs:创建一个ShaderIDs class, 用于存储计算shader properities后的int ID值
-
OnCameraSetup():准备render target相关信息
-
Execute():Render Pass的灵魂,主要逻辑都在这里实现
-
OnCameraCleanup():执行完pass后,释放render target占用的内存
效果
-
模糊前
-
模糊后
reference
https://zhuanlan.zhihu.com/p/125744132
标签:自定义,render,descriptor,Unity,passSetting,Pass,public,Kawase,MainTex From: https://www.cnblogs.com/chenglixue/p/17817958.html