【Unity Shaders】ShadowGun系列之一――飛機墜毀的濃煙效果
來源:程序員人生 發布時間:2014-12-12 08:43:04 閱讀次數:7487次
寫在前面
最近1直在思考下面的學習該怎樣進行,固然自己有在1邊做項目1邊學OpenGL,偶爾翻翻論文之類的。但是,寫shader是1個需要實戰和動手經驗的進程,而模仿是前期學習的必經之路。很多人都會問,怎樣學shader,看甚么書。固然我經驗也不夠,目前的線路是:掌握1門著色語言+讀幾本經典書籍+學習優秀的shader實例+動手實踐+動手實踐+動手實踐。每個都不容易,所以學shader是1個漫長而艱辛的進程。
鐺鐺當~所以,在繼Surface Shader系列以后,我打算學習1下現在已有的各種案例shader。這些shader可能來自于某些網站,可能來自于開源項目,也可能來自于我自己看書的總結。而ShadowGun就是其中1個開源項目。
ShadowGun
ShadowGun其實最開始是2011年的1個移動平臺的第3人稱射擊游戲。固然,也是用Unity開發的。當年,由于在畫面上的出色表現贏得了很多眼球~更難能寶貴的是,在2012的時候,它的開發者放出了示例場景,來讓更多的開發者學習如何優化移動平臺上的shader。下載地址請戳官方博客。看不懂英文的可以看這篇(寫得很不錯)。項目里共包括了將近20個優化后的shader。關于使用許可的問題,項目里的shader都是可以避免費使用的,而貼圖和模型是不可用于商業用處的呦~
雖然ShadowGun的出場時間有點久遠了,但很多技術還是可以鑒戒滴~而且它現在依然在更新,并且價格為高昂的¥30,可見其對自信程度。
ShadowGun里包括了幾個比較重要的shader,例如非常著名的旗幟飛舞的shader,動態效果的天空盒子的shader,環境高光紋理映照等等。而這篇的飛機墜毀的濃煙效果shader應當是其中非常好理解的1篇。我們也從這里開始學習。
濃煙效果
飛機墜毀的濃煙效果可以從下圖看出來:

傳統的實現這類效果通常是使用粒子系統,而盡人皆知,粒子系統對性能的消耗太大,更何況是資源緊缺的移動平臺。因此ShadowGun使用了網格+紋理+移動UV的方法來摹擬這個效果。同時,還奇妙地使用了頂點色彩和透明度,來摹擬火焰色彩和平滑網格的邊沿。結果從畫面上看來表現還是可以接受的~
具體分析見下。
Shader源碼
Shader "MADFINGER/Environment/Scroll 2 Layers Sine AlphaBlended" {
Properties {
_MainTex ("Base layer (RGB)", 2D) = "white" {}
_DetailTex ("2nd layer (RGB)", 2D) = "white" {}
_ScrollX ("Base layer Scroll speed X", Float) = 1.0
_ScrollY ("Base layer Scroll speed Y", Float) = 0.0
_Scroll2X ("2nd layer Scroll speed X", Float) = 1.0
_Scroll2Y ("2nd layer Scroll speed Y", Float) = 0.0
_SineAmplX ("Base layer sine amplitude X",Float) = 0.5
_SineAmplY ("Base layer sine amplitude Y",Float) = 0.5
_SineFreqX ("Base layer sine freq X",Float) = 10
_SineFreqY ("Base layer sine freq Y",Float) = 10
_SineAmplX2 ("2nd layer sine amplitude X",Float) = 0.5
_SineAmplY2 ("2nd layer sine amplitude Y",Float) = 0.5
_SineFreqX2 ("2nd layer sine freq X",Float) = 10
_SineFreqY2 ("2nd layer sine freq Y",Float) = 10
_Color("Color", Color) = (1,1,1,1)
_MMultiplier ("Layer Multiplier", Float) = 2.0
}
SubShader {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,0) }
LOD 100
CGINCLUDE
#pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON
#pragma exclude_renderers molehill
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _DetailTex;
float4 _MainTex_ST;
float4 _DetailTex_ST;
float _ScrollX;
float _ScrollY;
float _Scroll2X;
float _Scroll2Y;
float _MMultiplier;
float _SineAmplX;
float _SineAmplY;
float _SineFreqX;
float _SineFreqY;
float _SineAmplX2;
float _SineAmplY2;
float _SineFreqX2;
float _SineFreqY2;
float4 _Color;
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
fixed4 color : TEXCOORD1;
};
v2f vert (appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord.xy,_MainTex) + frac(float2(_ScrollX, _ScrollY) * _Time);
o.uv.zw = TRANSFORM_TEX(v.texcoord.xy,_DetailTex) + frac(float2(_Scroll2X, _Scroll2Y) * _Time);
o.uv.x += sin(_Time * _SineFreqX) * _SineAmplX;
o.uv.y += sin(_Time * _SineFreqY) * _SineAmplY;
o.uv.z += sin(_Time * _SineFreqX2) * _SineAmplX2;
o.uv.w += sin(_Time * _SineFreqY2) * _SineAmplY2;
o.color = _MMultiplier * _Color * v.color;
return o;
}
ENDCG
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
fixed4 frag (v2f i) : COLOR
{
fixed4 o;
fixed4 tex = tex2D (_MainTex, i.uv.xy);
fixed4 tex2 = tex2D (_DetailTex, i.uv.zw);
o = tex * tex2 * i.color;
return o;
}
ENDCG
}
}
}
其實,
ShadowGun里的Shader都不長,但有些shader要完全理解還是需要1些時間的。固然這篇還是很簡單的~
SubShader Tags
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
這個shader使用的tags算是Unity里半透明物體渲染的標配。
"Queue"="Transparent"指明了該shader的渲染隊列,
"IgnoreProjector"="True"指明該shader不會受Projector的影響(半透明物體1般設為true),
"RenderType"="Transparent"指明了它的渲染種別,文檔上說是可以被用于shader replacement,但我現在還不是很理解,有人知道請留言告知我~謝謝。
關于SubShader Tags的說明,請見官網。
渲染設置
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
Lighting Off
ZWrite Off
Fog { Color (0,0,0,0) }
這篇shader的特點就是充分利用了alpha混合的技術,因此第1行就是指明了它的混合系數。Alpha混合是本篇的重點。在場景中所有的shader被渲染終了后,每一個shader產生的像素寫入了幀緩存中,由于它們的渲染是依照1定順序的,因此如何控制這些前后像素的混合順序就是靠Blend指令控制的。它會指定源像素和目標像素的混合系數,按這個系數對兩種像素進行處理后作為輸出像素。具體可見這篇文章和官網。而
Blend SrcAlpha OneMinusSrcAlpha就是用于alpha混合的系數,也就是說靠alpha通道來控制像素色彩。
Cull Off 指明該shader不會剔除面,即背面也會被渲染。
ZWrite Off 指明不寫入深度緩存,而是根據渲染順序來決定顯示的。其實"Queue"="Transparent"會自動生成ZWrite Off 語句的。
其他命令可以參見官網。
算法分析
這篇shader的關鍵在于vert函數:
v2f vert (appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord.xy,_MainTex) + frac(float2(_ScrollX, _ScrollY) * _Time);
o.uv.zw = TRANSFORM_TEX(v.texcoord.xy,_DetailTex) + frac(float2(_Scroll2X, _Scroll2Y) * _Time);
o.uv.x += sin(_Time * _SineFreqX) * _SineAmplX;
o.uv.y += sin(_Time * _SineFreqY) * _SineAmplY;
o.uv.z += sin(_Time * _SineFreqX2) * _SineAmplX2;
o.uv.w += sin(_Time * _SineFreqY2) * _SineAmplY2;
o.color = _MMultiplier * _Color * v.color;
return o;
}
里面主要用到了下面的
函數公式:
u = sin(freq * x) * ampl + scroll * x;
上述公式了表示了U方向上的輸出。這個函數其實就是正弦波函數+1次函數,由此得到特定方向上的波動效果。這類函數的函數圖象(其中scroll = ⑵,ampl = 0.005,freq = 250)類似下面這樣:

可以看出來就是1個傾斜波動的模樣,以此來摹擬火焰緩慢波動并上移的模樣。
Shader利用了兩種紋理來摹擬立體效果,每張紋理對應了6個參數,分別代表了U方向和V方向上的函數參數。
除函數里利用,這篇shader還有1個非常奇妙的地方,就是利用了頂點色彩。從1開始的動態圖可以看出來其中有紅色的火焰,但其實這不是紋理里的色彩,而是頂點色彩。如果我們把紋理都去掉,可以發現它真實的模型實際上是長這樣的:

也就是說它本身的頂點色彩就摹擬了火焰色彩。他們很奇妙地利用了頂點色彩本身及其alpha通道的值,來摹擬從火焰到濃煙的過渡效果。下面的代碼雖然只有1行,但起到了很關鍵的作用:
o.color = _MMultiplier * _Color * v.color;
Shader允許我們在面板中(通過
_MMultiplier和
_Color)在頂點色彩的基礎上調劑整體色彩,并將結果存儲到
v2f中。要注意的是,這里的頂點色彩,即
v.color,包括了重要的透明度信息,濃煙的透明過渡效果其實都是它的功勞。
寫在最后
站在前人的肩膀上總是能看得更遠。這篇Shader雖小,但體現了很多很常見的手法:alpha混合,UV動畫,利用模型頂點信息來減少紋理輸入等等。感謝前人們的貢獻和分享,希望大家可以有所收獲~下次見啦!
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈