Unity 屏幕模糊效果
前几天,美术要我做一个屏幕模糊的效果,百度了半天,最后总算解决了。
趁着有时间,现在来总结一下。
首先我这边是为了出效果,所以原理先放一边,来谈谈如何实现。什么高斯模糊,均值模糊先忽略,看起来太懵,要花大时间去钻。总的来说,实现主要是两种类型,一种是基于摄像机,一种是基于UI遮罩实现。
摄像机实现
Shader部分
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "CameraFilterPack/Blur_Movie" {
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_TimeX ("Time", Range(0.0, 1.0)) = 1.0
_Distortion ("_Distortion", Range(0.0, 1.0)) = 0.3
_ScreenResolution ("_ScreenResolution", Vector) = (0.,0.,0.,0.)
_Radius ("_Radius", Range(0.0, 1000.0)) = 700.0
_Factor ("_Factor", Range(0.0, 1000.0)) = 200.0
}
SubShader
{
Pass
{
ZTest Always
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma target 3.0
#pragma glsl
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform float _TimeX;
uniform float _Distortion;
uniform float4 _ScreenResolution;
uniform float _Radius;
uniform float _Factor;
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
half2 texcoord : TEXCOORD0;
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
};
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color;
return OUT;
}
#define tex2D(sampler,uvs) tex2Dlod( sampler , float4( ( uvs ) , 0.0f , 0.0f) )
float4 frag (v2f i) : COLOR
{
float factor = _Factor/_ScreenResolution.y * 64.0;
float radius = _Radius/_ScreenResolution.x * 2.0;
float4 col = 0.0;
float4 accumColx = 0.0;
float4 accumW = 0.0;
for (float j = -5.0; j < 5.0; j += 1.0)
{
for (float u = -5.0; u < 5.0; u += 1.0)
{
float2 offsetx = (1.0/_ScreenResolution.xy) * float2(u + j, j - u);
col = tex2D(_MainTex, i.texcoord.xy + offsetx * radius);
float4 movie = 1.0 + col * col * col * factor;
accumColx = accumColx + col * movie;
accumW += movie;
}
}
accumColx = accumColx/accumW;
return accumColx;
}
ENDCG
}
}
}
c#部分
using UnityEngine;
[ExecuteInEditMode]
[AddComponentMenu ("Camera Filter Pack/Blur/Movie")]
public class CameraFilterPack_Blur_Movie : MonoBehaviour {
#region Variables
public Shader SCShader;
private float TimeX = 1.0f;
private Vector4 ScreenResolution;
private Material SCMaterial;
[Range(0,1000)]
public float Radius = 150.0f;
[Range(0,1000)]
public float Factor = 200.0f;
[Range(1,8)]
public int FastFilter = 2;
public static float ChangeRadius;
public static float ChangeFactor;
public static int ChangeFastFilter;
#endregion
#region Properties
Material material
{
get
{
if(SCMaterial == null)
{
SCMaterial = new Material(SCShader);
SCMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return SCMaterial;
}
}
#endregion
void Start ()
{
ChangeRadius = Radius;
ChangeFactor = Factor;
ChangeFastFilter = FastFilter;
SCShader = Shader.Find("CameraFilterPack/Blur_Movie");
if(!SystemInfo.supportsImageEffects)
{
enabled = false;
return;
}
}
void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)
{
if(SCShader != null)
{
int DownScale=FastFilter;
TimeX+=Time.deltaTime;
if (TimeX>100) TimeX=0;
material.SetFloat("_TimeX", TimeX);
material.SetFloat("_Radius", Radius/DownScale);
material.SetFloat("_Factor", Factor);
material.SetVector("_ScreenResolution",new Vector2(Screen.width/DownScale,Screen.height/DownScale));
int rtW = sourceTexture.width/DownScale;
int rtH = sourceTexture.height/DownScale;
if (FastFilter>1)
{
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(sourceTexture, buffer, material);
Graphics.Blit(buffer, destTexture);
RenderTexture.ReleaseTemporary(buffer);
}
else
{
Graphics.Blit(sourceTexture, destTexture, material);
}
}
else
{
Graphics.Blit(sourceTexture, destTexture);
}
}
void OnValidate()
{
ChangeRadius=Radius;
ChangeFactor=Factor;
ChangeFastFilter=FastFilter;
}
// Update is called once per frame
void Update ()
{
if (Application.isPlaying)
{
Radius = ChangeRadius;
Factor = ChangeFactor;
FastFilter = ChangeFastFilter;
}
#if UNITY_EDITOR
if (Application.isPlaying!=true)
{
SCShader = Shader.Find("CameraFilterPack/Blur_Movie");
}
#endif
}
void OnDisable ()
{
if(SCMaterial)
{
DestroyImmediate(SCMaterial);
}
}
}
将CameraFilterPack_Blur_Movie 挂在你想实现模糊得摄像机上,就可以实现屏幕模糊效果了。
调整Radius,Factor,FastFilter三个值去得到你想要得模糊度。
但这个效果,并不是我要的效果。我做的是2D休闲小游戏的开发,要求背景模糊,但是UI不能模糊。有人说,你看上面效果,UI不是没有模糊吗?不是完全符合你的开发需求?其实,在上面的场景里,我用了两个摄像机,一个主相机照场景,一个UI相机照UI。我只是让主相机模糊了,但UI相机还是正常的。而我开发的2D游戏,是只有一个摄像机的,所以这个方法不能符合我的开发需求。
基于UGI遮罩实现
Shader部分
Shader "MyShader/BackBlur" {
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Main Color", Color) = (1,1,1,1)
_Size ("Size", Range(0, 20)) = 1
}
Category {
// We must be transparent, so other objects are drawn before this one.
Tags {
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
SubShader {
// Horizontal blur
GrabPass {
Tags { "LightMode" = "Always" }
}
Pass {
Tags { "LightMode" = "Always" }
Name "BackBlurHor"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float4 color : COLOR;
};
struct v2f {
float4 vertex : POSITION;
float4 uvgrab : TEXCOORD0;
float4 color : COLOR;
};
v2f vert (appdata_t v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif
o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
o.uvgrab.zw = o.vertex.zw;
o.color = v.color;
return o;
}
sampler2D _GrabTexture;
float4 _GrabTexture_TexelSize;
float4 _MainTex_TexelSize;
float _Size;
uniform float4 _Color;
// static float GaussianKernel[9] = {
// 0.05, 0.09, 0.12,
// 0.15, 0.18, 0.15,
// 0.12, 0.09, 0.05
// };
// static float GaussianKernel[19] = {
// 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09,
// 0.1,
// 0.09, 0.08, 0.07, 0.06, 0.05, 0.04, 0.03, 0.02, 0.01,
// };
// static float GaussianKernelD[19] = {
// -9.0, -8.0, -7.0, -6.0, -5.0, -4.0, -3.0, -2.0, -1.0,
// 0.0,
// +1.0, +2.0, +3.0, +4.0, +5.0, +6.0, +7.0, +8.0, +9.0,
// };
half4 GrabPixel(v2f i, float weight, float kernelx){
if (i.uvgrab.x == 0 && i.uvgrab.y == 0){
kernelx = 0;
}
return tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x + _GrabTexture_TexelSize.x*kernelx*_Size, i.uvgrab.y, i.uvgrab.z, i.uvgrab.w))) * weight;
}
half4 frag( v2f i ) : COLOR {
half4 sum = half4(0,0,0,0);
// #define GRABPIXEL(weight, kernelx) tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x + _GrabTexture_TexelSize.x * kernelx*_Size, i.uvgrab.y, i.uvgrab.z, i.uvgrab.w))) * weight
sum += GrabPixel(i, 0.05, -4.0);
sum += GrabPixel(i, 0.09, -3.0);
sum += GrabPixel(i, 0.12, -2.0);
sum += GrabPixel(i, 0.15, -1.0);
sum += GrabPixel(i, 0.18, 0.0);
sum += GrabPixel(i, 0.15, +1.0);
sum += GrabPixel(i, 0.12, +2.0);
sum += GrabPixel(i, 0.09, +3.0);
sum += GrabPixel(i, 0.05, +4.0);
// sum += GrabPixel(i, 0.01, -9.0);
// sum += GrabPixel(i, 0.02, -8.0);
// sum += GrabPixel(i, 0.03, -7.0);
// sum += GrabPixel(i, 0.04, -6.0);
// sum += GrabPixel(i, 0.05, -5.0);
// sum += GrabPixel(i, 0.06, -4.0);
// sum += GrabPixel(i, 0.07, -3.0);
// sum += GrabPixel(i, 0.08, -2.0);
// sum += GrabPixel(i, 0.09, -1.0);
// sum += GrabPixel(i, 0.10, 0.0);
// sum += GrabPixel(i, 0.09, +1.0);
// sum += GrabPixel(i, 0.08, +2.0);
// sum += GrabPixel(i, 0.07, +3.0);
// sum += GrabPixel(i, 0.06, +4.0);
// sum += GrabPixel(i, 0.05, +5.0);
// sum += GrabPixel(i, 0.04, +6.0);
// sum += GrabPixel(i, 0.03, +7.0);
// sum += GrabPixel(i, 0.02, +8.0);
// sum += GrabPixel(i, 0.01, +9.0);
float4 col5 = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
float decayFactor = 1.0f;
if (i.uvgrab.x == 0 && i.uvgrab.y == 0){
decayFactor = 0;
}
sum = lerp(col5, sum, decayFactor) * i.color * _Color;
return sum;
}
ENDCG
}
// Vertical blur
GrabPass {
Tags { "LightMode" = "Always" }
}
Pass {
Tags { "LightMode" = "Always" }
Name "BackBlurVer"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord: TEXCOORD0;
float4 color : COLOR;
};
struct v2f {
float4 vertex : POSITION;
float4 uvgrab : TEXCOORD0;
float4 color : COLOR;
};
v2f vert (appdata_t v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif
o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
o.uvgrab.zw = o.vertex.zw;
o.color = v.color;
return o;
}
sampler2D _GrabTexture;
float4 _GrabTexture_TexelSize;
float _Size;
uniform float4 _Color;
half4 GrabPixel(v2f i, float weight, float kernely){
if (i.uvgrab.x == 0 && i.uvgrab.y == 0){
kernely = 0;
}
return tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x, i.uvgrab.y + _GrabTexture_TexelSize.y*kernely*_Size, i.uvgrab.z, i.uvgrab.w))) * weight;
}
half4 frag( v2f i ) : COLOR {
half4 sum = half4(0,0,0,0);
// #define GRABPIXEL(weight,kernely) tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x, i.uvgrab.y + _GrabTexture_TexelSize.y * kernely*_Size, i.uvgrab.z, i.uvgrab.w))) * weight
sum += GrabPixel(i, 0.05, -4.0);
sum += GrabPixel(i, 0.09, -3.0);
sum += GrabPixel(i, 0.12, -2.0);
sum += GrabPixel(i, 0.15, -1.0);
sum += GrabPixel(i, 0.18, 0.0);
sum += GrabPixel(i, 0.15, +1.0);
sum += GrabPixel(i, 0.12, +2.0);
sum += GrabPixel(i, 0.09, +3.0);
sum += GrabPixel(i, 0.05, +4.0);
// sum += GrabPixel(i, 0.01, -9.0);
// sum += GrabPixel(i, 0.02, -8.0);
// sum += GrabPixel(i, 0.03, -7.0);
// sum += GrabPixel(i, 0.04, -6.0);
// sum += GrabPixel(i, 0.05, -5.0);
// sum += GrabPixel(i, 0.06, -4.0);
// sum += GrabPixel(i, 0.07, -3.0);
// sum += GrabPixel(i, 0.08, -2.0);
// sum += GrabPixel(i, 0.09, -1.0);
// sum += GrabPixel(i, 0.10, 0.0);
// sum += GrabPixel(i, 0.09, +1.0);
// sum += GrabPixel(i, 0.08, +2.0);
// sum += GrabPixel(i, 0.07, +3.0);
// sum += GrabPixel(i, 0.06, +4.0);
// sum += GrabPixel(i, 0.05, +5.0);
// sum += GrabPixel(i, 0.04, +6.0);
// sum += GrabPixel(i, 0.03, +7.0);
// sum += GrabPixel(i, 0.02, +8.0);
// sum += GrabPixel(i, 0.01, +9.0);
float4 col5 = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
float decayFactor = 1.0f;
if (i.uvgrab.x == 0 && i.uvgrab.y == 0){
decayFactor = 0;
}
sum = lerp(col5, sum, decayFactor) * i.color * _Color;
return sum;
}
ENDCG
}
}
}
}
C#部分
using System.Collections;
using UnityEngine;
namespace Hyperbyte
{
public static class UIExtentions
{
// Clear all child gameobjects of the given gameobject.
public static void ClearAllChild(this GameObject obj)
{
if (obj.transform.childCount > 0)
{
foreach (Transform child in obj.transform)
{
GameObject.Destroy(child.gameObject);
}
}
}
// Clear all child gameobjects of the given transform.
public static void ClearAllChild(this Transform obj)
{
if (obj.childCount > 0)
{
foreach (Transform child in obj)
{
GameObject.Destroy(child.gameObject);
}
}
}
// Clear all child gameobjects of the given rect transform.
public static void ClearAllChild(this RectTransform obj)
{
if (obj.childCount > 0)
{
foreach (Transform child in obj)
{
GameObject.Destroy(child.gameObject);
}
}
}
// Activates the given gameobject with animation. used for only popups of the game.
public static void Activate(this GameObject target, bool addToStack = true)
{
// target.gameObject.SetActive(true);
// target.transform.SetAsLastSibling();
// if(addToStack) {
// UIController.Instance.Push(target.name);
// }
}
// Deactivates the game object with animation.
public static void Deactivate(this GameObject target)
{
// Animator animator = target.GetComponent<Animator>();
// if (animator != null)
// {
// animator.Play("Close");
// UIController.Instance.StartCoroutine(DisableWindow(target));
// }
// else
// {
// target.SetActive(false);
// UIController.Instance.Pop(target.name);
// }
}
/// <summary>
/// Disable given gameobject and removes it from stack added to any.
/// </summary>
static IEnumerator DisableWindow(GameObject target)
{
yield return new WaitForSeconds(0.3F);
// target.SetActive(false);
// UIController.Instance.Pop(target.name);
}
}
}
用MyShader/BackBlur新建一个材质,再新建一个Image。把这个Image的材质设置为它。它挡住的地方(层级比他低的)都会变得模糊。
这就完美符合我的需求了。只要把IMAGE遮住底部,就可以完美实现背景模糊的效果。调整它的层级就能决定谁模糊,谁不模糊。