UnityShader实例15:屏幕特效之Bloom

2024-05-28 23:32

本文主要是介绍UnityShader实例15:屏幕特效之Bloom,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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;};//在vert函数里对uv坐标做了四次偏移,对原像素周围临近的像素采样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; }	
//Frag函数用偏移的uv坐标采样,并且与原像素进行对比,如果亮度比原像素大,则取代原像素,因此亮部像素得到了扩展处理。这里ONE_MINUS_INTENSITY是由脚本传递过来的参数,用来控制bloom范围,功能就是讲低于这个值的像素设置为黑色。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; }	
//用vert传过来的uv坐标数组进行采样,并乘以对应的权重进行叠加,其结果是个近似高斯模糊。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;};
//考虑到D3D9的uv坐标Y轴是反转的,因此需要做个判断进行调整,防止图像倒转。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_D3D9if (_MainTex_TexelSize.y < 0.0)o.uv.w = 1.0 - o.uv.w;#endifreturn 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_EDITORFindShaders ();CheckSupport ();CreateMaterials ();	#endifif(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));	// material.SetFloat("_blurSize",BlurSize);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次多余的采样,第二步的高斯模糊同样也可以降阶,甚至也可以换成均值模糊,也能节省不少的计算。


下载链接:

       屏幕特效之Bloom

这篇关于UnityShader实例15:屏幕特效之Bloom的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1012003

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

实例:如何统计当前主机的连接状态和连接数

统计当前主机的连接状态和连接数 在 Linux 中,可使用 ss 命令来查看主机的网络连接状态。以下是统计当前主机连接状态和连接主机数量的具体操作。 1. 统计当前主机的连接状态 使用 ss 命令结合 grep、cut、sort 和 uniq 命令来统计当前主机的 TCP 连接状态。 ss -nta | grep -v '^State' | cut -d " " -f 1 | sort |

Java Websocket实例【服务端与客户端实现全双工通讯】

Java Websocket实例【服务端与客户端实现全双工通讯】 现很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发 出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点 – 浏 览器需要不断的向服务器发出请求,然而HTTP

828华为云征文|华为云Flexus X实例docker部署rancher并构建k8s集群

828华为云征文|华为云Flexus X实例docker部署rancher并构建k8s集群 华为云最近正在举办828 B2B企业节,Flexus X实例的促销力度非常大,特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Nginx等服务的需求,一定不要错过这个机会。赶紧去看看吧! 什么是华为云Flexus X实例 华为云Flexus X实例云服务是新一代开箱即用、体

Adblock Plus官方规则Easylist China说明与反馈贴(2015.12.15)

-------------------------------特别说明--------------------------------------- 视频广告问题:因Adblock Plus的局限,存在以下现象,优酷、搜狐、17173黑屏并倒数;乐视、爱奇艺播放广告。因为这些视频网站的Flash播放器被植入了检测代码,而Adblock Plus无法修改播放器。 如需同时使用ads

『功能项目』战士的平A特效【35】

我们打开上一篇34武器的切换实例的项目, 本章要做的事情是在战士的每次按A键时在指定位置生成一个平A特效 首先将之前下载的技能拖拽至场景中 完全解压缩后重命名为AEffect 拖拽至预制体文件夹 进入主角动画的战士动画层级 双击第一次攻击 选择Animation 创建事件 创建的动画事件帧放在攻击动画挥剑指定处 命名为PerpetualAtt

第49课 Scratch入门篇:骇客任务背景特效

骇客任务背景特效 故事背景:   骇客帝国特色背景在黑色中慢慢滚动着! 程序原理:  1 、 角色的设计技巧  2 、克隆体的应用及特效的使用 开始编程   1、使用 黑色的背景: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7d74c872f06b4d9fbc88aecee634b074.png#pic_center)   2