在LWRP中用Scriptable Render Pass来实现模糊场景的屏幕后特效。官方给出了PerObjectBloom,ToonOutline等等不少的示例可参考。不过SRP版本迭代得非常快,很多示例的写法很快就过时了,而且导入后还会经常碰到异常的效果,免不了调试一番。
实现思路
先给CustomForwardRendererData写一个ScriptableRendererFeature。为了节约性能,额外用代码控制何时AddRenderPasses。
public class BlurGrabPass : UnityEngine.Rendering.Universal.ScriptableRendererFeature { [Serializable] public class Settings { public Vector2 m_BlurAmount; public Material m_BlurMaterial; } private Material m_BlurMaterial; public Settings settings; private GrabPassImpl m_grabPass; public override void Create() { m_BlurMaterial = settings.m_BlurMaterial; m_grabPass = new GrabPassImpl(m_BlurMaterial, settings.m_BlurAmount); //m_grabPass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox; //防止粒子效果被剔除,防止PostProcessing效果被剔除,包括camera fog m_grabPass.renderPassEvent = UnityEngine.Rendering.Universal.RenderPassEvent.AfterRendering; } public override void AddRenderPasses(UnityEngine.Rendering.Universal.ScriptableRenderer renderer, ref UnityEngine.Rendering.Universal.RenderingData renderingData) { //无时无刻都使用GrabPass会降低性能(真机测试降低了10fps),于是加入条件控制 if(GameSceneManager.instance && GameSceneManager.instance.blurAmount.x>0.01f && GameSceneManager.instance.blurAmount.y>0.01f) { m_grabPass.UpdateBlurAmount(GameSceneManager.instance.blurAmount); renderer.EnqueuePass(m_grabPass); } } }
主要目的是给菜单的背景材质准备一张经过模糊处理的屏幕截图(命名做_GrabBlurTexture)。因为LWRP不再支持build-in shader的grab pass,所以要自己写抓取屏幕截图的代码。
public class GrabPassImpl : UnityEngine.Rendering.Universal.ScriptableRenderPass { const string k_RenderGrabPassTag = "Blur Refraction Pass"; private Material m_BlurMaterial; private Vector2 m_BlurAmount; private RenderTextureDescriptor m_OpaqueDesc; private RenderTextureDescriptor m_BaseDescriptor; private UnityEngine.Rendering.Universal.RenderTargetHandle m_ColorHandle; public GrabPassImpl(Material blurMaterial, Vector2 blurAmount) { m_BlurMaterial = blurMaterial; m_ColorHandle = UnityEngine.Rendering.Universal.RenderTargetHandle.CameraTarget; m_BlurAmount = blurAmount; } public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { cameraTextureDescriptor.msaaSamples = 1; m_OpaqueDesc = cameraTextureDescriptor; cmd.GetTemporaryRT(m_ColorHandle.id, cameraTextureDescriptor, FilterMode.Bilinear); } public void UpdateBlurAmount(Vector2 newBlurAmount) { m_BlurAmount = newBlurAmount; } public override void Execute(ScriptableRenderContext context, ref UnityEngine.Rendering.Universal.RenderingData renderingData) { CommandBuffer buf = CommandBufferPool.Get(k_RenderGrabPassTag); using (new ProfilingSample(buf, k_RenderGrabPassTag)) { // copy screen into temporary RT int screenCopyID = Shader.PropertyToID("_ScreenCopyTexture"); buf.GetTemporaryRT(screenCopyID, m_OpaqueDesc, FilterMode.Bilinear); buf.Blit(m_ColorHandle.Identifier(), screenCopyID); m_OpaqueDesc.width = m_OpaqueDesc.width>>1; m_OpaqueDesc.height = m_OpaqueDesc.height>>1; // get two smaller RTs int blurredID = Shader.PropertyToID("_BlurRT1"); int blurredID2 = Shader.PropertyToID("_BlurRT2"); buf.GetTemporaryRT(blurredID, m_OpaqueDesc, FilterMode.Bilinear); buf.GetTemporaryRT(blurredID2, m_OpaqueDesc, FilterMode.Bilinear); // downsample screen copy into smaller RT, release screen RT buf.Blit(screenCopyID, blurredID); // horizontal blur buf.SetGlobalVector("offsets", new Vector4(m_BlurAmount.x / Screen.width, 0, 0, 0)); buf.Blit(blurredID, blurredID2, m_BlurMaterial); // vertical blur buf.SetGlobalVector("offsets", new Vector4(0, m_BlurAmount.y / Screen.height, 0, 0)); buf.Blit(blurredID2, blurredID, m_BlurMaterial); // horizontal blur buf.SetGlobalVector("offsets", new Vector4(m_BlurAmount.x * 2 / Screen.width, 0, 0, 0)); buf.Blit(blurredID, blurredID2, m_BlurMaterial); // vertical blur buf.SetGlobalVector("offsets", new Vector4(0, m_BlurAmount.y * 2 / Screen.height, 0, 0)); buf.Blit(blurredID2, blurredID, m_BlurMaterial); // horizontal blur buf.SetGlobalVector("offsets", new Vector4(m_BlurAmount.x * 4 / Screen.width, 0, 0, 0)); buf.Blit(blurredID, blurredID2, m_BlurMaterial); // vertical blur buf.SetGlobalVector("offsets", new Vector4(0, m_BlurAmount.y * 4 / Screen.height, 0, 0)); buf.Blit(blurredID2, blurredID, m_BlurMaterial); //Set Texture for Shader Graph buf.SetGlobalTexture("_GrabBlurTexture", blurredID); buf.ReleaseTemporaryRT(screenCopyID); buf.ReleaseTemporaryRT(blurredID); buf.ReleaseTemporaryRT(blurredID2); } context.ExecuteCommandBuffer(buf); CommandBufferPool.Release(buf); } public override void FrameCleanup(CommandBuffer cmd) { cmd.ReleaseTemporaryRT(m_ColorHandle.id); } }
blit方法使用的m_BlurMaterial的shader,是采用简单加权取邻近像素颜色的方式进行模糊。
Shader "MyAct/Blur" { Properties { _MainTex ("Base (RGB)", 2D) = "" {} } CGINCLUDE #include "UnityCG.cginc" struct v2f { float4 pos : POSITION; float2 uv : TEXCOORD0; float4 uv01 : TEXCOORD1; float4 uv23 : TEXCOORD2; float4 uv45 : TEXCOORD3; }; float4 offsets; float4 tintColor; sampler2D _MainTex; v2f vert (appdata_img v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv.xy = v.texcoord.xy; o.uv01 = v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1); o.uv23 = v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 2.0; o.uv45 = v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 3.0; return o; } half4 frag (v2f i) : COLOR { half4 color = float4 (0,0,0,0); color += 0.40 * tex2D (_MainTex, i.uv); color += 0.15 * tex2D (_MainTex, i.uv01.xy); color += 0.15 * tex2D (_MainTex, i.uv01.zw); color += 0.10 * tex2D (_MainTex, i.uv23.xy); color += 0.10 * tex2D (_MainTex, i.uv23.zw); color += 0.05 * tex2D (_MainTex, i.uv45.xy); color += 0.05 * tex2D (_MainTex, i.uv45.zw); return color; } ENDCG Fallback off }
Review
在Unity2019.3中LWRP变成了URP,并且post processing stack进行了升级整合,可以使用内置volume的Depth Of Field来实现场景画面模糊,因为是按景深来模糊,所以更有镜头感。