用Unity实现Bloom

Bloom是一种常见的后处理效果,用来给发光的物体增加光晕。接下来让我们看看如何在Unity中实现它。

首先,需要在主相机上挂一个后处理C#脚本:

using UnityEngine;
using System;[ExecuteInEditMode, ImageEffectAllowedInSceneView]
public class BloomEffect : MonoBehaviour {void OnRenderImage (RenderTexture source, RenderTexture destination) {Graphics.Blit(source, destination);}
}

接下来,我们可以认为Bloom其实就是将原始的画面进行模糊之后,再叠加到原始画面上。那么如何进行模糊呢?我们可以参考mipmap,对原始的render texture不断进行downsample,重复一定次数之后,再不断进行upsample,回到原始render texture的大小。此时得到的render texture必然是模糊的,模糊的程度可以通过重复的次数进行调节。

可以使用双线性插值来进行sample。所谓双线性插值,就是将目标texel周围4个texel两两插值得到最后结果。使用双线性插值的的downsample示意图如下:

再看一下使用双线性插值的upsample示意图:

不断downsample再upsample的代码如下:

     int width = source.width / 2;int height = source.height / 2;RenderTextureFormat format = source.format;RenderTexture currentDestination = textures[0] =RenderTexture.GetTemporary(width, height, 0, format);Graphics.Blit(source, currentDestination);RenderTexture currentSource = currentDestination;int i = 1;for (; i < iterations; i++) {width /= 2;height /= 2;if (height < 2) {break;}currentDestination = textures[i] =RenderTexture.GetTemporary(width, height, 0, format);Graphics.Blit(currentSource, currentDestination);currentSource = currentDestination;}for (i -= 2; i >= 0; i--) {currentDestination = textures[i];textures[i] = null;Graphics.Blit(currentSource, currentDestination);RenderTexture.ReleaseTemporary(currentSource);currentSource = currentDestination;}Graphics.Blit(currentSource, destination);RenderTexture.ReleaseTemporary(currentSource);

来看下不同迭代次数下的模糊效果。首先是原图:

接下来分别是1-4次的效果:

可以看到,样子有了,但是还不够好,锯齿感太浓厚了。那么我们就要为后处理编写自定义的shader了,自定义的shader拥有两个pass,一个pass是给downsample用的,还有一个是给upsample用的:

     int width = source.width / 2;int height = source.height / 2;RenderTextureFormat format = source.format;RenderTexture currentDestination = textures[0] =RenderTexture.GetTemporary(width, height, 0, format);Graphics.Blit(source, currentDestination, bloom, BoxDownPass);RenderTexture currentSource = currentDestination;int i = 1;for (; i < iterations; i++) {width /= 2;height /= 2;if (height < 2) {break;}currentDestination = textures[i] =RenderTexture.GetTemporary(width, height, 0, format);Graphics.Blit(currentSource, currentDestination, bloom, BoxDownPass);currentSource = currentDestination;}for (i -= 2; i >= 0; i--) {currentDestination = textures[i];textures[i] = null;Graphics.Blit(currentSource, currentDestination, bloom, BoxUpPass);RenderTexture.ReleaseTemporary(currentSource);currentSource = currentDestination;}Graphics.Blit(currentSource, destination, bloom, BoxUpPass);RenderTexture.ReleaseTemporary(currentSource);

为了消除锯齿,需要让画面更加模糊。我们可以增加一个box filter来融合像素。这里使用的是2*2的box kernel:

采样的4个点由于双线性插值的关系,实际上还会采样到周围4个点,这样总共真正采样到的点有4x2x2=16个。

     half3 Sample (float2 uv) {return tex2D(_MainTex, uv).rgb;}half3 SampleBox (float2 uv) {float4 o = _MainTex_TexelSize.xyxy * float2(-1, 1).xxyy;half3 s =Sample(uv + o.xy) + Sample(uv + o.zy) +Sample(uv + o.xw) + Sample(uv + o.zw);return s * 0.25f;}

让我们来看看迭代4次的模糊效果:

可以发现这次锯齿是彻底没有了,但是画面模糊过头了。为什么会这样呢?原因出在upsample上。每次upsample,我们是其实希望原本对应像素的信息能最大程度的保留,但是使用box filter,再加上双线性插值,导致结果并非如此:

蓝色是当前采样点,理论上来说,upsample后的结果,蓝色所在的pixel的权重应该最高,但实际上它是最小的:

如图,对蓝色点进行采样时,会对2*2的box filter的4个顶点分别进行采样,也就是红色的点。然后根据双线性插值,距离红色点近的pixel权重更高,我们假设近的权重为2,远的权重为1,得到的最后结果即如图所示。蓝色点所在的pixel权重居然只有1,也就意味着在upsample的过程中还会不断被周围的像素模糊掉。

因此,在upsample的过程中,我们需要调整一下box kernel的大小,让它变成1*1:

     // downsample:delta = 1// upsample:delta = 0.5half3 SampleBox (float2 uv, float delta) {float4 o = _MainTex_TexelSize.xyxy * float2(-delta, delta).xxyy;half3 s =Sample(uv + o.xy) + Sample(uv + o.zy) +Sample(uv + o.xw) + Sample(uv + o.zw);return s * 0.25f;}

来看看经过调整后迭代4次的模糊效果:

有了模糊效果之后,下一步要做的就是把这些效果叠加起来。我们可以把迭代的中间结果都进行叠加:

叠加本身很简单,就是在upsample的时候把blend mode设置为one one。然后在最后一次upsample时,还需要传入source render texture:

     int width = source.width / 2;int height = source.height / 2;RenderTextureFormat format = source.format;RenderTexture currentDestination = textures[0] =RenderTexture.GetTemporary(width, height, 0, format);Graphics.Blit(source, currentDestination, bloom, BoxDownPass);RenderTexture currentSource = currentDestination;int i = 1;for (; i < iterations; i++) {width /= 2;height /= 2;if (height < 2) {break;}currentDestination = textures[i] =RenderTexture.GetTemporary(width, height, 0, format);Graphics.Blit(currentSource, currentDestination, bloom, BoxDownPass);currentSource = currentDestination;}for (i -= 2; i >= 0; i--) {currentDestination = textures[i];textures[i] = null;Graphics.Blit(currentSource, currentDestination, bloom, BoxUpPass);RenderTexture.ReleaseTemporary(currentSource);currentSource = currentDestination;}bloom.SetTexture("_SourceTex", source);Graphics.Blit(currentSource, destination, bloom, ApplyBloomPass);RenderTexture.ReleaseTemporary(currentSource);
     Pass { // BoxUpPassBlend One OneCGPROGRAM#pragma vertex VertexProgram#pragma fragment FragmentProgramhalf4 FragmentProgram (Interpolators i) : SV_Target {return half4(SampleBox(i.uv, 0.5), 1);}ENDCG}Pass { // ApplyBloomPassCGPROGRAM#pragma vertex VertexProgram#pragma fragment FragmentProgramhalf4 FragmentProgram (Interpolators i) : SV_Target {half4 c = tex2D(_SourceTex, i.uv);c.rgb += SampleBox(i.uv, 0.5);return c;}ENDCG}

尝试不同迭代次数下的效果如下:

大体上已经OK了,剩下的就是一些细节补充。首先,我们希望bloom只对比较明亮的像素才生效。因此在第一次downsample时,可以做一次预处理,把不够亮的像素剔除掉:

     half3 Prefilter (half3 c) {half brightness = max(c.r, max(c.g, c.b));half contribution = max(0, brightness - _Threshold);contribution /= max(brightness, 0.00001);return c * contribution;}      Pass { // BoxDownPrefilterPassCGPROGRAM#pragma vertex VertexProgram#pragma fragment FragmentProgramhalf4 FragmentProgram (Interpolators i) : SV_Target {return half4(Prefilter(SampleBox(i.uv, 1)), 1);}ENDCG}
     RenderTexture currentDestination = textures[0] =RenderTexture.GetTemporary(width, height, 0, format);Graphics.Blit(source, currentDestination, bloom, BoxDownPrefilterPass);

我们提供了参数_Threshold方便调节,来看下从0变化到1的效果:

最后,再增加一个参数来调节bloom的强度。我们将其应用于最后一次upsample的pass上:

     Pass { // ApplyBloomPassCGPROGRAM#pragma vertex VertexProgram#pragma fragment FragmentProgramhalf4 FragmentProgram (Interpolators i) : SV_Target {half4 c = tex2D(_SourceTex, i.uv);c.rgb += _Intensity * SampleBox(i.uv, 0.5);return c;}ENDCG}

最后我们看一下_Intensity不断增加的bloom效果:

如果你觉得我的文章有帮助,欢迎关注我的微信公众号:我是真的想做游戏啊

Reference

[1] Bloom Blurring Light

用Unity实现Bloom相关推荐

  1. unity后处理Bloom与HDR和ColorGrade,ToneMapping

    (1)HDR的格式: 在unity中是 RGBA 每个通道16位 非HDR: 每个通道8位 (2)逐步执行DrawCall时,你会注意到场景看起来比最终结果要暗.发生这种情况是因为这些步骤存储在HDR ...

  2. Unity Shader - Bloom(光晕、泛光)

    前言 Bloom(光晕)是一种计算机图形效果,用于视频游戏,演示和高动态范围渲染(HDRR)中,以再现真实相机的成像伪像.该效果会产生从图像中明亮区域的边界延伸的条纹(或羽毛),从而造成超亮的光使摄像 ...

  3. 图形学基础——HDR与LDR

    百人计划学习视频连接:HDR与LDR 一.基本概念 什么是动态范围 (Dynamic Range)? 简称DR,就是指最高的和最低的亮度之间的比值 因此有以下两种 LDR = Low Dynamic ...

  4. 【博物纳新】Isaura—光环特效开源库评测

    [博物纳新]是UWA重磅推出的全新栏目,旨在为开发者推荐新颖.易用.有趣的开源项目,帮助大家在项目研发之余发现世界上的热门项目.前沿技术或者令人惊叹的视觉效果,并探索将其应用到自己项目的可行性.很多时 ...

  5. unity, 荧光效果(bloom)

    ----更新:2015-5-31 详细实现过程见:http://www.cnblogs.com/wantnon/p/4542172.html ----原帖:2015-4-16 用摄像机特效只能做全屏b ...

  6. Unity Shader 屏幕后效果——Bloom外发光

    Bloom的原理很简单,主要是提取渲染图像中的亮部区域,并对亮部区域进行模糊处理,再与原始图像混合而成. 一般对亮部进行模糊处理的部分采用高斯模糊,关于高斯模糊,详见之前的另一篇博客: https:/ ...

  7. Unity Shader PostProcessing - 8 - Bloom 泛光

    文章目录 实现 提取亮度图 将提取的像素模糊 将模糊的像素与原图叠加 Project References 事情一大堆,要拿快递,又要寄快递,还要帮人板家具,还要去买菜,还要偶尔做饭,还要做包子,拖地 ...

  8. Unity 给手游用的高性能辉光(Bloom) Shader

    某天项目中美术大哥们提出使用Bloom的需求.但是直接使用Unity 的PostProcess Bloom性能消耗又很大. 所以开发了一个简单的Bloom效果,能满足基本需求,又能兼顾性能.**使用方 ...

  9. unity生成预制体_【Unity·月之泪复刻】Bloom+摇曳+可交互草地

    最近在做机械纪元的同人游戏,作为名场面-月之泪花田必须有姓名→ω→ 经过两天的缝合(不是),目前效果如下: awsl 对比一下原版-awsl: 机械纪元中的月之泪花田 场景包含: 草地摇曳效果 角色与 ...

最新文章

  1. script标签的defer属性
  2. WWW 2020 | 信息检索中基于上下文的文本词项权重生成
  3. Spring Boot应用的后台运行配置
  4. PowerBI Report Server 自定义视图无法显示故障解决
  5. Java编程中组合、继承和代理的区别
  6. 啤酒游戏及其牛鞭效应的vensim模拟
  7. 如何使用Arduino UNO开发板编程ATtiny85
  8. Android数据库更新并保留原来数据的实现
  9. 关于javaBean运行后出现Name was not previously introduced as per JSP.5.3的解决方法
  10. [IDE]vs code更新后变成英文版
  11. 美国无人机技术及相关项目
  12. 妈妈不在身边的第X个母亲节,用AI找回她的美好时光
  13. Tableau-热力图
  14. pr中创建镜像效果,并用渐变进行过渡
  15. x264参数设置详解(x264 settings)
  16. Quartus II 13.0波形仿真
  17. 数据挖掘与分析——回归模型
  18. 2022年在家安装一个监控摄像头需要多少成本
  19. 企业薪酬体系设计:弱化“工资补丁”,用定额平衡修正工作量
  20. 软硬一体的流媒体边缘计算设备在视频“云、边、端”解决方案中的重要作用

热门文章

  1. Flash 终将谢幕:微软将于年底停止对 Flash 的支持
  2. 《失控》之九--《冒出》的生态圈
  3. HDDREG结合MHDD快速修复硬盘坏道(转载)
  4. imagecreatefromjpeg(),imagecreatefrompng()打开不同格式的图片报错误
  5. 23考研预报名详细步骤、流程及常见问题解答
  6. 惠普手机将入华展开厮杀 - 摘自IT时代周刊
  7. 良心推荐5款Python编辑器,请择优选用!
  8. php select只有一条_读取数据库如何只取出一条数据????请赐教!
  9. BUUCTF-强网杯-随便注
  10. 小程序 项目文件夹命名导致的Bug