【OpenGL】Shader實例分析(九)- AngryBots中的主角受傷特效
轉發請保持地址:http://blog.csdn.net/stalendp/article/details/40859441
AngryBots是Unity官方的一個非常棒的樣例。非常有研究價值。
曾經研究的時候。因為其內容豐富,一時間不知道從哪入手寫文章分析。
這一段時間研究shader技術比較多一些,就從shader的這一方面開始吧。首先分析當中的一個屏幕特效:當主角受到攻擊時會出現的全屏效果(postScreenEffect)。效果例如以下:
事實上這是一種的Bloom效果,相關文件有:MobileBloom.js 和 MobileBloom.shader;關於怎樣查看這兩個文件,請參考下圖:
JS代碼分析
MobileBloom.js部分代碼例如以下:
function OnRenderImage (source : RenderTexture, destination : RenderTexture) { #if UNITY_EDITOR FindShaders (); CheckSupport (); CreateMaterials (); #endif agonyTint = Mathf.Clamp01 (agonyTint - Time.deltaTime * 2.75f); var tempRtLowA : RenderTexture = RenderTexture.GetTemporary (source.width / 4, source.height / 4, rtFormat); var tempRtLowB : RenderTexture = RenderTexture.GetTemporary (source.width / 4, source.height / 4, rtFormat); // prepare data apply.SetColor ("_ColorMix", colorMix); apply.SetVector ("_Parameter", Vector4 (colorMixBlend * 0.25f, 0.0f, 0.0f, 1.0f - intensity - agonyTint)); // downsample & blur Graphics.Blit (source, tempRtLowA, apply, agonyTint < 0.5f ? 1 : 5); Graphics.Blit (tempRtLowA, tempRtLowB, apply, 2); Graphics.Blit (tempRtLowB, tempRtLowA, apply, 3); // apply apply.SetTexture ("_Bloom", tempRtLowA); Graphics.Blit (source, destination, apply, QualityManager.quality > Quality.Medium ? 4 : 0); RenderTexture.ReleaseTemporary (tempRtLowA); RenderTexture.ReleaseTemporary (tempRtLowB); }
知識點準備
1)OnRenderImage函數
這是一個回調函數,是MonoBehaviour的生命周期的一部分。每一幀都會被調用;當這個函數被調用時。全部的3d渲染已經完畢,渲染結果以參數source傳入到函數中,後期效果的實現就是對source的處理,並把結果整合到destination中。這個函數所在的腳本一般綁定在Camera上。
此函數僅僅有在Unity Pro版本號中才可以使用。
2)Graphics.Blit函數
static void Blit(Texture source, RenderTexture dest); static void Blit(Texture source, RenderTexture dest, Material mat, int pass = -1); static void Blit(Texture source, Material mat, int pass = -1);
這個函數就像過轉化器一樣。source圖片經過它的處理變成了dest圖片,當中材質對象mat負責算法實施(更準確的說法:算法是綁定到該mat上的shader來實現的。shader能夠有多個pass。能夠通過pass參數指定特定的shader,-1表示運行這個shader上全部的pass)。
3)RenderTexture.GetTemporary函數 和RenderTexture.ReleaseTemporary函數
GetTemporary獲取暫時的RenderTexture。ReleaseTemporary用來釋放指定的RenderTexture。
RenderTexture一般在GPU中實現,速度快但資源稀缺。unity內部對RenderTexture做了池化操作。以便復用之。對GetTemporary函數的調用事實上就是獲取unity中RenderTexture的引用;當處理完之後。使用ReleaseTemporary來釋放對此RenderTexture的引用。達到復用的目的。提高性能。
JS代碼分析
了解了三個知識點,上面代碼的功能就很清晰了,分析例如以下:
- a)獲取兩個渲染貼圖tempRtLowA和tempRtLowB(長寬都是原圖的1/4,用以加快渲染速度)
- b)設置Mat中Shader的參數
- c)通過Mat來處理貼圖,終於渲染到destination貼圖中。用來顯示
- d)釋放暫時的貼圖。
這裏先解釋a和c。
【步驟a】。獲取兩個貼圖,並縮小到原來的1/16(長寬都縮小為原來的1/4,面積為原來的1/16),節約了GPU內存,同一時候提高渲染速度;因為接下來的步驟是對圖片進行模糊處理(對質量要求不高),這樣做是可行的。
【步驟c】(註:調用Blit函數來過濾貼圖,當中最後一個數字參數是用來指代shader的pass的)
pass1 或者 pass5, 提取顏色中最亮的部分。pass2 對高亮圖片進行縱向模糊;pass3 對高亮圖片進行橫向模糊;pass0或pass4;把模糊的圖片疊加到原圖片上。
一個亮點。先經過橫向模糊。再經過縱向模糊的過程,例如以下圖所看到的(能夠把這理解為“使一個點向周圍擴散的算法”):
圖解算法
如今的重點是【步驟c】中的shader算法是怎麽實現的了。先圖解一下算法:
圖1 原圖
圖2【初始化】原圖縮放成原來的1/16
圖3【步驟1】擴大高亮區域
圖4 【步驟2】縱向模糊
圖5 【步驟3】橫向模糊
圖6 【步驟4a】(原圖 + 步驟3的效果)終於疊加的效果,這個效果稱之為glow或者bloom。
圖7 【步驟4b】(原圖 + 步驟3的效果)終於疊加的效果 《===(註意:這個效果須要在步驟1中加入紅色成份)
調節步驟1中的圖片顏色強度。能夠形成對應的動畫,例如以下圖所看到的:
Shader分析
接下來,我將依照上圖的序列來分析shader開始。
圖3【步驟1】擴大高亮區域
js代碼:
Graphics.Blit (source, tempRtLowA, apply, 1);shader代碼:
struct v2f_withMaxCoords { half4 pos : SV_POSITION; half2 uv : TEXCOORD0; half2 uv2[4] : TEXCOORD1; }; v2f_withMaxCoords vertMax (appdata_img v) { v2f_withMaxCoords o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord; o.uv2[0] = v.texcoord + _MainTex_TexelSize.xy * half2(1.5,1.5); o.uv2[1] = v.texcoord + _MainTex_TexelSize.xy * half2(-1.5,1.5); o.uv2[2] = v.texcoord + _MainTex_TexelSize.xy * half2(-1.5,-1.5); o.uv2[3] = v.texcoord + _MainTex_TexelSize.xy * half2(1.5,-1.5); return o; } fixed4 fragMax ( v2f_withMaxCoords i ) : COLOR { fixed4 color = tex2D(_MainTex, i.uv.xy); color = max(color, tex2D (_MainTex, i.uv2[0])); color = max(color, tex2D (_MainTex, i.uv2[1])); color = max(color, tex2D (_MainTex, i.uv2[2])); color = max(color, tex2D (_MainTex, i.uv2[3])); return saturate(color - ONE_MINUS_INTENSITY); } // 1 Pass { CGPROGRAM #pragma vertex vertMax #pragma fragment fragMax #pragma fragmentoption ARB_precision_hint_fastest ENDCG }這段代碼的作用能夠描寫敘述為:當渲染某一點時。在這一點及其周圍四點(左上、右上、左下、右下)中。選取最亮的一點作為該點的顏色。詳細解釋為:在vertMax的代碼中,構造了向四個方向偏移的uv坐標。結合本身uv。共5個uv,一起提交給openGL。光柵化後傳給fragmentShader使用。在fragMax中從5個uv所相應的像素中。選取當中最大的作為顏色輸出。結果如圖3所看到的。
圖4 【步驟2】縱向模糊
js端
Graphics.Blit (tempRtLowA, tempRtLowB, apply, 2);Shader端代碼:
struct v2f_withBlurCoords { half4 pos : SV_POSITION; half2 uv2[4] : TEXCOORD0; }; v2f_withBlurCoords vertBlurVertical (appdata_img v) { v2f_withBlurCoords o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv2[0] = v.texcoord + _MainTex_TexelSize.xy * half2(0.0, -1.5); o.uv2[1] = v.texcoord + _MainTex_TexelSize.xy * half2(0.0, -0.5); o.uv2[2] = v.texcoord + _MainTex_TexelSize.xy * half2(0.0, 0.5); o.uv2[3] = v.texcoord + _MainTex_TexelSize.xy * half2(0.0, 1.5); return o; } fixed4 fragBlurForFlares ( v2f_withBlurCoords i ) : COLOR { fixed4 color = tex2D (_MainTex, i.uv2[0]); color += tex2D (_MainTex, i.uv2[1]); color += tex2D (_MainTex, i.uv2[2]); color += tex2D (_MainTex, i.uv2[3]); return color * 0.25; } // 2 Pass { CGPROGRAM #pragma vertex vertBlurVertical #pragma fragment fragBlurForFlares #pragma fragmentoption ARB_precision_hint_fastest ENDCG }
這段代碼的作用能夠描寫敘述為:當渲染某一點時,在豎直方向上距其0.5和1.5個單位的四個點(上下各兩個)的顏色疊加起來。作為該點的顏色。
結果如圖4所看到的。
圖5 【步驟3】橫向模糊 (同圖四的描寫敘述)
圖6 【步驟4a】終於疊加的效果
(原圖 + 步驟3的效果)終於疊加的效果,這個效果稱之為glow或者bloom。
js段代碼:
apply.SetTexture ("_Bloom", tempRtLowA); Graphics.Blit (source, destination, apply, 0);Shader端代碼:
struct v2f_simple { half4 pos : SV_POSITION; half4 uv : TEXCOORD0; }; v2f_simple vertBloom (appdata_img v) { v2f_simple o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord.xyxy; #if SHADER_API_D3D9 if (_MainTex_TexelSize.y < 0.0) o.uv.w = 1.0 - o.uv.w; #endif return o; } fixed4 fragBloom ( v2f_simple i ) : COLOR { fixed4 color = tex2D(_MainTex, i.uv.xy); return color + tex2D(_Bloom, i.uv.zw); } // 0 Pass { CGPROGRAM #pragma vertex vertBloom #pragma fragment fragBloom #pragma fragmentoption ARB_precision_hint_fastest ENDCG }
這段代碼的作用能夠描寫敘述為:把圖5的結果疊加到原圖上。
結果如圖6所看到的。
Shader的完整代碼
MobileBloom.shader:
Shader "Hidden/MobileBloom" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _Bloom ("Bloom (RGB)", 2D) = "black" {} } CGINCLUDE #include "UnityCG.cginc" sampler2D _MainTex; sampler2D _Bloom; uniform fixed4 _ColorMix; uniform half4 _MainTex_TexelSize; uniform fixed4 _Parameter; #define ONE_MINUS_INTENSITY _Parameter.w struct v2f_simple { half4 pos : SV_POSITION; half4 uv : TEXCOORD0; }; struct v2f_withMaxCoords { half4 pos : SV_POSITION; half2 uv : TEXCOORD0; half2 uv2[4] : TEXCOORD1; }; struct v2f_withBlurCoords { half4 pos : SV_POSITION; half2 uv2[4] : TEXCOORD0; }; v2f_simple vertBloom (appdata_img v) { v2f_simple o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord.xyxy; #if SHADER_API_D3D9 if (_MainTex_TexelSize.y < 0.0) o.uv.w = 1.0 - o.uv.w; #endif return o; } v2f_withMaxCoords vertMax (appdata_img v) { v2f_withMaxCoords o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord; o.uv2[0] = v.texcoord + _MainTex_TexelSize.xy * half2(1.5,1.5); o.uv2[1] = v.texcoord + _MainTex_TexelSize.xy * half2(-1.5,1.5); o.uv2[2] = v.texcoord + _MainTex_TexelSize.xy * half2(-1.5,-1.5); o.uv2[3] = v.texcoord + _MainTex_TexelSize.xy * half2(1.5,-1.5); return o; } v2f_withBlurCoords vertBlurVertical (appdata_img v) { v2f_withBlurCoords o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv2[0] = v.texcoord + _MainTex_TexelSize.xy * half2(0.0, -1.5); o.uv2[1] = v.texcoord + _MainTex_TexelSize.xy * half2(0.0, -0.5); o.uv2[2] = v.texcoord + _MainTex_TexelSize.xy * half2(0.0, 0.5); o.uv2[3] = v.texcoord + _MainTex_TexelSize.xy * half2(0.0, 1.5); return o; } v2f_withBlurCoords vertBlurHorizontal (appdata_img v) { v2f_withBlurCoords o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv2[0] = v.texcoord + _MainTex_TexelSize.xy * half2(-1.5, 0.0); o.uv2[1] = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5, 0.0); o.uv2[2] = v.texcoord + _MainTex_TexelSize.xy * half2(0.5, 0.0); o.uv2[3] = v.texcoord + _MainTex_TexelSize.xy * half2(1.5, 0.0); return o; } fixed4 fragBloom ( v2f_simple i ) : COLOR { fixed4 color = tex2D(_MainTex, i.uv.xy); return color + tex2D(_Bloom, i.uv.zw); } fixed4 fragBloomWithColorMix ( v2f_simple i ) : COLOR { fixed4 color = tex2D(_MainTex, i.uv.xy); half colorDistance = Luminance(abs(color.rgb-_ColorMix.rgb)); color = lerp(color, _ColorMix, (_Parameter.x*colorDistance)); color += tex2D(_Bloom, i.uv.zw); return color; } fixed4 fragMaxWithPain ( v2f_withMaxCoords i ) : COLOR { fixed4 color = tex2D(_MainTex, i.uv.xy); color = max(color, tex2D (_MainTex, i.uv2[0])); color = max(color, tex2D (_MainTex, i.uv2[1])); color = max(color, tex2D (_MainTex, i.uv2[2])); color = max(color, tex2D (_MainTex, i.uv2[3])); return saturate(color + half4(0.25,0,0,0) - ONE_MINUS_INTENSITY); } fixed4 fragMax ( v2f_withMaxCoords i ) : COLOR { fixed4 color = tex2D(_MainTex, i.uv.xy); color = max(color, tex2D (_MainTex, i.uv2[0])); color = max(color, tex2D (_MainTex, i.uv2[1])); color = max(color, tex2D (_MainTex, i.uv2[2])); color = max(color, tex2D (_MainTex, i.uv2[3])); return saturate(color - ONE_MINUS_INTENSITY); } fixed4 fragBlurForFlares ( v2f_withBlurCoords i ) : COLOR { fixed4 color = tex2D (_MainTex, i.uv2[0]); color += tex2D (_MainTex, i.uv2[1]); color += tex2D (_MainTex, i.uv2[2]); color += tex2D (_MainTex, i.uv2[3]); return color * 0.25; } ENDCG SubShader { ZTest Always Cull Off ZWrite Off Blend Off Fog { Mode off } // 0 Pass { CGPROGRAM #pragma vertex vertBloom #pragma fragment fragBloom #pragma fragmentoption ARB_precision_hint_fastest ENDCG } // 1 Pass { CGPROGRAM #pragma vertex vertMax #pragma fragment fragMax #pragma fragmentoption ARB_precision_hint_fastest ENDCG } // 2 Pass { CGPROGRAM #pragma vertex vertBlurVertical #pragma fragment fragBlurForFlares #pragma fragmentoption ARB_precision_hint_fastest ENDCG } // 3 Pass { CGPROGRAM #pragma vertex vertBlurHorizontal #pragma fragment fragBlurForFlares #pragma fragmentoption ARB_precision_hint_fastest ENDCG } // 4 Pass { CGPROGRAM #pragma vertex vertBloom #pragma fragment fragBloomWithColorMix #pragma fragmentoption ARB_precision_hint_fastest ENDCG } // 5 Pass { CGPROGRAM #pragma vertex vertMax #pragma fragment fragMaxWithPain #pragma fragmentoption ARB_precision_hint_fastest ENDCG } } FallBack Off }
參考文獻
官方樣例AngryBots的鏈接地址:http://u3d.as/content/unity-technologies/angry-bots/5CF
《Unity Shaders and Effects Cookbook》的章節:
Chapter 10 Screen Effects with Unity Render Textures
Chapter 11 Gameplay and Screen Effects
[GPU Gems] Real-Time Glow:http://http.developer.nvidia.com/GPUGems/gpugems_ch21.html
【OpenGL】Shader實例分析(九)- AngryBots中的主角受傷特效