在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来实现场景画面模糊,因为是按景深来模糊,所以更有镜头感。



