1. 程式人生 > >【OpenGL】Shader實例分析(九)- AngryBots中的主角受傷特效

【OpenGL】Shader實例分析(九)- AngryBots中的主角受傷特效

spa 2.7 imp ttext pro tint shader 作用 負責

轉發請保持地址: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中的主角受傷特效