UnityShader實例15:屏幕特效之Bloom

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中實現大體和上面所述流程相似,只是在第一步作了少量改動,在這裏咱們將亮部的像素進行了擴展,關鍵代碼以下:
[csharp]  view plain  copy
 
 print?
  1.         struct v2f_withMaxCoords {  
  2.             half4 pos : SV_POSITION;  
  3.             half2 uv2[5] : TEXCOORD0;  
  4.         };  
  5.   
  6.   
  7. //在vert函數裏對uv座標作了四次偏移,對原像素周圍臨近的像素採樣  
  8.         v2f_withMaxCoords vertMax (appdata_img v)  
  9.         {  
  10.             v2f_withMaxCoords o;  
  11.             o.pos = mul (UNITY_MATRIX_MVP, v.vertex);  
  12.             o.uv2[0] = v.texcoord + _MainTex_TexelSize.xy * half2(1.5,1.5);                   
  13.             o.uv2[1] = v.texcoord + _MainTex_TexelSize.xy * half2(-1.5,1.5);  
  14.             o.uv2[2] = v.texcoord + _MainTex_TexelSize.xy * half2(-1.5,-1.5);  
  15.             o.uv2[3] = v.texcoord + _MainTex_TexelSize.xy * half2(1.5,-1.5);  
  16.             o.uv2[4] = v.texcoord ;  
  17.             return o;   
  18.         }     
  19. //Frag函數用偏移的uv座標採樣,而且與原像素進行對比,若是亮度比原像素大,則取代原像素,所以亮部像素獲得了擴展處理。這裏ONE_MINUS_INTENSITY是由腳本傳遞過來的參數,用來控制bloom範圍,功能就是講低於這個值的像素設置爲黑色。  
  20.         fixed4 fragMax ( v2f_withMaxCoords i ) : COLOR  
  21.         {                 
  22.             fixed4 color = tex2D(_MainTex, i.uv2[4]);  
  23.             color = max(color, tex2D (_MainTex, i.uv2[0]));   
  24.             color = max(color, tex2D (_MainTex, i.uv2[1]));   
  25.             color = max(color, tex2D (_MainTex, i.uv2[2]));   
  26.             color = max(color, tex2D (_MainTex, i.uv2[3]));   
  27.             return saturate(color - ONE_MINUS_INTENSITY);  
  28.         }   
 
     流程的第二步就是講上一步的結果作模糊處理,在這裏咱們使用的上一個例子所使用的高斯模糊,所以很少作解釋,關鍵代碼以下面所示:
 
[csharp]  view plain  copy
 
 print?
  1.         struct v2f_withBlurCoordsSGX   
  2.         {  
  3.             float4 pos : SV_POSITION;  
  4.             half2 offs[7] : TEXCOORD0;  
  5.         };  
  6.   
  7. //水平方向的像素偏移,用來作水平模糊  
  8.         v2f_withBlurCoordsSGX vertBlurHorizontalSGX (appdata_img v)  
  9.         {  
  10.             v2f_withBlurCoordsSGX o;  
  11.             o.pos = mul (UNITY_MATRIX_MVP, v.vertex);  
  12.             half2 netFilterWidth = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _Parameter.x;   
  13.   
  14.             o.offs[0] = v.texcoord + netFilterWidth;  
  15.             o.offs[1] = v.texcoord + netFilterWidth*2.0;  
  16.             o.offs[2] = v.texcoord + netFilterWidth*3.0;  
  17.             o.offs[3] = v.texcoord - netFilterWidth;  
  18.             o.offs[4] = v.texcoord - netFilterWidth*2.0;  
  19.             o.offs[5] = v.texcoord - netFilterWidth*3.0;  
  20.             o.offs[6] = v.texcoord;  
  21.   
  22.             return o;   
  23.         }         
  24. //垂直方向的像素偏移,用來作水平模糊  
  25.           
  26.         v2f_withBlurCoordsSGX vertBlurVerticalSGX (appdata_img v)  
  27.         {  
  28.             v2f_withBlurCoordsSGX o;  
  29.             o.pos = mul (UNITY_MATRIX_MVP, v.vertex);     
  30.             half2 netFilterWidth = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _Parameter.x;  
  31.               
  32.             o.offs[0] = v.texcoord + netFilterWidth;  
  33.             o.offs[1] = v.texcoord + netFilterWidth*2.0;  
  34.             o.offs[2] = v.texcoord + netFilterWidth*3.0;  
  35.             o.offs[3] = v.texcoord - netFilterWidth;  
  36.             o.offs[4] = v.texcoord - netFilterWidth*2.0;  
  37.             o.offs[5] = v.texcoord - netFilterWidth*3.0;  
  38.             o.offs[6] = v.texcoord;  
  39.   
  40.             return o;   
  41.         }     
  42. //用vert傳過來的uv座標數組進行採樣,並乘以對應的權重進行疊加,其結果是個近似高斯模糊。  
  43.         fixed4 fragBlurSGX ( v2f_withBlurCoordsSGX i ) : COLOR  
  44.         {  
  45.               
  46.             fixed4 color = tex2D(_MainTex, i.offs[6]) * curve[3];  
  47.             color += tex2D(_MainTex, i.offs[0])*curve[2];  
  48.             color += tex2D(_MainTex, i.offs[1])*curve[1];  
  49.             color += tex2D(_MainTex, i.offs[2])*curve[0];  
  50.             color += tex2D(_MainTex, i.offs[3])*curve[2];  
  51.             color += tex2D(_MainTex, i.offs[4])*curve[1];  
  52.             color += tex2D(_MainTex, i.offs[5])*curve[0];  
  53.   
  54.             return color;  
  55.   
  56.         }     
  57.           
 
        流程的最後一步就很是簡單了,將上一步獲取的結果與原圖進行權重求和便可,就能獲得一個bloom效果。在這裏咱們添加了從C#腳本傳遞過來的權重參數_Parameter.z和顏色參數_ColorMix,用來控制bloom的強度以及顏色傾向。
 
[csharp]  view plain  copy
 
 print?
  1.         struct v2f_simple {  
  2.             half4 pos : SV_POSITION;  
  3.             half4 uv : TEXCOORD0;  
  4.         };  
  5. //考慮到D3D9的uv座標Y軸是反轉的,所以須要作個判斷進行調整,防止圖像倒轉。  
  6.         v2f_simple vertBloom (appdata_img v)  
  7.         {  
  8.             v2f_simple o;  
  9.             o.pos = mul (UNITY_MATRIX_MVP, v.vertex);  
  10.             o.uv = v.texcoord.xyxy;           
  11.              #if SHADER_API_D3D9  
  12.                 if (_MainTex_TexelSize.y < 0.0)  
  13.                     o.uv.w = 1.0 - o.uv.w;  
  14.             #endif  
  15.             return o;   
  16.         }  
  17.   
  18.   
  19.         fixed4 fragBloom ( v2f_simple i ) : COLOR  
  20.         {     
  21.             fixed4 color = tex2D(_MainTex, i.uv.xy);  
  22.             color += tex2D(_Bloom, i.uv.zw)*_Parameter.z*_ColorMix;  
  23.             return color;  
  24.         }     
        本例Bloom特效的shader部分關鍵代碼就是這麼多,這裏就不貼出完整代碼了,有須要的同窗能夠到文章末尾點積連接下載,在完整代碼裏,咱們使用了CGINCLUDE和ENDCG模塊化的方式組織代碼,減小了必定代碼量,而且是代碼的可讀性更好,方便C#腳本調用。

C#腳本

        C#腳本相對而言比較簡單,和前面的的屏幕特效腳本相似,須要對shader的不一樣pass分別調用,而且開放了四個參數以方便效果的調節:Color Mix控制bloom特效的顏色傾向,Threshold控制bloom效果的範圍,Intensity控制bloom特效的強度,Blur Size控制模糊範圍以及模糊的質量。關鍵代碼以下;完整代碼請到文末放出的連接下載。
 
[csharp]  view plain  copy
 
 print?
  1. void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)  
  2. {     
  3.     #if UNITY_EDITOR  
  4.         FindShaders ();  
  5.         CheckSupport ();  
  6.         CreateMaterials ();   
  7.     #endif  
  8.   
  9.     if(threshold != 0 && intensity != 0){  
  10.   
  11.         int rtW = sourceTexture.width/4;  
  12.         int rtH = sourceTexture.height/4;  
  13.   
  14.         BloomMaterial.SetColor ("_ColorMix", colorMix);  
  15.         BloomMaterial.SetVector ("_Parameter", new Vector4(BlurSize*1.5f, 0.0f, intensity,0.8f - threshold));     
  16.         // material.SetFloat("_blurSize",BlurSize);  
  17.   
  18.         RenderTexture rtTempA = RenderTexture.GetTemporary (rtW, rtH, 0,rtFormat);  
  19.            rtTempA.filterMode = FilterMode.Bilinear;  
  20.   
  21.            RenderTexture rtTempB = RenderTexture.GetTemporary (rtW, rtH, 0,rtFormat);  
  22.            rtTempA.filterMode = FilterMode.Bilinear;  
  23.   
  24.            Graphics.Blit (sourceTexture, rtTempA,BloomMaterial,0);  
  25.   
  26.   
  27.            Graphics.Blit (rtTempA, rtTempB, BloomMaterial,1);  
  28.            RenderTexture.ReleaseTemporary(rtTempA);  
  29.   
  30.   
  31.            rtTempA = RenderTexture.GetTemporary (rtW, rtH, 0, rtFormat);  
  32.            rtTempB.filterMode = FilterMode.Bilinear;  
  33.            Graphics.Blit (rtTempB, rtTempA, BloomMaterial,2);  
  34.   
  35.   
  36.            BloomMaterial.SetTexture ("_Bloom", rtTempA);  
  37.            Graphics.Blit (sourceTexture, destTexture, BloomMaterial,3);  
  38.         
  39.   
  40.            RenderTexture.ReleaseTemporary(rtTempA);  
  41.            RenderTexture.ReleaseTemporary(rtTempB);  
  42.     }  
  43.   
  44.     else{  
  45.         Graphics.Blit(sourceTexture, destTexture);  
  46.           
  47.     }  
  48.       
  49.       
  50. }  

本例實現的效果如圖

 

總結

        本例bloom效果是爲移動平臺開發,作了很多的優化,使之在移動平臺上也有不錯的效率,固然本例效果還有進一步的優化空間,好比將第一步的像素擴展去掉,能夠節省掉4次多餘的採樣,第二步的高斯模糊一樣也能夠降階,甚至也能夠換成均值模糊,也能節省很多的計算。
 
 

下載連接:

        屏幕特效之Bloom
相關文章
相關標籤/搜索