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次多余的采样,第二步的高斯模糊一样也能够降阶,甚至也能够换成均值模糊,也能节省很多的计算。
下载连接: