1. Bloom效果
Bloom描述的是图像中较亮的部分向周围一定范围内发生扩散,造成一种朦胧的效果,常用于表现游戏中的灯光或隧道出口之类的效果。
下面的例子将实现一个简单的Bloom效果,其原理是:
- 将原始图像中较亮(灰度值超过阈值)的部分提取出来,存储到一张临时纹理上
- 对提取出来的亮部做高斯模糊,使图像发散
- 将高斯模糊过的亮部与原始图像进行叠加
这里我们第一次涉及到图像翻转的问题:由于OpenGL平台和DX平台对屏幕空间的Y轴方向定义是相反的,而Unity的渲染底层是基于OpenGL标准,因此我们在Unity中抓取的屏幕图像,在DX平台上直接使用就存在上下颠倒的问题。为了避免这种情况,在大部分情况下Unity会在Graphics.Blit时自动进行翻转,但像本次Bloom效果这种,在Blit中需要对多张纹理进行采样时,就需要我们手动进行翻转
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0)
o.uv.w = 1 - o.uv.w;
#endif
测试脚本
using UnityEngine;
public class PostEffect_Bloom : PostEffectBase
{
public Shader BloomShader;
public Material BlooMaterial;
[Range(0.0f, 1.0f)]
public float BrightThreshold = 0.5f;
[Range(1, 8)]
public int DowmSampler = 4;
[Range(0.1f, 3.0f)]
public float SmaplerStep = 1;
[Range(1, 8)]
public int BlurRound = 1;
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Material _mat = CheckShaderAndMaterial(BloomShader, BlooMaterial);
if (null == _mat) Graphics.Blit(src, dest);
else
{
_mat.SetFloat("_BrightThreshold", BrightThreshold);
int _width = src.width / DowmSampler;
int _height = src.height / DowmSampler;
RenderTexture _buffer_0 = RenderTexture.GetTemporary(_width, _height, 0);
_buffer_0.filterMode = FilterMode.Bilinear;
//用第一个Pass将亮部提取出来
Graphics.Blit(src, _buffer_0, _mat, 0);
for (int i = 0; i < BlurRound; i++)
{
_mat.SetFloat("_SmaplerStep", i * SmaplerStep);
//使用第二个Pass横向滤波
RenderTexture _buffer_1 = RenderTexture.GetTemporary(_width, _height, 0);
Graphics.Blit(_buffer_0, _buffer_1, _mat, 1);
RenderTexture.ReleaseTemporary(_buffer_0);
_buffer_0 = _buffer_1;
//使用第三个Pass纵向滤波
_buffer_1 = RenderTexture.GetTemporary(_width, _height, 0);
Graphics.Blit(_buffer_0, _buffer_1, _mat, 2);
RenderTexture.ReleaseTemporary(_buffer_0);
_buffer_0 = _buffer_1;
}
//使用第四个Pass将处理后的亮部与原始图像混合
_mat.SetTexture("_BloomTex", _buffer_0);
Graphics.Blit(src, dest, _mat, 3);
RenderTexture.ReleaseTemporary(_buffer_0);
Graphics.Blit(src, dest, _mat);
}
}
}
测试Shader
Shader "MyShader/Chapter_12/Chapter_12_Bloom_Shader"
{
Properties
{
_MainTex("MainTex", 2D) = "white"{}
}
SubShader
{
ZTest Always ZWrite Off Cull Off
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_TexelSize;
fixed _BrightThreshold;
sampler2D _BloomTex;
half _SmaplerStep;
struct v2f_ExtractBright
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f_ExtractBright vert_ExtractBright(appdata_img v)
{
v2f_ExtractBright o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed Luminance(fixed4 _color)
{
return 0.2125 * _color.r + 0.7154 * _color.g + 0.0721 * _color.b;
}
fixed4 frag_ExtractBright(v2f_ExtractBright i) : SV_Target
{
fixed4 _samplerColor = tex2D(_MainTex, i.uv);
fixed _fixValue = clamp(Luminance(_samplerColor) - _BrightThreshold, 0, 1);
return _samplerColor * _fixValue;
}
struct v2f_Mix
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
};
v2f_Mix vert_Mix(appdata_img v)
{
v2f_Mix o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord;
o.uv.zw = v.texcoord;
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0)
o.uv.w = 1 - o.uv.w;
#endif
return o;
}
fixed4 frag_Mix(v2f_Mix i) : SV_Target
{
return tex2D(_MainTex, i.uv.xy) + tex2D(_BloomTex, i.uv.zw);
}
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert_ExtractBright
#pragma fragment frag_ExtractBright
ENDCG
}
//调用已有的高斯模糊PASS
UsePass "MyShader/Chapter_12/Chapter_12_GaussianBlur_Shader/GAUSSIAN_BLUR_HORIZENTAL"
UsePass "MyShader/Chapter_12/Chapter_12_GaussianBlur_Shader/GAUSSIAN_BLUR_VERTICAL"
Pass
{
CGPROGRAM
#pragma vertex vert_Mix
#pragma fragment frag_Mix
ENDCG
}
}
}
测试效果
2. 运动模糊
运动模糊的原理是,当物体发生位置变化时,前后帧的影像发生叠加,造成模糊的效果。通过运动模糊可以更好的表现速度感。
下面的例子将通过累积缓存的方式实现一个运动模糊的效果:
- 创建一张临时纹理,用于记录已经渲染过的图像,即过去帧的影像
- 设置过去帧的混合系数(MixAmount),用于控制过去帧的模糊影像的清晰程度
- 在第一个 Pass 中将当前帧的影像与保存的过去帧图像进行混合,混合系数为(1 - 过去帧的 MixAmount),这一步的目的只是为了在同一张图片上能够同时表现出物体在不同时刻的位置,因此我们只需要表现物体颜色的 RGB 部分,而并不希望混合时也改变 Alpha 值,这里可以通过 ColorMask RGB 实现只写入 RGB 通道的值
- 最终渲染图像的 Alpha 值应以当前帧的实际 Alpha 值为准,因此在第二个 Pass 中再次对当前帧原始图像进行采样,通过 Color Mask A 只将Alpha值写入
测试脚本
using UnityEngine;
public class PostEffect_MotionBlur : PostEffectBase
{
public Shader MotionBlurShader;
public Material MotionBlurMat;
/// <summary>
/// 已渲染图像在混合中的权重
/// </summary>
[Range(0, 1)]
public float PreviousAmount = 0.5f;
//用于记录已经渲染的图像
private RenderTexture mAccRT;
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Material _mat = CheckShaderAndMaterial(MotionBlurShader, MotionBlurMat);
if(null == _mat) Graphics.Blit(src, dest);
else
{
CheckAccRT(src.width, src.height);
_mat.SetFloat("_MixAmount", 1 - PreviousAmount);
//调用 Shader 进行混合
//先将结果混合到 AccRT 中,用于保存结果给下一帧使用
Graphics.Blit(src, mAccRT, _mat);
//再将混合结果输出
Graphics.Blit(mAccRT, dest);
}
}
/// <summary>
/// 检测当前 AccRT 是否已创建以及是否与本帧尺寸一致
/// </summary>
/// <param name="_width"></param>
/// <param name="_height"></param>
private void CheckAccRT(int _width, int _height)
{
if (null == mAccRT || _width != mAccRT.width || _height != mAccRT.height)
{
DestroyImmediate(mAccRT);
mAccRT = new RenderTexture(_width, _height, 0);
mAccRT.hideFlags = HideFlags.HideAndDontSave;
}
}
private void OnDestroy()
{
DestroyImmediate(mAccRT);
}
}
测试Shader
Shader "MyShader/Chapter_12/Chapter_12_MotionBlur_Shader"
{
Properties
{
_MainTex("MainTex", 2D) = "white"{}
}
SubShader
{
ZTest Always ZWrite Off Cull Off
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed _MixAmount;
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag_RGB(v2f i) : SV_Target
{
return fixed4(tex2D(_MainTex, i.uv).rgb, _MixAmount);
}
fixed4 frag_A(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
CGPROGRAM
#pragma vertex vert
#pragma fragment frag_RGB
ENDCG
}
Pass
{
Blend One Zero
ColorMask A
CGPROGRAM
#pragma vertex vert
#pragma fragment frag_A
ENDCG
}
}
}
测试效果