unity 2d摄像机类型_2D游戏实现背景模糊

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.html​docs.unity3d.com
v2-3d7c84d71630eb0eafefb5471662caf0_180x120.jpg

Graphics Command Buffers

Graphics Command Buffers​docs.unity3d.com
v2-3d7c84d71630eb0eafefb5471662caf0_180x120.jpg

当时也看过LWRP自定义渲染管线的宣传视频,详解Unity轻量级渲染管线LWRP:

Invitation to Join 详解Unity轻量级渲染管线LWRP by Richard Yang 杨栋​connect.unity.com
v2-e52fd42566f8723333f0755261a25029_180x120.jpg

LWRP支持增加特定插入点

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

但是最后效果并不好,模糊的效果有点差

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
没有使用LWRP

v2-0a31542a9b9f6c366ebb6ed132b377c0_b.jpg
使用LWRP

最后方案5 使用Command Buffers的效果,先来一张最后的效果图吧

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
重点内容

接着就是增加一个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

v2-fbcec1e190a5e9f7bb0da665e76690e3_b.jpg
红框框住的就是Command的处理

v2-d25eeea3025a9b0a97705f046e7b97aa_b.jpg

最后的模糊效果。这个事其实还挺有意思的。

希望大家能喜欢吧

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值