![v2-78ef424bf213e71f4c49529269b4fb87_1440w.jpg?source=172ae18b](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-78ef424bf213e71f4c49529269b4fb87_1440w.jpg?source=172ae18b)
之前在Switch上通关了《空洞骑士》感觉画面非常好看,而且很精致。光影,水纹,灰尘,背景都浑然一体非常好看。
因为之前自己也上线过2D游戏《J-Girl》,所以对好看的2D游戏的风格也很感兴趣,想实现一下类似《空洞骑士》的场景效果。
这篇主要记录一下背景模糊的实现。
我自己搜了一下,提到了2个方法
1、直接给背景的spriteRenderer赋值一个模糊材质
2、增加一个新的摄像机专门渲染背景层级,之后给相机增加一个模糊的后效
因为自己Shader写的也比较少。所以会经常翻冯乐乐的《UnityShader入门精要》,
书中10.2节介绍了一个“玻璃效果”,看完之后又多了两个解决方法
3、用渲染纹理来实现,实际和第二条差不多
4、用GrabPass,抓取屏幕图片当作一张纹理然后进行处理。
作者最后又留了一个介绍使用命令缓冲(Command Buffers)来实现,同时附加了一个官方的链接:
https://docs.unity3d.com/Manual/GraphicsCommandBuffers.htmldocs.unity3d.com![v2-3d7c84d71630eb0eafefb5471662caf0_180x120.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-3d7c84d71630eb0eafefb5471662caf0_180x120.jpg)
Graphics Command Buffers
Graphics Command Buffersdocs.unity3d.com![v2-3d7c84d71630eb0eafefb5471662caf0_180x120.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-3d7c84d71630eb0eafefb5471662caf0_180x120.jpg)
当时也看过LWRP自定义渲染管线的宣传视频,详解Unity轻量级渲染管线LWRP:
Invitation to Join 详解Unity轻量级渲染管线LWRP by Richard Yang 杨栋connect.unity.com![v2-e52fd42566f8723333f0755261a25029_180x120.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-e52fd42566f8723333f0755261a25029_180x120.jpg)
LWRP支持增加特定插入点
![v2-2e460c3fdbef9c26d1afeb8d1476848a_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-2e460c3fdbef9c26d1afeb8d1476848a_b.jpg)
有多了2种方案
5、使用Command Buffers来定义额外渲染操作
6、将Unity渲染管线设置为LWRP,然后实现接口
然后开始了实验步骤,方案1,直接上模糊材质,这里贴上sprite模糊的shader
Shader "Unlit/SpriteBlur"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_Color ("_Color", Color) = (1,1,1,1)
_Distortion ("Distortion", Range(0,3)) = 0
_Alpha ("Alpha", Range (0,1)) = 1.0
}
SubShader
{
Tags {"Queue"="Transparent" "IgnoreProjector"="true" "RenderType"="Transparent"}
ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma target 3.0
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
half2 texcoord : TEXCOORD0;
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
};
sampler2D _MainTex;
fixed4 _Color;
float _Distortion;
fixed _Alpha;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color;
return OUT;
}
float4 frag (v2f i) : COLOR
{
float stepU = 0.00390625f * _Distortion;
float stepV = stepU;
fixed3x3 gaussian = fixed3x3( 1.0, 2.0, 1.0, 2.0, 4.0, 2.0, 1.0, 2.0, 1.0);
float4 result = 0;
float4 Alpha = tex2D(_MainTex, i.texcoord);
float2 texCoord;
texCoord = i.texcoord.xy + float2( -stepU, -stepV ); result += tex2D(_MainTex,texCoord);
texCoord = i.texcoord.xy + float2( -stepU, 0 ); result += 2.0 * tex2D(_MainTex,texCoord);
texCoord = i.texcoord.xy + float2( -stepU, stepV ); result += tex2D(_MainTex,texCoord);
texCoord = i.texcoord.xy + float2( 0, -stepV ); result += 2.0 * tex2D(_MainTex,texCoord);
texCoord = i.texcoord.xy ; result += 4.0 * tex2D(_MainTex,texCoord);
texCoord = i.texcoord.xy + float2( 0, stepV ); result += 2.0 * tex2D(_MainTex,texCoord);
texCoord = i.texcoord.xy + float2( stepU, -stepV ); result += tex2D(_MainTex,texCoord);
texCoord = i.texcoord.xy + float2( stepU, 0 ); result += 2.0* tex2D(_MainTex,texCoord);
texCoord = i.texcoord.xy + float2( stepU, -stepV ); result += tex2D(_MainTex,texCoord);
float4 r;
r=result*0.0625;
r.a*=Alpha.a*(1.0-_Alpha);
r=r*i.color;
return r;
}
ENDCG
}
}
Fallback "Sprites/Default"
}
![v2-50a7711fde80a5588115ae38ed77cf6a_b.png](http://img-01.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-50a7711fde80a5588115ae38ed77cf6a_b.png)
但是最后效果并不好,模糊的效果有点差
![v2-ceba34f6e806f9d67631c98e989da343_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-ceba34f6e806f9d67631c98e989da343_b.jpg)
方案2和方案3并没有实践,因为我知道效果肯定能实现,但是多的开销其实没有太大的意义,冯乐乐虽然在书中写了一句:“尽管这种发放需要把部分场景再次渲染一遍,但是我们可以通过调整摄像机的渲染层减少二次渲染的场景大小,或使用其他方法控制摄像机是否需要开启。”因为背景的模糊其实是一直长期存在的,如果别的方法能实现,就不想增加一个相机。
方案4通过GrabPass实现,这个开销更大,书上补充了“高分辨率的设备上可能会造成严重的带宽影响,而且移动设备有的不支持”。我也上网查过,老外的建议是能用Command Buffers实现就不要用GrabPass
方案5和方案6,其实我是先用了方案6 LWRP,但是很可惜在2D游戏里LWRP并没有像宣传的产生高效的渲染效果,当我替换渲染管线之后,场景的FPS降低了一半。所以有就直接放弃了LWRP的方案。(可能是因为LWRP是给3D使用的,2D游戏没有使用灯光,所以显得鸡肋)
![v2-7bc662a7957bc80fea8b08035c5ce47a_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-7bc662a7957bc80fea8b08035c5ce47a_b.jpg)
![v2-0a31542a9b9f6c366ebb6ed132b377c0_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-0a31542a9b9f6c366ebb6ed132b377c0_b.jpg)
最后方案5 使用Command Buffers的效果,先来一张最后的效果图吧
![v2-b7867216496e6a453f7b8d7016b12b33_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-b7867216496e6a453f7b8d7016b12b33_b.jpg)
模糊的很均匀,就是我要的效果。
首先是添加一个Shader,这里是直接用Unity官方示例里的高斯模糊
Shader "Hidden/SeparableGlassBlur" {
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;
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
Subshader {
Pass {
ZTest Always Cull Off ZWrite Off
Fog { Mode off }
CGPROGRAM
#pragma fragmentoption ARB_precision_hint_fastest
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
Fallback off
}
然后要给背景的SpriteRenderer替换一个渲染队列为2000的sprite材质。这样子实现分开渲染。
(这里有一个bug,修改材质之后原本的Order in Layer没有起作用,有的顺序低的还会跑到前面来。我现在的解决办法是用一个渲染队列为1999的sprite材质赋值,强制它变低。)
---2019.8.2添加---
后来想再深入了解一下为什么渲染层级低的会出物体,渲染的顺序反过来了,具体原因参考这篇
Unity渲染顺序总结www.jianshu.com![v2-6b3d3e931e195fcac340fc45ba31137a_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-6b3d3e931e195fcac340fc45ba31137a_b.jpg)
接着就是增加一个Comma Buffer的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteInEditMode]
public class CommandBufferBlur : MonoBehaviour
{
[Tooltip("模糊程度")]
public float BufferSize = 0.5f;
public Shader m_BlurShader;
private Material m_Material;
private Camera m_Cam;
private Dictionary<Camera, CommandBuffer> m_Cameras = new Dictionary<Camera, CommandBuffer>();
// Remove command buffers from all cameras we added into
private void Cleanup()
{
foreach (var cam in m_Cameras)
{
if (cam.Key)
{
cam.Key.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, cam.Value);
}
}
m_Cameras.Clear();
Object.DestroyImmediate(m_Material);
}
public void OnEnable()
{
Cleanup();
SetCommandBuffer();
}
public void OnDisable()
{
Cleanup();
}
// Whenever any camera will render us, add a command buffer to do the work on it
public void SetCommandBuffer()
{
var act = gameObject.activeInHierarchy && enabled;
if (!act)
{
Cleanup();
return;
}
var cam = Camera.main;
if (!cam)
return;
CommandBuffer buf = null;
// Did we already add the command buffer on this camera? Nothing to do then.
if (m_Cameras.ContainsKey(cam))
return;
if (!m_Material)
{
m_Material = new Material(m_BlurShader);
m_Material.hideFlags = HideFlags.HideAndDontSave;
}
buf = new CommandBuffer();
buf.name = "Grab screen and blur";
m_Cameras[cam] = buf;
// copy screen into temporary RT
int screenCopyID = Shader.PropertyToID("_ScreenCopyTexture");
buf.GetTemporaryRT(screenCopyID, -1, -1, 0, FilterMode.Bilinear);
buf.Blit(BuiltinRenderTextureType.CurrentActive, screenCopyID);
// get two smaller RTs
int blurredID = Shader.PropertyToID("_Temp1");
int blurredID2 = Shader.PropertyToID("_Temp2");
buf.GetTemporaryRT(blurredID, -2, -2, 0, FilterMode.Bilinear);
buf.GetTemporaryRT(blurredID2, -2, -2, 0, FilterMode.Bilinear);
// downsample screen copy into smaller RT, release screen RT
buf.Blit(screenCopyID, blurredID);
buf.ReleaseTemporaryRT(screenCopyID);
// horizontal blur
buf.SetGlobalVector("offsets", new Vector4(2.0f* BufferSize / Screen.width, 0, 0, 0));
buf.Blit(blurredID, blurredID2, m_Material);
// vertical blur
buf.SetGlobalVector("offsets", new Vector4(0, 2.0f * BufferSize / Screen.height, 0, 0));
buf.Blit(blurredID2, blurredID, m_Material);
// horizontal blur
buf.SetGlobalVector("offsets", new Vector4(4.0f * BufferSize / Screen.width, 0, 0, 0));
buf.Blit(blurredID, blurredID2, m_Material);
// vertical blur
buf.SetGlobalVector("offsets", new Vector4(0, 4.0f * BufferSize / Screen.height, 0, 0));
buf.Blit(blurredID2, blurredID, m_Material);
buf.Blit(blurredID, BuiltinRenderTextureType.CameraTarget);
cam.AddCommandBuffer(CameraEvent.AfterForwardOpaque, buf);
}
}
我也是在官方的基础上修改的,所以有一些冗余的代码。
这里最后说一下为什么这种办法模糊效果更好,因为逻辑当中对图案模糊了4次,可以打开Frame Debug来查看
![v2-211fb9be5ea3bf1eaa79584c5a8ec499_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-211fb9be5ea3bf1eaa79584c5a8ec499_b.jpg)
![v2-fbcec1e190a5e9f7bb0da665e76690e3_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-fbcec1e190a5e9f7bb0da665e76690e3_b.jpg)
![v2-d25eeea3025a9b0a97705f046e7b97aa_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b39d4025-8f2e-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-d25eeea3025a9b0a97705f046e7b97aa_b.jpg)
最后的模糊效果。这个事其实还挺有意思的。
希望大家能喜欢吧