前言
本篇将介绍如何通过添加RenderFeature实现自定义的postprocess——散景模糊(Bokeh Blur)和景深
关于RenderFeature的基础可以看这篇https://www.cnblogs.com/chenglixue/p/17816447.html
景深
-
定义:聚焦清晰的焦点前后可接受的清晰区域,也就是画面中景象清晰的范围
如下图相机景深原理图所示,DOF为景深范围,S为被拍摄物体的距离,\(D_n\)为近点距离,\(D_F\)为远点距离,DOF左边(\(D_F\) - S)为后景深,DOF右边(S - \(D_n\))为前景深 -
但实际上,在shader可以无需模拟相机的成像,只需根据深度图来确定相机的模糊范围即可
散景模糊
-
定义:落在景深以外的画面,会有逐渐产生松散模糊的效果
下图中带有模糊的甜甜圈形状正是散景模糊的效果
不过这一形状不是固定的,它主要受镜头的光圈叶片数的影响,所形成的光圈孔形状不同 -
同样,我们无需模拟相机的成像,而是通过黄金角度、均匀排布来实现
-
黄金分割(黄金比例):对一整体切一刀,形成一个较大长度a,较小长度b,黄金比例也就是\(\frac{a}{b} = \frac{b}{a + b} ≈ 0.618\)
-
黄金角度:黄金角度也是使用的黄金比例的公式,只不过是对2\(\pi\)进行切割,a为较小角度,b为较大角度,求得a = 137.5°
-
黄金角度有何用处?
其实在大自然界,许多现象都与黄金角度有关:大部分花朵的花瓣以137.5°的角度旋转;植物的果实也以137.5°的角度进行生长
-
-
双螺旋均匀分布
假设现在最中心有一个像素,逐渐向外移动并以黄金角度进行旋转,就会形成如下图所示完美的双螺旋均匀分布
奇特的是,就算角度有1°之差,双螺旋现象就会被打破
-
实现思路:可以看到,上图所示的均匀排布的形状很像最开始所提到的甜甜圈形状,只是这里都是点。但对这些旋转后的均匀排布图像逐渐进行叠加,即可实现散景模糊
Shader
-
实现
Shader "Custom/BokehBlur" { Properties { [HideinInspector] _MainTex("Main Tex", 2D) = "white" {} } SubShader { Tags { "RenderPipeline" = "UniversalRenderPipeline" "RenderType" = "Transparent" } Cull Off ZWrite OFF ZTest Always HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #pragma vertex VS #pragma fragment PS CBUFFER_START(UnityPerMaterial) float4 _MainTex_TexelSize; CBUFFER_END half _FocusDistance; //焦点 half _FarBlurIntensity; //远景模糊强度 half _NearBlurIntensity; // 近景模糊强度 half _BlurLoop; // 散景模糊迭代次数,越大越模糊 half _BlurRadius; // 采样半径,越大甜甜圈越大 TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); TEXTURE2D(_SourTex); // 模糊前的纹理 SAMPLER(sampler_SourTex); SAMPLER(_CameraDepthTexture); // viewport的深度纹理 struct VSInput { float4 positionL : POSITION; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float2 uv : TEXCOORD0; }; ENDHLSL Pass { NAME "Boken Blur" HLSLPROGRAM PSInput VS(VSInput vsInput) { PSInput vsOutput; vsOutput.positionH = TransformObjectToHClip(vsInput.positionL); #ifdef UNITY_UV_STARTS_AT_TOP if(_MainTex_TexelSize.y < 0) vsInput.uv.y = 1 - vsInput.uv.y; #endif vsOutput.uv = vsInput.uv; return vsOutput; } float4 PS(PSInput psInput) : SV_TARGET { float4 outputColor; float angle = 2.3398; // 弧度制的黄金角度 float2x2 rotation = float2x2(cos(angle), -sin(angle), sin(angle), cos(angle)); // 旋转矩阵 float2 offsetUV = float2(_BlurRadius, 0); // 每次旋转都需要进行偏移 float2 targetUV; float r; for(int i = 1; i < _BlurLoop; ++i) { // 甜甜圈的面积是均匀分布,因此r是线性增加的 r = sqrt(i); offsetUV = mul(rotation, offsetUV); targetUV = psInput.uv + _MainTex_TexelSize.xy * offsetUV * r; // 暗处模糊不会很明显,但明处会 outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, targetUV); } return outputColor / (_BlurLoop - 1); // 模糊基操 } ENDHLSL } Pass { NAME "depth of field" HLSLPROGRAM PSInput VS(VSInput vsInput) { PSInput vsOutput; vsOutput.positionH = TransformObjectToHClip(vsInput.positionL); #ifdef UNITY_UV_STARTS_AT_TOP if(_MainTex_TexelSize.y < 0) vsInput.uv.y = 1 - vsInput.uv.y; #endif vsOutput.uv = vsInput.uv; return vsOutput; } float4 PS(PSInput psInput) : SV_TARGET { float4 blurTex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv); float4 sourceTex = SAMPLE_TEXTURE2D(_SourTex, sampler_SourTex, psInput.uv); // 未模糊的纹理 // 转换为线性深度,且由近到远,值从0到1 float depth = Linear01Depth(tex2D(_CameraDepthTexture, psInput.uv).r , _ZBufferParams); float distance; // 分为远处模糊和近处模糊 if(depth > _FocusDistance) { // 因为深度是线性的,但我们想要的是焦点处清晰,但前后逐渐模糊,所以这里采用二次函数 distance = saturate((depth - _FocusDistance) * (depth - _FocusDistance) * _FarBlurIntensity); } else { distance = saturate((depth - _FocusDistance) * (depth - _FocusDistance) * _NearBlurIntensity); } // 最后模糊图和原图基于距离进行lerp return lerp(sourceTex, blurTex, distance); } ENDHLSL } } }
RenderFeature
-
都是些老朋友,这里放重要部分
-
PassSetting
[System.Serializable] public class PassSetting { [Tooltip("profiler tag will show up in frame debugger")] public readonly string m_ProfilerTag = "Dual Blur Pass"; [Tooltip("Pass insert position")] public RenderPassEvent m_passEvent = RenderPassEvent.AfterRenderingTransparents; [Tooltip("resolution ratio of sample")] [Range(1, 10)] public int m_Downsample = 1; [Tooltip("Loop of sample")] [Range(2, 7)] public int m_PassLoop = 2; [Tooltip("Radius of sample")] [Range(0, 10)] public float m_BlurRadius = 1; [Tooltip("Near Blur Intensity of DOF")] [Range(0, 10)] public float m_NearBlurIntensity = 1; [Tooltip("Far Blur Intensity of DOF")] [Range(0,10)] public float m_FarBlurIntensity = 1; [Tooltip("Camera Focus Point")] [Range(0, 1)] public float m_FocusDistance = 1; }
-
全部实现
public class BokehBlurRenderFeature : ScriptableRendererFeature { // render feature 显示内容 [System.Serializable] public class PassSetting { [Tooltip("profiler tag will show up in frame debugger")] public readonly string m_ProfilerTag = "Dual Blur Pass"; [Tooltip("Pass insert position")] public RenderPassEvent m_passEvent = RenderPassEvent.AfterRenderingTransparents; [Tooltip("resolution ratio of sample")] [Range(1, 10)] public int m_Downsample = 1; [Tooltip("Loop of sample")] [Range(2, 7)] public int m_PassLoop = 2; [Tooltip("Radius of sample")] [Range(0, 10)] public float m_BlurRadius = 1; [Tooltip("Near Blur Intensity of DOF")] [Range(0, 10)] public float m_NearBlurIntensity = 1; [Tooltip("Far Blur Intensity of DOF")] [Range(0,10)] public float m_FarBlurIntensity = 1; [Tooltip("Camera Focus Point")] [Range(0, 1)] public float m_FocusDistance = 1; } public PassSetting m_Setting = new PassSetting(); BokehBlurRenderPass m_BokehBlurPass; // 初始化 public override void Create() { m_BokehBlurPass = new BokehBlurRenderPass(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_BokehBlurPass); } }
RenderPass
-
ShaderIDs
static class ShaderIDs { // int 相较于 string可以获得更好的性能,因为这是预处理的 internal static readonly int m_BlurRadiusProperty = Shader.PropertyToID("_BlurRadius"); internal static readonly int m_NearBlurIntensityProperty = Shader.PropertyToID("_NearBlurIntensity"); internal static readonly int m_FarBlurIntensityProperty = Shader.PropertyToID("_FarBlurIntensity"); internal static readonly int m_PassLoopProperty = Shader.PropertyToID("_BlurLoop"); internal static readonly int m_FocusDistanceProperty = Shader.PropertyToID("_FocusDistance"); // Blur RT and source RT internal static readonly int m_BlurRTProperty = Shader.PropertyToID("_BufferRT1"); internal static readonly int m_SourRTProperty = Shader.PropertyToID("_SourTex"); }
-
构造函数
// 用于设置material 属性 public BokehBlurRenderPass(BokehBlurRenderFeature.PassSetting passSetting) { this.m_passSetting = passSetting; renderPassEvent = m_passSetting.m_passEvent; if (m_Material == null) m_Material = CoreUtils.CreateEngineMaterial("Custom/BokehBlur"); // 基于pass setting设置material Properties m_Material.SetFloat(ShaderIDs.m_BlurRadiusProperty, m_passSetting.m_BlurRadius); m_Material.SetFloat(ShaderIDs.m_NearBlurIntensityProperty, m_passSetting.m_NearBlurIntensity); m_Material.SetFloat(ShaderIDs.m_FarBlurIntensityProperty, m_passSetting.m_FarBlurIntensity); m_Material.SetFloat(ShaderIDs.m_FocusDistanceProperty, m_passSetting.m_FocusDistance); m_Material.SetInt(ShaderIDs.m_PassLoopProperty, m_passSetting.m_PassLoop); }
-
OnCameraSetup
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { // Grab the color buffer from the renderer camera color target m_TargetBuffer = renderingData.cameraData.renderer.cameraColorTarget; // camera target descriptor will be used when creating a temporary render texture RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor; // 设置 temporary render texture的depth buffer的精度 descriptor.depthBufferBits = 0; // 注意!因为申请RT后,RT的分辨率不可改变,而该Source RT在后续由于camera render targt会复制一份给他,所以这里不能用降采样的分辨率 cmd.GetTemporaryRT(ShaderIDs.m_SourRTProperty, descriptor, FilterMode.Bilinear); // 降采样 descriptor.width /= m_passSetting.m_Downsample; descriptor.height /= m_passSetting.m_Downsample; cmd.GetTemporaryRT(ShaderIDs.m_BlurRTProperty, descriptor, FilterMode.Bilinear); }
-
Execute
// 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))) { // 将camera texture复制给sour RT,同时发送给shader中的_SourTex cmd.CopyTexture(m_TargetBuffer, ShaderIDs.m_SourRTProperty); // 进行 bokenh blur并复制给Blur RT cmd.Blit(m_TargetBuffer, ShaderIDs.m_BlurRTProperty, m_Material, 0); // 将blur后且进行景深计算的RT复制给输出的RT cmd.Blit(ShaderIDs.m_BlurRTProperty, m_TargetBuffer, m_Material, 1); } // Execute the command buffer and release it context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); }
-
OnCameraCleanUp
// 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"); cmd.ReleaseTemporaryRT(ShaderIDs.m_BlurRTProperty); cmd.ReleaseTemporaryRT(ShaderIDs.m_SourRTProperty); }
效果
reference
https://zhuanlan.zhihu.com/p/67389489
https://zhuanlan.zhihu.com/p/125744132
https://zh.wikipedia.org/wiki/黄金分割率
https://zhuanlan.zhihu.com/p/64288140
https://zhuanlan.zhihu.com/p/394821497
https://zhuanlan.zhihu.com/p/387107536
标签:uv,散景,自定义,cmd,Tooltip,Unity,ShaderIDs,public,模糊 From: https://www.cnblogs.com/chenglixue/p/17822931.html