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中实现大致和上面所述流程类似,只是在第一步做了少许改动,在这里我们将亮部的像素进行了扩展,关键代码如下:
  1. struct v2f_withMaxCoords {
  2. half4 pos : SV_POSITION;
  3. half2 uv2[5] : TEXCOORD0;
  4. };
  5. //在vert函数里对uv坐标做了四次偏移,对原像素周围临近的像素采样
  6. v2f_withMaxCoords vertMax (appdata_img v)
  7. {
  8. v2f_withMaxCoords o;
  9. o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
  10. o.uv2[0] = v.texcoord + _MainTex_TexelSize.xy * half2(1.5,1.5);
  11. o.uv2[1] = v.texcoord + _MainTex_TexelSize.xy * half2(-1.5,1.5);
  12. o.uv2[2] = v.texcoord + _MainTex_TexelSize.xy * half2(-1.5,-1.5);
  13. o.uv2[3] = v.texcoord + _MainTex_TexelSize.xy * half2(1.5,-1.5);
  14. o.uv2[4] = v.texcoord ;
  15. return o;
  16. }
  17. //Frag函数用偏移的uv坐标采样,并且与原像素进行对比,如果亮度比原像素大,则取代原像素,因此亮部像素得到了扩展处理。这里ONE_MINUS_INTENSITY是由脚本传递过来的参数,用来控制bloom范围,功能就是讲低于这个值的像素设置为黑色。
  18. fixed4 fragMax ( v2f_withMaxCoords i ) : COLOR
  19. {
  20. fixed4 color = tex2D(_MainTex, i.uv2[4]);
  21. color = max(color, tex2D (_MainTex, i.uv2[0]));
  22. color = max(color, tex2D (_MainTex, i.uv2[1]));
  23. color = max(color, tex2D (_MainTex, i.uv2[2]));
  24. color = max(color, tex2D (_MainTex, i.uv2[3]));
  25. return saturate(color - ONE_MINUS_INTENSITY);
  26. }
 
     流程的第二步就是讲上一步的结果做模糊处理,在这里我们使用的上一个例子所使用的高斯模糊,因此不多做解释,关键代码如下面所示:
 
  1. struct v2f_withBlurCoordsSGX
  2. {
  3. float4 pos : SV_POSITION;
  4. half2 offs[7] : TEXCOORD0;
  5. };
  6. //水平方向的像素偏移,用来做水平模糊
  7. v2f_withBlurCoordsSGX vertBlurHorizontalSGX (appdata_img v)
  8. {
  9. v2f_withBlurCoordsSGX o;
  10. o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
  11. half2 netFilterWidth = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _Parameter.x;
  12. o.offs[0] = v.texcoord + netFilterWidth;
  13. o.offs[1] = v.texcoord + netFilterWidth*2.0;
  14. o.offs[2] = v.texcoord + netFilterWidth*3.0;
  15. o.offs[3] = v.texcoord - netFilterWidth;
  16. o.offs[4] = v.texcoord - netFilterWidth*2.0;
  17. o.offs[5] = v.texcoord - netFilterWidth*3.0;
  18. o.offs[6] = v.texcoord;
  19. return o;
  20. }
  21. //垂直方向的像素偏移,用来做水平模糊
  22. v2f_withBlurCoordsSGX vertBlurVerticalSGX (appdata_img v)
  23. {
  24. v2f_withBlurCoordsSGX o;
  25. o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
  26. half2 netFilterWidth = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _Parameter.x;
  27. o.offs[0] = v.texcoord + netFilterWidth;
  28. o.offs[1] = v.texcoord + netFilterWidth*2.0;
  29. o.offs[2] = v.texcoord + netFilterWidth*3.0;
  30. o.offs[3] = v.texcoord - netFilterWidth;
  31. o.offs[4] = v.texcoord - netFilterWidth*2.0;
  32. o.offs[5] = v.texcoord - netFilterWidth*3.0;
  33. o.offs[6] = v.texcoord;
  34. return o;
  35. }
  36. //用vert传过来的uv坐标数组进行采样,并乘以对应的权重进行叠加,其结果是个近似高斯模糊。
  37. fixed4 fragBlurSGX ( v2f_withBlurCoordsSGX i ) : COLOR
  38. {
  39. fixed4 color = tex2D(_MainTex, i.offs[6]) * curve[3];
  40. color += tex2D(_MainTex, i.offs[0])*curve[2];
  41. color += tex2D(_MainTex, i.offs[1])*curve[1];
  42. color += tex2D(_MainTex, i.offs[2])*curve[0];
  43. color += tex2D(_MainTex, i.offs[3])*curve[2];
  44. color += tex2D(_MainTex, i.offs[4])*curve[1];
  45. color += tex2D(_MainTex, i.offs[5])*curve[0];
  46. return color;
  47. }
 
        流程的最后一步就非常简单了,将上一步获取的结果与原图进行权重求和即可,就能得到一个bloom效果。在这里我们添加了从C#脚本传递过来的权重参数_Parameter.z和颜色参数_ColorMix,用来控制bloom的强度以及颜色倾向。
 
  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. fixed4 fragBloom ( v2f_simple i ) : COLOR
  18. {
  19. fixed4 color = tex2D(_MainTex, i.uv.xy);
  20. color += tex2D(_Bloom, i.uv.zw)*_Parameter.z*_ColorMix;
  21. return color;
  22. }

本例Bloom特效的shader部分关键代码就是这么多,这里就不贴出完整代码了,有需要的同学可以到文章末尾点积链接下载,在完整代码里,我们使用了CGINCLUDE和ENDCG模块化的方式组织代码,减少了一定代码量,并且是代码的可读性更好,方便C#脚本调用。

C#脚本

        C#脚本相对而言比较简单,和前面的的屏幕特效脚本类似,需要对shader的不同pass分别调用,并且开放了四个参数以方便效果的调节:Color Mix控制bloom特效的颜色倾向,Threshold控制bloom效果的范围,Intensity控制bloom特效的强度,Blur Size控制模糊范围以及模糊的质量。关键代码如下;完整代码请到文末放出的链接下载。
 
  1. void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)
  2. {
  3. #if UNITY_EDITOR
  4. FindShaders ();
  5. CheckSupport ();
  6. CreateMaterials ();
  7. #endif
  8. if(threshold != 0 && intensity != 0){
  9. int rtW = sourceTexture.width/4;
  10. int rtH = sourceTexture.height/4;
  11. BloomMaterial.SetColor ("_ColorMix", colorMix);
  12. BloomMaterial.SetVector ("_Parameter", new Vector4(BlurSize*1.5f, 0.0f, intensity,0.8f - threshold));
  13. // material.SetFloat("_blurSize",BlurSize);
  14. RenderTexture rtTempA = RenderTexture.GetTemporary (rtW, rtH, 0,rtFormat);
  15. rtTempA.filterMode = FilterMode.Bilinear;
  16. RenderTexture rtTempB = RenderTexture.GetTemporary (rtW, rtH, 0,rtFormat);
  17. rtTempA.filterMode = FilterMode.Bilinear;
  18. Graphics.Blit (sourceTexture, rtTempA,BloomMaterial,0);
  19. Graphics.Blit (rtTempA, rtTempB, BloomMaterial,1);
  20. RenderTexture.ReleaseTemporary(rtTempA);
  21. rtTempA = RenderTexture.GetTemporary (rtW, rtH, 0, rtFormat);
  22. rtTempB.filterMode = FilterMode.Bilinear;
  23. Graphics.Blit (rtTempB, rtTempA, BloomMaterial,2);
  24. BloomMaterial.SetTexture ("_Bloom", rtTempA);
  25. Graphics.Blit (sourceTexture, destTexture, BloomMaterial,3);
  26. RenderTexture.ReleaseTemporary(rtTempA);
  27. RenderTexture.ReleaseTemporary(rtTempB);
  28. }
  29. else{
  30. Graphics.Blit(sourceTexture, destTexture);
  31. }
  32. }

本例实现的效果如图

 

总结

        本例bloom效果是为移动平台开发,做了不少的优化,使之在移动平台上也有不错的效率,当然本例效果还有进一步的优化空间,比如将第一步的像素扩展去掉,可以节省掉4次多余的采样,第二步的高斯模糊同样也可以降阶,甚至也可以换成均值模糊,也能节省不少的计算。
 
 

下载链接:

UnityShader实例15:屏幕特效之Bloom的更多相关文章

  1. UnityShader实例13:屏幕特效之均值模糊(Box Blur)

    均值模糊(Box Blur) 概述 因为公司手游项目需求.须要一个适合手机平台的模糊效果,同一时候须要开放一个參数便于调节模糊值.我首先想到的就是ps里面的均值模糊. 查资料能够知道均值模糊是一种高速 ...

  2. UnityShader之屏幕特效基础

    1.什么是屏幕特效 我们这里讲的屏幕特效技术,指的是在渲染完整个场景后得到的屏幕图象的基础上,再对这个屏幕图像做一系列处理,实现出屏幕特效,使用这种技术可以为屏幕画面增添各种风格的艺术效果,比如泛光. ...

  3. Unity shader(CG) 写一个 散色、折射、反射、菲涅尔、gamma、简单后期屏幕特效

    http://www.lai18.com/content/506918.html 1.自生要求是很重要的,当然不是什么强迫工作之类的,而是自己有限的能力上不断的扩展兴趣上的内容. 2.用生活的眼光去发 ...

  4. Android实例-获取屏幕的物理分辨率

    相关资料: http://blog.qdac.cc/?p=1161 实例代码: unit Unit1; interface uses System.SysUtils, System.Types, Sy ...

  5. UnityShader实例09:Stencil Buffer&Stencil Test

    http://blog.csdn.net/u011047171/article/details/46928463 Stencil Buffer&Stencil Test 在开始前先吐槽下uni ...

  6. 【html】【15】特效篇--分页

    下载参考: http://aspx.sc.chinaz.com/query.aspx?keyword=%E5%88%86%E9%A1%B5&classID=&page=1 实例:  h ...

  7. 简单的javascript实例一(时钟特效)

    方便以后copy 时钟特效 <html> <head> <meta http-equiv="Content-Type" content="t ...

  8. UnityShader - 模拟动态光照特效

    模型贴片 + 特效Shader = 动态光照特效 效果是这样的: 做法简单粗暴,直接使用模型贴片: shader上使用了noise只是提供一种思路,也有更简单的方法代替

  9. [Xcode 实际操作]九、实用进阶-(15)屏幕截屏:截取当前屏幕上的显示内容

    目录:[Swift]Xcode实际操作 本文将演示如何截取屏幕画面,并将截取图片,存入系统相册. 在项目导航区,打开视图控制器的代码文件[ViewController.swift] import UI ...

随机推荐

  1. EL 表达式 函数 操作 字符串

    <%@tablib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> ${fn ...

  2. 九度OJ 1192:回文字符串 (基础题)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:3807 解决:1778 题目描述: 给出一个长度不超过1000的字符串,判断它是不是回文(顺读,逆读均相同)的. 输入: 输入包括一行字符串 ...

  3. js滚动到指定位置显示或隐藏元素

    $(function(){ $(window).scroll(function(){ var scroll_top=$(window).scrollTop(); console.log(scroll_ ...

  4. Python菜鸟之路:JQuery基础

    前言 JQuery可以理解为是一个模块,里边封装了DOM以及JavaScript,可以方便的对JQuery对象进行操作. 版本 尽量选择1.X系统的Jquery版本,例如1.12.jquery.js. ...

  5. linux c编程:线程互斥二 线程死锁

    死锁就是不同的程序在运行时因为某种原因发生了阻塞,进而导致程序不能正常运行.阻塞程序的原因通常都是由于程序没有正确使用临界资源. 我们举个日常生活中的例子来比喻死锁.我们把马路上行驶的汽车比作运行着的 ...

  6. 基于Spring框架的Shiro配置(转发:http://kdboy.iteye.com/blog/1103794)

    一.在web.xml中添加shiro过滤器 <!-- Shiro filter--> <filter> <filter-name>shiroFilter</f ...

  7. 从分布式锁来看redis和zookpeer!

    从分布式锁来看redis和zookpeer! 目前网上大部分的基于zookpeer,和redis的分布式锁的文章都不够全面.要么就是特意避开集群的情况,要么就是考虑不全,读者看着还是一脸迷茫.坦白说, ...

  8. 第7条:用列表推导式来取代map和filter

    核心知识点: 1.列表推导式要比内置的map和filter函数清晰,因为它无需额外编写lambda表达式. 2.列表推导式可以跳过输入列表中的某些元素,如果改用map来做,那就必须辅以filter方能 ...

  9. python利用wxpy监控微信公众号

    此次利用wxpy可以进行微信公众号的消息推送监测(代码超级简单),这样能进行实时获取链接.但是不光会抓到公众号的消息,好友的消息也会抓到(以后会完善的,毕竟现在能用了,而且做项目的微信号肯定是没有好友 ...

  10. 高性能javascript学习总结(3)--数据访问

    在 JavaScript 中,数据存储位置可以对代码整体性能产生重要影响.有四种数据访问类型:直接量,变量,数组项,对象成员.         直接量仅仅代表自己,而不存储于特定位置. JavaScr ...