狂拽屌炸天的游戏效果实现,都离不开模糊效果,而我们最常用的方法是后处理,今天介绍几种模糊效果的实现。首先上一张大原图:
第一种介绍的是均值模糊,三种中最为简单的模糊效果,计算简单,效果也差强人意:
Shader:
Shader "Custom/SimpleBlur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
ZTest Always
Cull Off
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
half2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half2 uv1 : TEXCOORD1;
half2 uv2 : TEXCOORD2;
half2 uv3 : TEXCOORD3;
half2 uv4 : TEXCOORD4;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
float _BlurRadius;
v2f vert (appdata v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv.xy;
o.uv1 = v.uv + _BlurRadius * _MainTex_TexelSize * half2(0,1);
o.uv2 = v.uv + _BlurRadius * _MainTex_TexelSize * half2(0,-1);
o.uv3 = v.uv + _BlurRadius * _MainTex_TexelSize * half2(1,0);
o.uv4 = v.uv + _BlurRadius * _MainTex_TexelSize * half2(-1,0);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col += tex2D(_MainTex, i.uv1);
col += tex2D(_MainTex, i.uv2);
col += tex2D(_MainTex, i.uv3);
col += tex2D(_MainTex, i.uv4);
col *= 0.2;
return col;
}
ENDCG
}
}
}
C#:
using UnityEngine;
public class SimpleBlur : PostEffectBase
{
[Range(0.1f, 10)]
public float _BlurRadius = 0.1f;
[Range(0, 10)]
public int downSample = 1;
[Range(0, 10)]
public int iteration = 1;
public void OnRenderImage(RenderTexture src, RenderTexture dst)
{
if (_Material)
{
RenderTexture rt1 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
RenderTexture rt2 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
_Material.SetFloat("_BlurRadius", _BlurRadius);
Graphics.Blit(src, rt1, _Material);
for (int i = 0; i < iteration; i++)
{
Graphics.Blit(rt1, rt2, _Material);
Graphics.Blit(rt2, rt1, _Material);
}
Graphics.Blit(rt1, dst, _Material);
RenderTexture.ReleaseTemporary(rt1);
RenderTexture.ReleaseTemporary(rt2);
}
}
}
高斯,提到这个词,一般人都“敬而远之”,但其实很多效果都是在“高斯”的基础上实现的,这里实现了两版高斯模糊。
Shader:
Shader "Custom/GaussBlur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
ZTest Always
Cull Off
ZWrite Off
CGPROGRAM
#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
half _BlurRadius;
float totalWeight;
half GetWeight(float x, float y, float rho)
{
return exp(-(x*x + y*y)/(2.0f*rho*rho));
}
fixed4 GaussBlur(v2f i)
{
// half totalWeight = 0;
fixed4 finalColor = fixed4(0, 0, 0, 0);
half rho = _BlurRadius / 3.0f;
// for(int w = -_BlurRadius; w<= _BlurRadius; w++)
// {
// for(int h = -_BlurRadius; h<= _BlurRadius; h++)
// {
// half weight = GetWeight(w, h, rho);
// totalWeight += weight;
// }
// }
for(int x = -_BlurRadius; x<= _BlurRadius; x++)
{
for(int y = -_BlurRadius; y<= _BlurRadius; y++)
{
half wt = GetWeight(x, y, rho)/totalWeight;
fixed4 col = tex2D(_MainTex, i.uv + float2(x, y) * _MainTex_TexelSize.xy);
finalColor += col * wt;
}
}
return finalColor;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = GaussBlur(i);
return col;
}
ENDCG
}
}
}
C#:
using UnityEngine;
public class GaussBlur : PostEffectBase
{
[Range(0.1f, 10)]
public float _BlurRadius = 0.1f;
[Range(0, 10)]
public int downSample = 1;
[Range(0, 10)]
public int iteration = 1;
public void OnRenderImage(RenderTexture src, RenderTexture dst)
{
if (_Material)
{
RenderTexture rt1 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
RenderTexture rt2 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
_Material.SetFloat("_BlurRadius", _BlurRadius);
_Material.SetFloat("totalWeight", GetTotalWeight());
Graphics.Blit(src, rt1, _Material);
for (int i = 0; i < iteration; i++)
{
Graphics.Blit(rt1, rt2, _Material);
Graphics.Blit(rt2, rt1, _Material);
}
Graphics.Blit(rt1, dst, _Material);
RenderTexture.ReleaseTemporary(rt1);
RenderTexture.ReleaseTemporary(rt2);
}
}
private float GetWeight(float x, float y, float rho) {
return Mathf.Exp(-(x * x + y * y) / (2.0f * rho * rho));
}
private float GetTotalWeight()
{
float totalWeight = 0;
float rho = _BlurRadius / 3.0f;
for (int w = -(int)_BlurRadius; w <= _BlurRadius; w++) {
for (int h = -(int)_BlurRadius; h <= _BlurRadius; h++) {
float weight = GetWeight(w, h, rho);
totalWeight += weight;
}
}
return totalWeight;
}
}
Shader:
Shader "Custom/GaussBlur2"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_TexelSize;
half _BlurRadius;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uvs[5] : TEXCOORD1;
};
v2f vert_VerticalBlur(appdata v){
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uvs[0] = v.uv;
o.uvs[1] = v.uv + float2(0, _MainTex_TexelSize.y * 1) * _BlurRadius;
o.uvs[2] = v.uv + float2(0, _MainTex_TexelSize.y * -1) * _BlurRadius;
o.uvs[3] = v.uv + float2(0, _MainTex_TexelSize.y * 2) * _BlurRadius;
o.uvs[4] = v.uv + float2(0, _MainTex_TexelSize.y * -2) * _BlurRadius;
return o;
}
v2f vert_HorizontalBlur(appdata v){
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uvs[0] = v.uv;
o.uvs[1] = v.uv + float2(_MainTex_TexelSize.x * 1, 0) * _BlurRadius;
o.uvs[2] = v.uv + float2(_MainTex_TexelSize.x * -1, 0) * _BlurRadius;
o.uvs[3] = v.uv + float2(_MainTex_TexelSize.x * 2, 0) * _BlurRadius;
o.uvs[4] = v.uv + float2(_MainTex_TexelSize.x * -2, 0) * _BlurRadius;
return o;
}
fixed4 fragBlur (v2f i) : SV_Target
{
half weight[3] = {0.4026, 0.2442, 0.0545};
fixed4 col = tex2D(_MainTex, i.uvs[0]) * weight[0];
for(int j = 1; j < 3; j++)
{
col += tex2D(_MainTex, i.uvs[2*j - 1]) * weight[j];
col += tex2D(_MainTex, i.uvs[2*j]) * weight[j];
}
return col;
}
ENDCG
ZTest Always
Cull Off
ZWrite Off
//Pass1
Pass
{
NAME "GAUSSIAN_BLUR_VERTICAL"
CGPROGRAM
#pragma vertex vert_VerticalBlur
#pragma fragment fragBlur
ENDCG
}
//Pass2
Pass
{
NAME "GAUSSIAN_BLUR_HORIZONTAL"
CGPROGRAM
#pragma vertex vert_HorizontalBlur
#pragma fragment fragBlur
ENDCG
}
}
FallBack Off
}
C#:
using UnityEngine;
public class GaussBlur2 : PostEffectBase
{
[Range(0.1f, 10)]
public float _BlurRadius = 0.1f;
[Range(0, 10)]
public int downSample = 1;
[Range(0, 10)]
public int iteration = 1;
public void OnRenderImage(RenderTexture src, RenderTexture dst)
{
if (_Material)
{
RenderTexture rt1 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
RenderTexture rt2 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
_Material.SetFloat("_BlurRadius", _BlurRadius);
Graphics.Blit(src, rt1, _Material, 1);
for (int i = 0; i < iteration; i++)
{
Graphics.Blit(rt1, rt2, _Material, 0);
Graphics.Blit(rt2, rt1, _Material, 1);
}
Graphics.Blit(rt1, dst, _Material, 0);
RenderTexture.ReleaseTemporary(rt1);
RenderTexture.ReleaseTemporary(rt2);
}
}
}
这两版原理上是一样的,只是后者是借鉴前人的,而前者是自己对高斯计算做了封装,增加了模糊半径的动态调整,并合为一个Pass。
这个是应需求而生的效果,让模糊度与当前像素与屏幕中心的距离相关联。仔细观察,中间清晰,周围模糊,类似于吃鸡游戏里的开镜效果,嗯……暂且就叫中心模糊吧。
Shader:
Shader "Custom/CenterBlur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
half _BlurRadius;
half _CenterRadius;
float totalWeight;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 uv : TEXCOORD0;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.uv;
float2 uv1 = o.uv.xy;
o.uv.zw = o.uv.xy;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv.w = 1 - o.uv.w;
#endif
return o;
}
half GetWeight(float x, float y, float rho)
{
return exp(-(x*x + y*y)/(2.0f*rho*rho));
}
fixed4 GaussBlur(v2f i)
{
fixed4 finalColor = fixed4(0, 0, 0, 0);
half rho = _BlurRadius / 3.0f;
for(int x = -_BlurRadius; x<= _BlurRadius; x++)
{
for(int y = -_BlurRadius; y<= _BlurRadius; y++)
{
half wt = GetWeight(x, y, rho)/totalWeight;
fixed4 col = tex2D(_MainTex, i.uv + float2(x, y) * _MainTex_TexelSize.xy);
finalColor += col * wt;
}
}
return finalColor;
}
ENDCG
SubShader
{
Tags { "RenderType"="Opaque" }
//Pass1
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 frag (v2f i) : SV_Target
{
fixed4 texCol = tex2D(_MainTex, i.uv);
fixed4 blur = GaussBlur(i);
half radius = length(i.uv - fixed2(0.5f,0.5f));
half dis = max(0, min(1, _CenterRadius - radius));
fixed4 finalCol = lerp(blur, texCol, dis);
return finalCol;
}
ENDCG
}
//Pass2
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
int showFactor = 0;
sampler2D _BlurTex;
fixed4 frag (v2f i) : SV_Target
{
fixed4 texCol = tex2D(_MainTex, i.uv.xy);
fixed4 blur = tex2D(_BlurTex, i.uv.zw);
half radius = length(i.uv - fixed2(0.5f,0.5f));
half factor = max(0, min(1, _CenterRadius - radius));
fixed4 finalCol = lerp(blur, texCol, factor);
fixed4 factorCol = fixed4(1,1,1,1) * factor;
finalCol = lerp(finalCol, factorCol, showFactor);
return finalCol;
}
ENDCG
}
}
}
C#:
using UnityEngine;
public class CenterBlur : PostEffectBase
{
public bool ShowFactor = false;
[Range(0.1f, 10)]
public float _BlurRadius = 0.1f;
[Range(0.1f, 1.8f)]
public float CenterRadius = 0.1f;
[Range(0, 10)]
public int downSample = 1;
[Range(0, 10)]
public int iteration = 1;
public void OnRenderImage(RenderTexture src, RenderTexture dst)
{
if (_Material)
{
RenderTexture rt1 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
RenderTexture rt2 = RenderTexture.GetTemporary(src.width >> downSample, src.height >> downSample, 0, src.format);
_Material.SetFloat("_BlurRadius", _BlurRadius);
_Material.SetFloat("_CenterRadius", CenterRadius);
_Material.SetFloat("totalWeight", GetTotalWeight());
_Material.SetFloat("showFactor", ShowFactor ? 1 : 0);
Graphics.Blit(src, rt1, _Material, 0);
for (int i = 0; i < iteration; i++)
{
Graphics.Blit(rt1, rt2, _Material, 0);
Graphics.Blit(rt2, rt1, _Material, 0);
}
_Material.SetTexture("_BlurTex", rt1);
Graphics.Blit(src, dst, _Material, 1);
RenderTexture.ReleaseTemporary(rt1);
}
else
{
Graphics.Blit(src, dst);
}
}
private float GetWeight(float x, float y, float rho) {
return Mathf.Exp(-(x * x + y * y) / (2.0f * rho * rho));
}
private float GetTotalWeight() {
float totalWeight = 0;
float rho = _BlurRadius / 3.0f;
for (int w = -(int)_BlurRadius; w <= _BlurRadius; w++) {
for (int h = -(int)_BlurRadius; h <= _BlurRadius; h++) {
float weight = GetWeight(w, h, rho);
totalWeight += weight;
}
}
return totalWeight;
}
}
为了更好的观察当前模糊范围,可以把ShowFactor打开,然后来调整参数。其中,越黑的地方越模糊:
最后,由于后处理的“失之毫厘差之千里”特性,所以这次用的图都比较大,由于文章排版限制,图可能会比较小,你可以把图片单独拖出来看,大图看着会更清晰,对比差异更明显哦!OK,今天就到这里,如果有更多更好的效果,欢迎交流讨论,共同学习进步!