轉(zhuǎn)自: http://blog.csdn.net/poem_qianmo/article/details/49405909
原始場景效果圖:
開啟運(yùn)動(dòng)模糊特效后的場景效果圖:
圖先就上這兩張。文章末尾有更多的運(yùn)行截圖,并提供了源工程的下載。先放出可運(yùn)行的exe下載,如下:
【可運(yùn)行的exe游戲場景請(qǐng)點(diǎn)擊這里下載試玩】
好的,正文開始。
一、Unity5中新的Shader體系簡析
Unity5和之前的書寫模式有了一定的改變。Unity5時(shí)代的Shader Reference官方文檔也進(jìn)一步地變得豐滿。
主要需要了解到的是,在原來的Unity中,若想要新建一個(gè)Shader源文件,不考慮compute shader的話,僅有一種Shader模板供選擇。而自從Unity5.1起(好像是Unity5.1)
想在Unity5.1之后的版本中新建Shader,【右鍵在Project窗口中單擊】->【Create】,會(huì)出現(xiàn)如下的四個(gè)選項(xiàng):
而由于暫時(shí)不考慮compute shader。所以,新版Unity中有三種基本的Shader模板分別為:
Standard Surface Shader標(biāo)準(zhǔn)表面著色器
Unlit Shader 無燈光著色器
Image Effect Shader 圖像特效著色器
二、Unity5中新的Shader模板源碼解析
下面,對(duì)Unity5中三種基本Shader模板進(jìn)行逐行注釋與思路解析。
可以點(diǎn)擊這里跳轉(zhuǎn)到Github,查看詳細(xì)注釋好的三種Shader模板的源碼。
2.1 標(biāo)準(zhǔn)表面著色器(Standard Surface Shader)模板源碼解析
在Unity中,我們?nèi)粢獙?shí)現(xiàn)新的表面著色器時(shí),可以根據(jù)這個(gè)模板,進(jìn)行一步添加子著色器和新的參數(shù)與特性。
這個(gè)Shader模板的脈絡(luò)很清晰,先是定義一些屬性,然后在SubShader中設(shè)置渲染模式,層次細(xì)節(jié)LOD的值,然后開啟一個(gè)CG編程語言模塊,寫一些編譯指令#pragma,聲明一下變量讓屬性值在CG塊中可見,定義輸入結(jié)構(gòu),然后填充一下表面著色函數(shù)即可。注意:專門強(qiáng)調(diào)一句,SurfaceShader不能使用Pass,一使用就報(bào)錯(cuò),我們直接在SubShader中實(shí)現(xiàn)和填充代碼就可以了。
Standard Surface Shader模板詳細(xì)注釋的Shader代碼如下:
[cpp] view plain copy
Shader "淺墨Shader編程/Volume8/Surface Shader模板"
{
//------------------------------------【屬性值】------------------------------------
Properties
{
//主顏色
_Color("Color", Color) = (1,1,1,1)
//主紋理
_MainTex("Albedo (RGB)", 2D) = "white" {}
//光澤度
_Glossiness("Smoothness", Range(0,1)) = 0.5
//金屬度
_Metallic("Metallic", Range(0,1)) = 0.0
}
//------------------------------------【唯一的子著色器】------------------------------------
SubShader
{
//【注意:Surface Shader不能使用Pass,直接在SubShader中實(shí)現(xiàn)即可】
//渲染類型設(shè)置:不透明
Tags{"RenderType" = "Opaque" }
//細(xì)節(jié)層次設(shè)為:200
LOD200
//===========開啟CG著色器語言編寫模塊===========
CGPROGRAM
//編譯指令:告知編譯器表明著色函數(shù)的名稱為surf
//Standard表示光照模型為Unity標(biāo)準(zhǔn)版光照模型
//fullforwardshadows表示在正向渲染路徑中支持所有陰影類型
#pragma surface surf Standard fullforwardshadows
//編譯指令: 指定著色器編譯目標(biāo)為Shader Model 3.0
#pragma target 3.0
//變量的聲明
sampler2D _MainTex;
//表面輸入結(jié)構(gòu)體
struct Input
{
float2 uv_MainTex;//紋理坐標(biāo)
};
//變量的聲明
half _Glossiness;
half _Metallic;
fixed4 _Color;
//--------------------------------【表面著色函數(shù)】-----------------------------
//輸入:表面輸入結(jié)構(gòu)體
//輸出:Unity內(nèi)置的SurfaceOutputStandard結(jié)構(gòu)體
//SurfaceOutputStandard原型如下:
/*
struct SurfaceOutputStandard
{
fixed3 Albedo; // 漫反射顏色
fixed3 Normal; // 切線空間法線
half3 Emission; //自發(fā)光
half Metallic; // 金屬度;取0為非金屬, 取1為金屬
half Smoothness; // 光澤度;取0為非常粗糙, 取1為非常光滑
half Occlusion; // 遮擋(默認(rèn)值為1)
fixed Alpha; // 透明度
};
*/
//---------------------------------------------------------------------------------
void surf(Input IN, inout SurfaceOutputStandard o)
{
//【1】漫反射顏色為主紋理對(duì)應(yīng)的紋理坐標(biāo),并乘以主顏色
fixed4c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
//【2】將準(zhǔn)備好的顏色的rgb分量作為漫反射顏色
o.Albedo= c.rgb;
//【3】金屬度取自屬性值
o.Metallic= _Metallic;
//【4】光澤度也取自屬性值
o.Smoothness= _Glossiness;
//【5】將準(zhǔn)備好的顏色的alpha分量作為Alpha分量值
o.Alpha= c.a;
}
//===========結(jié)束CG著色器語言編寫模塊===========
ENDCG
}
//備胎為漫反射
FallBack"Diffuse"
}
接著來看Unity5的第二種Shader模板,無燈光著色器(Unlit Shader)模板。
2.2 無燈光著色器(Unlit Shader)模板源碼解析
Unlit Shader,簡單來說,就是直接采用漫反射紋理,不考慮場景中的任何燈光效果。使用無燈光著色器的話,也就不能使用任何鏡面或者法線效果了。Unlit系的Shader基本原理和其他Shader無異,但是計(jì)算量更小,更快速,更高效。
而在Unity內(nèi)置的各種著色器中,有如下的四種是Unlit系的:
好的,已經(jīng)稍微解釋了下什么是Unlit Shader。下面一起看一下Unity為我們提供的無燈光著色器模板的代碼:
[cpp] view plain copy
Shader "淺墨Shader編程/Volume8/無燈光著色器(Unlit Shader)模板"
{
//------------------------------------【屬性值】------------------------------------
Properties
{
//主紋理
_MainTex("Texture", 2D) = "white" {}
}
//------------------------------------【唯一的子著色器】------------------------------------
SubShader
{
//渲染類型設(shè)置:不透明
Tags{ "RenderType"="Opaque" }
//細(xì)節(jié)層次設(shè)為:100
LOD 100
//--------------------------------唯一的通道-------------------------------
Pass
{
//===========開啟CG著色器語言編寫模塊===========
CGPROGRAM
//編譯指令:告知編譯器頂點(diǎn)和片段著色函數(shù)的名稱
#pragma vertex vert
#pragma fragment frag
//著色器變體快捷編譯指令:霧效。編譯出幾個(gè)不同的Shader變體來處理不同類型的霧效(關(guān)閉/線性/指數(shù)/二階指數(shù))
#pragma multi_compile_fog
//包含頭文件
#include"UnityCG.cginc"
//頂點(diǎn)著色器輸入結(jié)構(gòu)
struct appdata
{
float4 vertex : POSITION;//頂點(diǎn)位置
float2 uv : TEXCOORD0;//紋理坐標(biāo)
};
//頂點(diǎn)著色器輸出結(jié)構(gòu)
struct v2f
{
float2 uv : TEXCOORD0;//紋理坐標(biāo)
UNITY_FOG_COORDS(1)//霧數(shù)據(jù)
float4 vertex : SV_POSITION;//像素位置
};
//變量聲明
sampler2D _MainTex;
float4 _MainTex_ST;
//--------------------------------【頂點(diǎn)著色函數(shù)】-----------------------------
//輸入:頂點(diǎn)輸入結(jié)構(gòu)體
//輸出:頂點(diǎn)輸出結(jié)構(gòu)體
//---------------------------------------------------------------------------------
v2f vert (appdata v)
{
//【1】實(shí)例化一個(gè)輸入結(jié)構(gòu)體
v2f o;
//【2】填充此輸出結(jié)構(gòu)
//輸出的頂點(diǎn)位置(像素位置)為模型視圖投影矩陣乘以頂點(diǎn)位置,也就是將三維空間中的坐標(biāo)投影到了二維窗口
o.vertex= mul(UNITY_MATRIX_MVP, v.vertex);
//【3】用UnityCG.cginc頭文件中內(nèi)置定義的宏,根據(jù)uv坐標(biāo)來計(jì)算真正的紋理上對(duì)應(yīng)的位置(按比例進(jìn)行二維變換)
o.uv= TRANSFORM_TEX(v.uv, _MainTex);
//【4】用UnityCG.cginc頭文件中內(nèi)置定義的宏處理霧效,從頂點(diǎn)著色器中輸出霧效數(shù)據(jù)
UNITY_TRANSFER_FOG(o,o.vertex);
//【5】返回此輸出結(jié)構(gòu)對(duì)象
return o;
}
//--------------------------------【片段著色函數(shù)】-----------------------------
//輸入:頂點(diǎn)輸出結(jié)構(gòu)體
//輸出:float4型的像素顏色值
//---------------------------------------------------------------------------------
fixed4 frag (v2f i) : SV_Target
{
//【1】采樣主紋理在對(duì)應(yīng)坐標(biāo)下的顏色值
fixed4 col = tex2D(_MainTex, i.uv);
//【2】用UnityCG.cginc頭文件中內(nèi)置定義的宏啟用霧效
UNITY_APPLY_FOG(i.fogCoord,col);
//【3】返回最終的顏色值
return col;
}
//===========結(jié)束CG著色器語言編寫模塊===========
ENDCG
}
}
}
不難分析得到,無燈光著色器是一種頂點(diǎn)&片段著色器,這邊模板給出的是單子著色器,單通道的寫法。
并且,無燈光著色器中使用了一些UnityCG.cginc頭文件中內(nèi)置的宏,比如說TRANSFORM_TEX、UNITY_TRANSFER_FOG、UNITY_APPLY_FOG。接下來分別把這三個(gè)宏簡單解釋一下。
2.2.1 TRANSFORM_TEX宏
TRANSFORM_TEX宏的定義為:
[cpp] view plain copy
define TRANSFORM_TEX(tex,name) (tex.xy *name##_ST.xy + name##_ST.zw)
其位于UnityCG.cginc(Unity5.2.1版本)的第266行。其可以根據(jù)uv坐標(biāo)來計(jì)算真正的紋理上對(duì)應(yīng)的位置(按比例進(jìn)行二維變換),組合上上文中定義的float4 _MainTex_ST,便可以計(jì)算真正的紋理上對(duì)應(yīng)的位置。
2.2.2 UNITY_TRANSFER_FOG宏
UNITY_TRANSFER_FOG宏的作用是從頂點(diǎn)著色輸出霧數(shù)據(jù)。在UnityCG.cginc(Unity5.2.1版本)的第772行起,具體定義如下:
[cpp] view plain copy
if (SHADER_TARGET < 30) ||defined(SHADER_API_MOBILE)
//手機(jī)端或者Shader Mode 2.0: 計(jì)算每個(gè)頂點(diǎn)的霧效因子
#define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord =unityFogFactor
#else
//Shader Mode 3.0和PC和游戲機(jī): 計(jì)算每像素的霧距離,和每像素的霧效因子
#define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord = (outpos).z
#endif
2.2.3 UNITY_APPLY_FOG宏
UNITY_APPLY_FOG宏的定義稍微有些長,從UnityCG.cginc(Unity 5.2.1版本)的第787行起:
[cpp] view plain copy
if defined(FOG_LINEAR) || defined(FOG_EXP)|| defined(FOG_EXP2)
#if(SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
//mobile or SM2.0: fog factor was already calculated per-vertex, so just lerp thecolor
#defineUNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_FOG_LERP_COLOR(col,fogCol,coord)
#else
//SM3.0 and PC/console: calculate fog factor and lerp fog color
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_CALC_FOG_FACTOR(coord);UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)
#endif
else
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol)
endif
ifdef UNITY_PASS_FORWARDADD
#define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0))
else
#define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor)
endif
可以發(fā)現(xiàn),UNITY_APPLY_FOG宏的作用是從頂點(diǎn)著色器中輸出霧效數(shù)據(jù),將第二個(gè)參數(shù)中的顏色值作為霧效的顏色值,且在正向附加渲染通道(forward-additive pass)中,自動(dòng)設(shè)置純黑色(fixed4(0,0,0,0))的霧效。其在定義中借助了UNITY_APPLY_FOG_COLOR宏,而我們也可以使用UNITY_APPLY_FOG_COLOR來指定特定顏色的霧效。
2.3 圖像特效著色器(Image Effect Shader) 模板源碼解析
這里的圖像特效一般指的就是屏幕圖像特效,在Camera加上各種濾鏡,比如說屏幕濺血,像素化,色調(diào)的調(diào)整,畫面模糊等效果。其也是一個(gè)頂點(diǎn)&片段著色器,且一般主要的操作集中在片段著色函數(shù)中。Unity為我們提供的模板,經(jīng)過詳細(xì)注釋后的源碼如下:
[cpp] view plain copy
Shader "淺墨Shader編程/Volume8/圖像特效Shader模板"
{
//------------------------------------【屬性值】------------------------------------
Properties
{
//主紋理
_MainTex("Texture", 2D) = "white" {}
}
//------------------------------------【唯一的子著色器】------------------------------------
SubShader
{
//關(guān)閉剔除操作
Cull Off
//關(guān)閉深度寫入模式
ZWrite Off
//設(shè)置深度測(cè)試模式:渲染所有像素.等同于關(guān)閉透明度測(cè)試(AlphaTestOff)
ZTest Always
//--------------------------------唯一的通道-------------------------------
Pass
{
//===========開啟CG著色器語言編寫模塊===========
CGPROGRAM
//編譯指令:告知編譯器頂點(diǎn)和片段著色函數(shù)的名稱
#pragma vertex vert
#pragma fragment frag
//包含頭文件
#include"UnityCG.cginc"
//頂點(diǎn)著色器輸入結(jié)構(gòu)
struct appdata
{
float4 vertex : POSITION;//頂點(diǎn)位置
float2 uv : TEXCOORD0;//一級(jí)紋理坐標(biāo)
};
//頂點(diǎn)著色器輸出結(jié)構(gòu)(v2f,vertex to fragment)
struct v2f
{
float2 uv : TEXCOORD0;//一級(jí)紋理坐標(biāo)
float4 vertex : SV_POSITION;//像素位置
};
//--------------------------------【頂點(diǎn)著色函數(shù)】-----------------------------
//輸入:頂點(diǎn)輸入結(jié)構(gòu)體
//輸出:頂點(diǎn)輸出結(jié)構(gòu)體
//---------------------------------------------------------------------------------
//頂點(diǎn)著色函數(shù)
v2f vert (appdata v)
{
//【1】實(shí)例化一個(gè)輸入結(jié)構(gòu)體
v2f o;
//【2】填充此輸出結(jié)構(gòu)
//輸出的頂點(diǎn)位置(像素位置)為模型視圖投影矩陣乘以頂點(diǎn)位置,也就是將三維空間中的坐標(biāo)投影到了二維窗口
o.vertex= mul(UNITY_MATRIX_MVP, v.vertex);
//輸入的UV紋理坐標(biāo)為頂點(diǎn)輸出的坐標(biāo)
o.uv= v.uv;
//【3】返回此輸出結(jié)構(gòu)對(duì)象
return o;
}
//變量的聲明
sampler2D _MainTex;
//--------------------------------【片段著色函數(shù)】-----------------------------
//輸入:頂點(diǎn)輸出結(jié)構(gòu)體
//輸出:float4型的像素顏色值
//---------------------------------------------------------------------------------
fixed4 frag (v2f i) : SV_Target
{
//【1】采樣主紋理在對(duì)應(yīng)坐標(biāo)下的顏色值
fixed4 col = tex2D(_MainTex, i.uv);
//【2】將顏色值反向
col= 1 - col;
//【3】返回最終的顏色值
return col;
}
//===========結(jié)束CG著色器語言編寫模塊===========
ENDCG
}
}
}
2.4 Shader模板中文注釋格式調(diào)整版替換
其實(shí)可以將Unity5中自帶的上述三個(gè)著色器模板,替換成上文中貼出來的、經(jīng)過詳細(xì)注釋和格式調(diào)整的Shader模板,這樣在每次新建Shader時(shí),就已經(jīng)得到了具有很高可讀性的Shader模板了,非常便捷。
一定要吐槽的是,Unity5.2.1自帶的三個(gè)Shader模板的縮進(jìn)和空格完全是混用的,導(dǎo)致在通過他們新建出來的Shader里面寫代碼的時(shí)候,格式非常混亂,十分影響新版Unity中Shader的編碼體驗(yàn)。很明顯,準(zhǔn)備此Shader模板的Unity開發(fā)人員的編碼習(xí)慣有點(diǎn)欠缺,得在這里點(diǎn)名批評(píng),輕噴一下。
淺墨在一發(fā)現(xiàn)他們格式有問題的時(shí)候就馬上替換掉了,所以現(xiàn)在在Unity中寫Shader代碼的體驗(yàn)是非常棒的。這邊也教大家如何替換掉自帶的3個(gè)模板。
Unity中Shader模板的位置是…Unity\Editor\Data\Resources\ScriptTemplates,比如說Unity安裝在D:\ProgramFiles\路徑下,整體路徑就是:
D:\ProgramFiles\Unity\Editor\Data\Resources\ScriptTemplates。
在此路徑下的3個(gè)txt,即為對(duì)應(yīng)的三個(gè)Shader模板文件:
83-Shader__Standard SurfaceShader-NewSurfaceShader.shader.txt
84-Shader__UnlitShader-NewUnlitShader.shader.txt
85-Shader__Image EffectShader-NewImageEffectShader.shader
這邊已經(jīng)將調(diào)整好格式,詳細(xì)注釋的三種模板準(zhǔn)備好了,下載之后,找到上面提到的…Unity\Editor\Data\Resources\ScriptTemplates目錄。替換掉對(duì)應(yīng)的3個(gè)txt文件即可。需要注意的是,如果你想自己DIY Shader模板,需要將txt保存為UTF-8編碼格式,否則可能會(huì)出現(xiàn)亂碼。
替換的模板下載地址在這里:
【Unity5-Shader模板中文注釋格式調(diào)整版替換文件】下載
另外還有一個(gè)小細(xì)節(jié)可以提一下。如果你安裝了兩個(gè)或者兩個(gè)以上的Unity5.1之后版本的Unity,如果你替換你當(dāng)前使用的Unity路徑下的模板文件后,新建的模板文件沒有改變的話,你試著將所有的Unity5.1之后版本的路徑下的這三個(gè)模板文件都進(jìn)行替換,應(yīng)該就可以實(shí)現(xiàn)想要的替換效果。淺墨的機(jī)器上就是同時(shí)存在Unity5.2.1和Unity5.2.0,然后使用Unity5.2.1,替換掉Unity5.2.1路徑下的三個(gè)模板文件后,并沒有發(fā)生變換。之后我按圖索驥,替換了Unity 5.2.0版路徑下的三個(gè)模板文化,才使得替換的模板文件生效。這估計(jì)是Unity多版本共存時(shí),自身的一個(gè)小bug。
三、運(yùn)動(dòng)模糊屏幕特效的實(shí)現(xiàn)
關(guān)于運(yùn)動(dòng)模糊特效,如果把握要要點(diǎn)的話,實(shí)現(xiàn)起來其實(shí)比較簡單,就是一個(gè)腳本文件配合一個(gè)Shader,便可以實(shí)現(xiàn)較為出色的運(yùn)動(dòng)模糊特效。而其中的腳本文件用于控制Shader中的外部參數(shù)。
也就是說一個(gè)屏幕特效通常分為兩部分來實(shí)現(xiàn):
Shader實(shí)現(xiàn)部分
腳本實(shí)現(xiàn)部分
下面我們對(duì)運(yùn)動(dòng)模糊屏幕特效的實(shí)現(xiàn)分別進(jìn)行簡單的描述。
可以點(diǎn)擊這里跳轉(zhuǎn)到Github,查看詳細(xì)注釋好的運(yùn)動(dòng)模糊屏幕特效的實(shí)現(xiàn)源碼。
3.1 Shader實(shí)現(xiàn)部分
先看一下Shader代碼的寫法,因?yàn)榛旧弦呀?jīng)逐行注釋,就不花時(shí)間和筆墨仔細(xì)講解了,詳細(xì)注釋的代碼如下:
[cpp] view plain copy
Shader "淺墨Shader編程/Volume8/運(yùn)動(dòng)模糊特效標(biāo)準(zhǔn)版"
{
//------------------------------------【屬性值】------------------------------------
Properties
{
_MainTex("主紋理 (RGB)", 2D) = "white" {}
_IterationNumber("迭代次數(shù)", Int)=16
}
//------------------------------------【唯一的子著色器】------------------------------------
SubShader
{
//--------------------------------唯一的通道-------------------------------
Pass
{
//設(shè)置深度測(cè)試模式:渲染所有像素.等同于關(guān)閉透明度測(cè)試(AlphaTest Off)
ZTest Always
//===========開啟CG著色器語言編寫模塊===========
CGPROGRAM
//編譯指令: 指定著色器編譯目標(biāo)為Shader Model 3.0
#pragma target 3.0
//編譯指令:告知編譯器頂點(diǎn)和片段著色函數(shù)的名稱
#pragma vertex vert
#pragma fragment frag
//包含輔助CG頭文件
#include "UnityCG.cginc"
//外部變量的聲明
uniform sampler2D _MainTex;
uniform float _Value;
uniform float _Value2;
uniform float _Value3;
uniform int _IterationNumber;
//頂點(diǎn)輸入結(jié)構(gòu)
struct vertexInput
{
float4 vertex : POSITION;//頂點(diǎn)位置
float4 color : COLOR;//顏色值
float2 texcoord : TEXCOORD0;//一級(jí)紋理坐標(biāo)
};
//頂點(diǎn)輸出結(jié)構(gòu)
struct vertexOutput
{
half2 texcoord : TEXCOORD0;//一級(jí)紋理坐標(biāo)
float4 vertex : SV_POSITION;//像素位置
fixed4 color : COLOR;//顏色值
};
//--------------------------------【頂點(diǎn)著色函數(shù)】-----------------------------
// 輸入:頂點(diǎn)輸入結(jié)構(gòu)體
// 輸出:頂點(diǎn)輸出結(jié)構(gòu)體
//---------------------------------------------------------------------------------
vertexOutput vert(vertexInput Input)
{
//【1】聲明一個(gè)輸出結(jié)構(gòu)對(duì)象
vertexOutput Output;
//【2】填充此輸出結(jié)構(gòu)
//輸出的頂點(diǎn)位置為模型視圖投影矩陣乘以頂點(diǎn)位置,也就是將三維空間中的坐標(biāo)投影到了二維窗口
Output.vertex = mul(UNITY_MATRIX_MVP, Input.vertex);
//輸出的紋理坐標(biāo)也就是輸入的紋理坐標(biāo)
Output.texcoord = Input.texcoord;
//輸出的顏色值也就是輸入的顏色值
Output.color = Input.color;
//【3】返回此輸出結(jié)構(gòu)對(duì)象
return Output;
}
//--------------------------------【片段著色函數(shù)】-----------------------------
// 輸入:頂點(diǎn)輸出結(jié)構(gòu)體
// 輸出:float4型的顏色值
//---------------------------------------------------------------------------------
float4 frag(vertexOutput i) : COLOR
{
//【1】設(shè)置中心坐標(biāo)
float2 center = float2(_Value2, _Value3);
//【2】獲取紋理坐標(biāo)的x,y坐標(biāo)值
float2 uv = i.texcoord.xy;
//【3】紋理坐標(biāo)按照中心位置進(jìn)行一個(gè)偏移
uv -= center;
//【4】初始化一個(gè)顏色值
float4 color = float4(0.0, 0.0, 0.0, 0.0);
//【5】將Value乘以一個(gè)系數(shù)
_Value *= 0.085;
//【6】設(shè)置坐標(biāo)縮放比例的值
float scale = 1;
//【7】進(jìn)行紋理顏色的迭代
for (int j = 1; j < _IterationNumber; ++j)
{
//將主紋理在不同坐標(biāo)采樣下的顏色值進(jìn)行迭代累加
color += tex2D(_MainTex, uv * scale + center);
//坐標(biāo)縮放比例依據(jù)循環(huán)參數(shù)的改變而變化
scale = 1 + (float(j * _Value));
}
//【8】將最終的顏色值除以迭代次數(shù),取平均值
color /= (float)_IterationNumber;
//【9】返回最終的顏色值
return color;
}
//===========結(jié)束CG著色器語言編寫模塊===========
ENDCG
}
}
}
可以發(fā)現(xiàn),這是一個(gè)單子著色器、單通道的頂點(diǎn)&片段著色器,頂點(diǎn)著色函數(shù)vert中基本上都是寫的比較中規(guī)中矩的代碼,精髓之處在于片段著色器frag中,用一個(gè)for
循環(huán),將像素顏色按照一條直線(uv * scale + center)進(jìn)行了迭代采樣累加,最終將采樣的顏色的總和除以采樣次數(shù),得到了想要實(shí)現(xiàn)的運(yùn)動(dòng)模糊效果。
3.2 腳本實(shí)現(xiàn)部分
腳本文件的實(shí)現(xiàn)方面,如下的即個(gè)點(diǎn)是要提出來專門講一下的,即Shader文件的獲取方法和OnRenderImage函數(shù)、Blit函數(shù)。
3.2.1 Shader文件的獲取
Shader文件的獲取可以使用Shader.Find函數(shù)實(shí)現(xiàn)。需要注意,Shader.Find函數(shù)參數(shù)應(yīng)該和Shader代碼中的名稱一致,也就是下面的代碼框架中xxx的值,而不是Shader的文件名:
[cpp] view plain copy
Shader "xxxx"
{
}
舉個(gè)例子,腳本代碼如果是這樣:
[cpp] view plain copy
CurShader = Shader.Find ("淺墨Shader編程/Volume8/運(yùn)動(dòng)模糊特效標(biāo)準(zhǔn)版");
那么獲取到的Shader,文件名是任意的,但Shader代碼框架肯定是這樣:
[cpp] view plain copy
Shader "淺墨Shader編程/Volume8/運(yùn)動(dòng)模糊特效標(biāo)準(zhǔn)版"
{
……
}
3.2.2 OnRenderImage函數(shù)與Blit函數(shù)
OnRenderImage()函數(shù)是MonoBehaviour中提供的一個(gè)可供我們重寫的函數(shù),它在unity完成所有圖片的渲染后被調(diào)用。所以我們想實(shí)現(xiàn)屏幕特效,主要依靠它來實(shí)現(xiàn)。而OnRenderImage函數(shù)的函數(shù)原型是:
[cpp] view plain copy
void OnRenderImage(RenderTexture sourceTexture,RenderTexture destTexture);
另外,我們需要配合一個(gè)Graphics.Blit函數(shù),實(shí)現(xiàn)從源紋理到目標(biāo)渲染紋理的拷貝過程,其原型如下三種:
[cpp] view plain copy
public static void Blit(Texture source,RenderTexture dest);
public static void Blit(Texture source,RenderTexture dest, Material mat, int pass = -1);
public static void Blit(Texture source,Material mat, int pass = -1);
其中。
第一個(gè)參數(shù),Texture類型的source,原始紋理。
第二個(gè)參數(shù),RenderTexture類型的dest,目標(biāo)渲染紋理,若為null,表示直接將原始紋理拷貝到屏幕之上。
第三個(gè)參數(shù),Material類型的mat,使用的材質(zhì)(其實(shí)也就是Shader),根據(jù)不同材質(zhì)的準(zhǔn)備,就是在這里實(shí)現(xiàn)后期的效果的。
第四個(gè)參數(shù),int類型的pass,有默認(rèn)值 -1,表示使用所有的pass。用于指定使用哪一個(gè)pass。
說個(gè)題外話,其實(shí)在很久之前的Win32 API游戲編程中,同樣原理和相似用途的Blit函數(shù)用得太多了。
好的,最后看一下實(shí)現(xiàn)屏幕特效的核心代碼,如下:
[cpp] view plain copy
void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
{
//著色器實(shí)例不為空,就進(jìn)行參數(shù)設(shè)置
if (CurShader != null)
{
//設(shè)置Shader中的外部變量
material.SetFloat("_IterationNumber", IterationNumber);
material.SetFloat("_Value", Intensity);
material.SetFloat("_Value2", OffsetX);
material.SetFloat("_Value3", OffsetY);
material.SetFloat("_Value4", blurWidth);
material.SetVector("_ScreenResolution", new Vector4(sourceTexture.width, sourceTexture.height, 0.0f, 0.0f));
//拷貝源紋理到目標(biāo)渲染紋理,加上我們的材質(zhì)效果
Graphics.Blit(sourceTexture, destTexture, material);
}
//著色器實(shí)例為空,直接拷貝屏幕上的效果。此情況下是沒有實(shí)現(xiàn)屏幕特效的
else
{
//直接拷貝源紋理到目標(biāo)渲染紋理
Graphics.Blit(sourceTexture, destTexture);
}
最后看一下詳細(xì)注釋后的腳本完整實(shí)現(xiàn)代碼:
[csharp] view plain copy
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class MotionBlurEffects : MonoBehaviour
{
//-------------------變量聲明部分-------------------
#region Variables
public Shader CurShader;//著色器實(shí)例
private Vector4 ScreenResolution;//屏幕分辨率
private Material CurMaterial;//當(dāng)前的材質(zhì)
[Range(5, 50)]
public float IterationNumber = 15;
[Range(-0.5f, 0.5f)]
public float Intensity = 0.125f;
[Range(-2f, 2f)]
public float OffsetX = 0.5f;
[Range(-2f, 2f)]
public float OffsetY = 0.5f;
public static float ChangeValue;
public static float ChangeValue2;
public static float ChangeValue3;
public static float ChangeValue4;
#endregion
//-------------------------材質(zhì)的get&set----------------------------
#region MaterialGetAndSet
Material material
{
get
{
if (CurMaterial == null)
{
CurMaterial = new Material(CurShader);
CurMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return CurMaterial;
}
}
#endregion
//-----------------------------------------【Start()函數(shù)】---------------------------------------------
// 說明:此函數(shù)僅在Update函數(shù)第一次被調(diào)用前被調(diào)用
//--------------------------------------------------------------------------------------------------------
void Start()
{
//依此賦值
ChangeValue = Intensity;
ChangeValue2 = OffsetX;
ChangeValue3 = OffsetY;
ChangeValue4 = IterationNumber;
//找到當(dāng)前的Shader文件
CurShader = Shader.Find("淺墨Shader編程/Volume8/運(yùn)動(dòng)模糊特效標(biāo)準(zhǔn)版");
//判斷是否支持屏幕特效
if (!SystemInfo.supportsImageEffects)
{
enabled = false;
return;
}
}
//-------------------------------------【OnRenderImage()函數(shù)】------------------------------------
// 說明:此函數(shù)在當(dāng)完成所有渲染圖片后被調(diào)用,用來渲染圖片后期效果
//--------------------------------------------------------------------------------------------------------
void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
{
//著色器實(shí)例不為空,就進(jìn)行參數(shù)設(shè)置
if (CurShader != null)
{
//設(shè)置Shader中的外部變量
material.SetFloat("_IterationNumber", IterationNumber);
material.SetFloat("_Value", Intensity);
material.SetFloat("_Value2", OffsetX);
material.SetFloat("_Value3", OffsetY);
material.SetVector("_ScreenResolution", new Vector4(sourceTexture.width, sourceTexture.height, 0.0f, 0.0f));
//拷貝源紋理到目標(biāo)渲染紋理,加上我們的材質(zhì)效果
Graphics.Blit(sourceTexture, destTexture, material);
}
//著色器實(shí)例為空,直接拷貝屏幕上的效果。此情況下是沒有實(shí)現(xiàn)屏幕特效的
else
{
//直接拷貝源紋理到目標(biāo)渲染紋理
Graphics.Blit(sourceTexture, destTexture);
}
}
//-----------------------------------------【OnValidate()函數(shù)】--------------------------------------
// 說明:此函數(shù)在編輯器中該腳本的某個(gè)值發(fā)生了改變后被調(diào)用
//--------------------------------------------------------------------------------------------------------
void OnValidate()
{
//將編輯器中的值賦值回來,確保在編輯器中值的改變立刻讓結(jié)果生效
ChangeValue4 = IterationNumber;
ChangeValue = Intensity;
ChangeValue2 = OffsetX;
ChangeValue3 = OffsetY;
}
//-----------------------------------------【Update()函數(shù)】------------------------------------------
// 說明:此函數(shù)在每一幀中都會(huì)被調(diào)用
//--------------------------------------------------------------------------------------------------------
void Update()
{
if (Application.isPlaying)
{
//賦值
IterationNumber = ChangeValue4;
Intensity = ChangeValue;
OffsetX = ChangeValue2;
OffsetY = ChangeValue3;
}
//找到對(duì)應(yīng)的Shader文件
if UNITY_EDITOR
if (Application.isPlaying != true)
{
CurShader = Shader.Find("淺墨Shader編程/Volume8/運(yùn)動(dòng)模糊特效標(biāo)準(zhǔn)版");
}
endif
}
//-----------------------------------------【OnDisable()函數(shù)】---------------------------------------
// 說明:當(dāng)對(duì)象變?yōu)椴豢捎没蚍羌せ顮顟B(tài)時(shí)此函數(shù)便被調(diào)用
//--------------------------------------------------------------------------------------------------------
void OnDisable()
{
if (CurMaterial)
{
DestroyImmediate(CurMaterial);
}
}
}
3.3 關(guān)于如何使用此特效
使用方面的話比較簡單,把腳本文件拖到主攝像機(jī)上面,效果就出來了。
腳本文件中有如下這些參數(shù)可以調(diào)整,得到不同的模糊效果:
Iteration Number- 迭代次數(shù)
Intensity - 模糊強(qiáng)度
Offset X - X方向上的偏移
Offset Y - Y方向上的偏移
四、最終的效果展示
這邊貼幾張場景的效果圖和使用了屏幕特效后的效果圖。需要注意的是,本次的場景效果,除了類似CS/CF的FPS游戲的控制系統(tǒng)以外,還可以使用鍵盤上的按鍵【F】,開啟或者關(guān)閉運(yùn)動(dòng)模糊特效。正如下圖所展示的:
下面放幾張測(cè)試截圖。
首先,Unity5中,導(dǎo)出的exe使用了新的片頭Logo,質(zhì)感不錯(cuò),好評(píng):
城鎮(zhèn)中的原始效果:
運(yùn)動(dòng)模糊后的效果:
海港原始效果:
海港運(yùn)動(dòng)模糊后的效果:
城鎮(zhèn)草叢前的效果:
城鎮(zhèn)草叢前運(yùn)動(dòng)模糊后的效果:
沙灘原始效果:
沙灘運(yùn)動(dòng)模糊后的效果:
海面原始效果:
海面運(yùn)動(dòng)模糊后的效果: