http://blog.csdn.net/u011047171/article/details/48522073
Bloom特效
概述
Bloom,又稱「全屏泛光」,是遊戲中經常使用的一種鏡頭效果,是一種比較廉價的「僞HDR」效果(以下右圖);使用了Bloom效果後,畫面的對比會獲得加強,亮的地方曝光也會獲得增強,畫面也會呈現一種朦朧,夢幻的效果,婚紗攝影中照片處理常常用到這種相似處理效果。Bloom效果通常用來近似模擬HDR效果,效果也比較相向,但實現原理卻徹底不一樣。本例將實現一個適合移動平臺使用的bloom屏幕特效。
Bloom特效與HDR特效的異同
要比較二者的異同,得先搞清楚HDR特效是什麼;HDR,自己是High-Dynamic Range(高動態範圍)的縮寫,這原本是一個CG概念。HDR的含義,簡單說,就是超越普通的光照的顏色和強度的光照。計算機在表示圖象的時候是用8bit(256)級或16bit(65536)級來區分圖象的亮度的,但這區區幾百或幾萬沒法再現真實天然的光照狀況。所以普通狀況下,沒法同時顯示亮部和暗部的全部細節。
現實中,當人由黑暗地方走到光亮地方,眼睛會自動眯起來。人在黑暗的地方,爲了看清楚對象,瞳孔會很大張開,以吸取更多光線。當忽然走到光亮地方,瞳孔來不及收縮,因此惟有眯上眼睛,保護視網膜上的視神經。而電腦是死物,惟有靠HDR技術模擬這效果——人眼自動適應光線變化的能力。方法是快速將光線渲染得很是光亮,而後將亮度逐漸下降。而HDR的最終效果是亮處的效果是鮮亮,而黑暗處的效果是能分辨物體的輪廓和深度,而不是以往的一團黑。。
想要實現HDR特效,首先,遊戲開發者要在遊戲開發過程當中,利用開發工具(就是遊戲引擎)將實際場景用HDRI記錄下來,固然開發技術強的開發組會直接用小開發工具(好比3D MAX的某些特效插件)創造HDRI圖像;其次,咱們的顯卡必須支持顯示HDR特效,nVIDIA的顯卡必須是GeForce 6系列或更高,ATI顯卡至少是Radeon 9550或以上。
那麼HDR與bloom效果的差異到底在什麼地方呢?
第一,HDR效果就是超亮的光照與超暗的黑暗的某種結合,這個效果是光照產生的,強度、顏色等方面是遊戲程序可動態控制的,是一種即時動態光影;bloom效果則是物體自己發出的光照,僅僅是將光照範圍調高到過飽和,是遊戲程序沒法動態控制的,是一種全屏泛光。
第二,bloom效果無需HDR就能夠實現,可是bloom效果是很受限的,它只支持8位RGBA,而HDR最高支持到32位RGBA。
第三,bloom效果的實現很簡單,好比《半條命2》的MOD就是一個很小的很簡單的MOD,並且bloom效果不受顯卡的規格的限制,你甚至能夠在TNT顯卡上實現bloom效果(固然效果不好)!而HDR,必須是6XXX以上的顯卡纔可以實現,這裏的HDR是指nVIDIA的HDR。這時有必要談nVIDIA和ATI的顯卡所實現的HDR,二者仍是有區別的,具體區別就很專業了,總之從真實性表現來看,nVIDIA的顯卡實現的HDR更好一些。HDR是nVIDIA提出的概念,從技術上來說,ATI固然沒法嚴格克隆nVIDIA的技術,因此ATI的HDR是另外一種途徑實現的儘量接近的HDR,不能算「真」HDR,據傳ATI的R520可以真正實現FP16 HDR。
未使用HDR圖像 使用HDR圖像
Bloom特效的實現流程
Bloom效果實現的流程與HDR的物理還原不一樣,它只是一種簡單的近似模擬:
- 第一步: 先獲取屏幕圖像,而後對每一個像素進行亮度檢測,若大於某個閥值即保留原始顏色值,不然置爲黑色;
- 第二步:對上一步獲取的圖像,作一個模糊,一般使用高斯模糊。
- 第三步:將模糊後的圖片和原圖片作一個加權和。
經過這三步就能夠達到一個全屏泛光的效果。
Bloom特效的shader實現
本例在shader中實現大體和上面所述流程相似,只是在第一步作了少量改動,在這裏咱們將亮部的像素進行了擴展,關鍵代碼以下:
- struct v2f_withMaxCoords {
- half4 pos : SV_POSITION;
- half2 uv2[5] : TEXCOORD0;
- };
-
-
- v2f_withMaxCoords vertMax (appdata_img v)
- {
- v2f_withMaxCoords o;
- o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
- 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);
- o.uv2[4] = v.texcoord ;
- return o;
- }
- fixed4 fragMax ( v2f_withMaxCoords i ) : COLOR
- {
- fixed4 color = tex2D(_MainTex, i.uv2[4]);
- 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);
- }
流程的第二步就是講上一步的結果作模糊處理,在這裏咱們使用的上一個例子所使用的高斯模糊,所以很少作解釋,關鍵代碼以下面所示:
- struct v2f_withBlurCoordsSGX
- {
- float4 pos : SV_POSITION;
- half2 offs[7] : TEXCOORD0;
- };
-
- v2f_withBlurCoordsSGX vertBlurHorizontalSGX (appdata_img v)
- {
- v2f_withBlurCoordsSGX o;
- o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
- half2 netFilterWidth = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _Parameter.x;
-
- o.offs[0] = v.texcoord + netFilterWidth;
- o.offs[1] = v.texcoord + netFilterWidth*2.0;
- o.offs[2] = v.texcoord + netFilterWidth*3.0;
- o.offs[3] = v.texcoord - netFilterWidth;
- o.offs[4] = v.texcoord - netFilterWidth*2.0;
- o.offs[5] = v.texcoord - netFilterWidth*3.0;
- o.offs[6] = v.texcoord;
-
- return o;
- }
-
- v2f_withBlurCoordsSGX vertBlurVerticalSGX (appdata_img v)
- {
- v2f_withBlurCoordsSGX o;
- o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
- half2 netFilterWidth = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _Parameter.x;
-
- o.offs[0] = v.texcoord + netFilterWidth;
- o.offs[1] = v.texcoord + netFilterWidth*2.0;
- o.offs[2] = v.texcoord + netFilterWidth*3.0;
- o.offs[3] = v.texcoord - netFilterWidth;
- o.offs[4] = v.texcoord - netFilterWidth*2.0;
- o.offs[5] = v.texcoord - netFilterWidth*3.0;
- o.offs[6] = v.texcoord;
-
- return o;
- }
- fixed4 fragBlurSGX ( v2f_withBlurCoordsSGX i ) : COLOR
- {
-
- fixed4 color = tex2D(_MainTex, i.offs[6]) * curve[3];
- color += tex2D(_MainTex, i.offs[0])*curve[2];
- color += tex2D(_MainTex, i.offs[1])*curve[1];
- color += tex2D(_MainTex, i.offs[2])*curve[0];
- color += tex2D(_MainTex, i.offs[3])*curve[2];
- color += tex2D(_MainTex, i.offs[4])*curve[1];
- color += tex2D(_MainTex, i.offs[5])*curve[0];
-
- return color;
-
- }
-
流程的最後一步就很是簡單了,將上一步獲取的結果與原圖進行權重求和便可,就能獲得一個bloom效果。在這裏咱們添加了從C#腳本傳遞過來的權重參數_Parameter.z和顏色參數_ColorMix,用來控制bloom的強度以及顏色傾向。
- 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);
- color += tex2D(_Bloom, i.uv.zw)*_Parameter.z*_ColorMix;
- return color;
- }
本例Bloom特效的shader部分關鍵代碼就是這麼多,這裏就不貼出完整代碼了,有須要的同窗能夠到文章末尾點積連接下載,在完整代碼裏,咱們使用了CGINCLUDE和ENDCG模塊化的方式組織代碼,減小了必定代碼量,而且是代碼的可讀性更好,方便C#腳本調用。
C#腳本
C#腳本相對而言比較簡單,和前面的的屏幕特效腳本相似,須要對shader的不一樣pass分別調用,而且開放了四個參數以方便效果的調節:Color Mix控制bloom特效的顏色傾向,Threshold控制bloom效果的範圍,Intensity控制bloom特效的強度,Blur Size控制模糊範圍以及模糊的質量。關鍵代碼以下;完整代碼請到文末放出的連接下載。
- void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)
- {
- #if UNITY_EDITOR
- FindShaders ();
- CheckSupport ();
- CreateMaterials ();
- #endif
-
- if(threshold != 0 && intensity != 0){
-
- int rtW = sourceTexture.width/4;
- int rtH = sourceTexture.height/4;
-
- BloomMaterial.SetColor ("_ColorMix", colorMix);
- BloomMaterial.SetVector ("_Parameter", new Vector4(BlurSize*1.5f, 0.0f, intensity,0.8f - threshold));
-
-
- RenderTexture rtTempA = RenderTexture.GetTemporary (rtW, rtH, 0,rtFormat);
- rtTempA.filterMode = FilterMode.Bilinear;
-
- RenderTexture rtTempB = RenderTexture.GetTemporary (rtW, rtH, 0,rtFormat);
- rtTempA.filterMode = FilterMode.Bilinear;
-
- Graphics.Blit (sourceTexture, rtTempA,BloomMaterial,0);
-
-
- Graphics.Blit (rtTempA, rtTempB, BloomMaterial,1);
- RenderTexture.ReleaseTemporary(rtTempA);
-
-
- rtTempA = RenderTexture.GetTemporary (rtW, rtH, 0, rtFormat);
- rtTempB.filterMode = FilterMode.Bilinear;
- Graphics.Blit (rtTempB, rtTempA, BloomMaterial,2);
-
-
- BloomMaterial.SetTexture ("_Bloom", rtTempA);
- Graphics.Blit (sourceTexture, destTexture, BloomMaterial,3);
-
-
- RenderTexture.ReleaseTemporary(rtTempA);
- RenderTexture.ReleaseTemporary(rtTempB);
- }
-
- else{
- Graphics.Blit(sourceTexture, destTexture);
-
- }
-
-
- }
本例實現的效果如圖
總結
本例bloom效果是爲移動平臺開發,作了很多的優化,使之在移動平臺上也有不錯的效率,固然本例效果還有進一步的優化空間,好比將第一步的像素擴展去掉,能夠節省掉4次多餘的採樣,第二步的高斯模糊一樣也能夠降階,甚至也能夠換成均值模糊,也能節省很多的計算。
下載連接: