簡介
游戲都追求畫面感,而個人感覺后處理是最有效的表現(xiàn)畫面感的方式之一(雖然比較收費)。我以前玩過這種模糊效果。今天我來看看另一個模糊效果,徑向模糊。這種效果對提高畫面感非常有幫助,特別是在快速動作突然加速的瞬間,或是老板吼出來的畫面效果。所以我第一次想到天涯明月刀的光影工作瞬間飛升加速,使用徑向模糊使光影工作突然加速。

“王者的榮耀”也有徑向模糊的效果。

徑向模糊效果的原理
放射狀模糊效果是后處理,后處理的原理不多,用描繪的屏幕圖像進行全畫面操作。首先,看圖,從中心部分向外側(cè)擴大,感覺畫面向外延伸。因此,在fragment階段,之前的文章為了在線效果的后處理時露出輪廓,可以使用模糊效果對像素和周圍的像素進行加權(quán)平均這樣的想法進行處理。另一方面,關(guān)于放射狀模糊也一樣,關(guān)于各像素點,雖然不一定是畫面的正中心,自己可以指定)求出1個方向,從中心朝向該像素點的方向是放射狀的模糊方向,其次是現(xiàn)在的像素點以及放射狀。進一步將幾個點作為抽樣點取得,抽樣點越靠近中心越緊密,變得疏松。最后,這個像素點的輸出是這些抽樣點的平均值。.像這樣,在靠近中心點的位置,取樣距離小,幾乎為0。沒有模糊,距離越近取樣距離越大,圖像變得模糊。
放射狀模糊的原理如下.

徑向模糊在Unity下的實現(xiàn)
了解原理后,我們在Unity下實現(xiàn)了1版放射狀模糊效果.在后期處理的組合中,C#腳本部分RadialBlurEffect類繼承了PostEffectBase類。這個等級在以前的報道中進行了說明,但是這里不顯示。
RadialBlurEffect腳本:
using UnityEngine;
 
public class RadialBlurEffect : PostEffectBase {
 
    //模糊程度,不能過高
    [Range(0,0.05f)]
    public float blurFactor = 1.0f;
    //模糊中心(0-1)屏幕空間,默認(rèn)為中心點
    public Vector2 blurCenter = new Vector2(0.5f, 0.5f);
 
void OnRenderImage (RenderTexture source, RenderTexture destination)
    {
        if (_Material)
        {
            _Material.SetFloat("_BlurFactor", blurFactor);
            _Material.SetVector("_BlurCenter", blurCenter);
            Graphics.Blit(source, destination, _Material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }   
} 
}
RadialBlurShader1:
Shader "ApcShader/PostEffect/RadialBlurShader1" 
{
Properties 
{
_MainTex ("Base (RGB)", 2D) = "white" {}
}
 
CGINCLUDE
uniform sampler2D _MainTex;
uniform float _BlurFactor; //模糊強度(0-0.05)
uniform float4 _BlurCenter; //模糊中心點xy值(0-1)屏幕空間
#include "UnityCG.cginc"
#define SAMPLE_COUNT 6 //迭代次數(shù)
 
fixed4 frag(v2f_img i) : SV_Target
{
//模糊方向為模糊中點指向邊緣(當(dāng)前像素點),而越邊緣該值越大,越模糊
float2 dir = i.uv - _BlurCenter.xy;
float4 outColor = 0;
//采樣SAMPLE_COUNT次
for (int j = 0; j < SAMPLE_COUNT; ++j)
{
//計算采樣uv值:正常uv值+從中間向邊緣逐漸增加的采樣距離
float2 uv = i.uv + _BlurFactor * dir * j;
outColor += tex2D(_MainTex, uv);
}
//取平均值
outColor /= SAMPLE_COUNT;
return outColor;
}
ENDCG
 
SubShader
{
Pass
{
ZTest Always
Cull Off
ZWrite Off
Fog{ Mode off }
 
//調(diào)用CG函數(shù)
CGPROGRAM
//使效率更高的編譯宏
#pragma fragmentoption ARB_precision_hint_fastest 
//vert_img是在UnityCG.cginc中定義好的,當(dāng)后處理vert階段計算常規(guī),可以直接使用自帶的vert_img
#pragma vertex vert_img
#pragma fragment frag 
ENDCG
}
}
Fallback off
}
我們在尋找場景并測試其效果。以下是未啟用“徑向模糊”效果的原始場景。

開啟徑向模糊效果后:

調(diào)整模糊強度:

徑向模糊的優(yōu)化
徑向模糊和高斯模糊,均值模糊等模糊效果一樣,都是采樣次數(shù)越高,模糊效果越好,但是采樣次數(shù)高了,性能就下去了,尤其是在移動設(shè)備上,GPU不強,但是分辨率極高,后處理這種全屏紋理采樣及其耗費性能。所以,優(yōu)化很重要。我們在高斯模糊以及Bloom效果中使用了降分辨率的操作,對于徑向模糊,一樣實用。優(yōu)化的思路如下:首先,我們將圖像渲染到一張降低了分辨率的RT上,然后使用這個降低了分辨率的RT進行上面的模糊處理;最后再將這個RT與原始圖像進行插值操作。這樣會多一個Pass(一個Draw Call),但是由于降低了分辨率,仍然對性能會有較好的提升。下面附上更改后的徑向模糊代碼。
RadialBlurEffect2腳本:
using UnityEngine;
 
public class RadialBlurEffect2 : PostEffectBase {
 
    //模糊程度,不能過高
    [Range(0,0.1f)]
    public float blurFactor = 1.0f;
    //清晰圖像與原圖插值
    [Range(0.0f, 2.0f)]
    public float lerpFactor = 0.5f;
    //降低分辨率
    public int downSampleFactor = 2;
    //模糊中心(0-1)屏幕空間,默認(rèn)為中心點
    public Vector2 blurCenter = new Vector2(0.5f, 0.5f);
 
void OnRenderImage (RenderTexture source, RenderTexture destination)
    {
        if (_Material)
        {
            //申請兩塊降低了分辨率的RT
            RenderTexture rt1 = RenderTexture.GetTemporary(source.width >> downSampleFactor, source.height >> downSampleFactor, 0, source.format);
            RenderTexture rt2 = RenderTexture.GetTemporary(source.width >> downSampleFactor, source.height >> downSampleFactor, 0, source.format);
            Graphics.Blit(source, rt1);
 
            //使用降低分辨率的rt進行模糊:pass0
            _Material.SetFloat("_BlurFactor", blurFactor);
            _Material.SetVector("_BlurCenter", blurCenter);
            Graphics.Blit(rt1, rt2, _Material, 0);
 
            //使用rt2和原始圖像lerp:pass1
            _Material.SetTexture("_BlurTex", rt2);
            _Material.SetFloat("_LerpFactor", lerpFactor);
            Graphics.Blit(source, destination, _Material, 1);
 
            //釋放RT
            RenderTexture.ReleaseTemporary(rt1);
            RenderTexture.ReleaseTemporary(rt2);
        }
        else
        {
            Graphics.Blit(source, destination);
        }   
} 
}

RadialBlurEffect2.shader:

Shader "ApcShader/PostEffect/RadialBlurShader2" 
{
Properties 
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_BlurTex("Blur Tex", 2D) = "white"{}
}
 
CGINCLUDE
uniform sampler2D _MainTex;
uniform sampler2D _BlurTex;
uniform float _BlurFactor; //模糊強度(0-0.05)
uniform float _LerpFactor;  //插值的強度(0-1)
uniform float4 _BlurCenter; //模糊中心點xy值(0-1)屏幕空間
float4 _MainTex_TexelSize;
#include "UnityCG.cginc"
#define SAMPLE_COUNT 6 //迭代次數(shù)
 
fixed4 frag_blur(v2f_img i) : SV_Target
{
//模糊方向為模糊中點指向邊緣(當(dāng)前像素點),而越邊緣該值越大,越模糊
float2 dir = i.uv - _BlurCenter.xy;
float4 outColor = 0;
//采樣SAMPLE_COUNT次
for (int j = 0; j < SAMPLE_COUNT; ++j)
{
//計算采樣uv值:正常uv值+從中間向邊緣逐漸增加的采樣距離
float2 uv = i.uv + _BlurFactor * dir * j;
outColor += tex2D(_MainTex, uv);
}
//取平均值
outColor /= SAMPLE_COUNT;
return outColor;
}
 
//定義最后插值使用的結(jié)構(gòu)體
struct v2f_lerp
{
float4 pos : SV_POSITION;
float2 uv1 : TEXCOORD0; //uv1
float2 uv2 : TEXCOORD1; //uv2
};
v2f_lerp vert_lerp(appdata_img v)
{
v2f_lerp o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv1 = v.texcoord.xy;
o.uv2 = v.texcoord.xy;
//dx中紋理從左上角為初始坐標(biāo),需要反向(在寫rt的時候需要注意)
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv2.y = 1 - o.uv2.y;
#endif
return o;
}
 
fixed4 frag_lerp(v2f_lerp i) : SV_Target
{
float2 dir = i.uv1 - _BlurCenter.xy;
float dis = length(dir);
fixed4 oriTex = tex2D(_MainTex, i.uv1);
fixed4 blurTex = tex2D(_BlurTex, i.uv2);
//按照距離乘以插值系數(shù)在原圖和模糊圖之間差值
return lerp(oriTex, blurTex, _LerpFactor * dis);
}
ENDCG
 
SubShader
{
//Pass 0 模糊操作
Pass
{
ZTest Always
Cull Off
ZWrite Off
Fog{ Mode off }
 
//調(diào)用CG函數(shù)
CGPROGRAM
//使效率更高的編譯宏
#pragma fragmentoption ARB_precision_hint_fastest 
//vert_img是在UnityCG.cginc中定義好的,當(dāng)后處理vert階段計算常規(guī),可以直接使用自帶的vert_img
#pragma vertex vert_img
#pragma fragment frag_blur 
ENDCG
}
 
//Pass 1與原圖插值操作
Pass
{
ZTest Always
Cull Off
ZWrite Off
Fog{ Mode off }
 
//調(diào)用CG函數(shù)
CGPROGRAM
//使效率更高的編譯宏
#pragma fragmentoption ARB_precision_hint_fastest 
//vert_img是在UnityCG.cginc中定義好的,當(dāng)后處理vert階段計算常規(guī),可以直接使用自帶的vert_img
#pragma vertex vert_lerp
#pragma fragment frag_lerp 
ENDCG
}
}
Fallback off

還是剛才的場景,使用新版本的后處理效果的結(jié)果如下:

這里我們降低了2倍分辨率,極大地降低了采樣帶來的消耗。雖然效果也打了些折扣,不過還是可以湊合看的,哈哈。
?
UNITY_UV_STARTS_AT_TOP宏
?
我們寫后處理shader時經(jīng)常遇到的一個問題就是有時候開啟后處理效果時屏幕上下顛倒了(或者疊加上去的部分上下顛倒了),在之前的屏幕水波紋效果中就遇到了類似的問題。那么有時候是什么時候呢?需要滿足幾個條件,第一,使用DX渲染器時(也就是在PC平臺);第二,開啟了抗鋸齒(AA);第三,開啟了后處理并且在后處理中使用了除了MainTex外的屏幕RT(或者后處理中跟屏幕像素相關(guān)的值直接傳入shader),比如上面的shader中我們除了MainTex外額外傳入了一張BlurTex,我們?nèi)匀挥肕ainTex的uv來處理時。同時滿足了上面三點,就會出現(xiàn)屏幕上下顛倒的現(xiàn)象。如下圖所示:

本人第一次遇到這個問題時瞬間想到《艾?!纷詈笈园椎哪嵌蜝oss戰(zhàn),旁白直接把屏幕倒轉(zhuǎn)過來,在《地獄邊境》中也有類似的上下顛倒的關(guān)卡。shader能做的東西好多,之前一直沒想到過那種上下屏幕顛倒是怎么做的,再仔細(xì)一想,其實只要在正常做,增加一個后處理,把采樣的uv的y坐標(biāo)反過來,就可以了。
?
那么,為什么會出現(xiàn)這種屏幕翻轉(zhuǎn)的問題,又為什么滿足這幾個條件才會有這種屏幕翻轉(zhuǎn)的問題呢?引用一段官方文檔上的解釋:
Direct3D-like: The coordinate is 0 at the top and increases downward. This applies to Direct3D, Metal and consoles.
OpenGL-like: The coordinate is 0 at the bottom and increases upward. This applies to OpenGL and OpenGL ES.
This difference tends not to have any effect on your project, other than when rendering into a Render Texture. When rendering into a Texture on a Direct3D-like platform, Unity internally flips rendering upside down. This makes the conventions match between platforms, with the OpenGL-like platform convention the standard.Image Effects and rendering in UV space are two common cases in the Shaders where you need to take action to ensure that the different coordinate conventions do not create problems in your project.
When you use Image Effects and anti-aliasing, the resulting source Texture for an Image Effect is not flipped to match the OpenGL-like platform convention. In this case, Unity renders to the screen to get anti-aliasing and then resolves rendering into a Render Texture for further processing with an Image Effect.
If your Image Effect is a simple one that processes one Render Texture at a time, Graphics.Blit deals with the inconsistent coordinates. However, if you’re processing more than one Render Texture together in your Image Effect, the Render Textures are likely to come out at different vertical orientations in Direct3D-like platforms and when you use anti-aliasing. To standardise the coordinates, you need to manually “flip” the screen Texture upside down in your Vertex Shader so that it matches the OpenGL-like coordinate standard.
由于DX和OpenGL之間的區(qū)別,Unity為了跨平臺,為我們處理了兩個圖形API紋理坐標(biāo)不同的問題,但是不是任何時候都為我們自動處理,當(dāng)我們用后處理(也就是寫入RT)并且開啟了抗鋸齒的時候,就不會為我們翻轉(zhuǎn)。Unity就是這么設(shè)定的,當(dāng)有這種情況的話我們就需要自己處理這種平臺差異。我們通過#if UNITY_UV_STARTS_AT_TOP
就可以判斷是否是DX系列平臺,正常的OpenGL從下到上的紋素為正,但是到DX下改成從下到上,如果主紋理的uv值y方向反了,那么這個_MainTex_TexelSize.y就小于0。我們通過這樣一個判斷語句,就可以自己處理平臺之間的差異了。上面的shader中就是增加了這樣一句判斷,就解決了在PC上出現(xiàn)反向的問題。