接上一篇基于高斯模糊的Bloom继续进行接下来的学习。
1 一些必要的思考*
1.1 关于高质量Bloom
前面提到了,Bloom对于游戏必不可少的效果之一,于是我们不仅仅要把Bloom效果实现出来,效果的质量好坏就更加是我们需要关注的点了。高质量泛光(bloom)从理论到实战这篇文章中对高质量、高品质的Bloom做了三点定义,我就直接把原话搬过来了:
- 发光物边缘向外“扩张”得足够大
- 发光物中心足够量(甚至超过1.0而被clamp成白色)
- 该亮的地方要亮,不该亮的地方不亮
这篇文章逻辑非常清楚,从实现高品质Bloom出发,介绍了如何利用HDR实现快速的大范围模糊、如何保证中心的高亮区域、以及如何处理泛光的方块图样、闪烁等问题,值得一看!
1.2 关于Kawase模糊
不同于【Unity Shader】屏幕后处理3.0:均值模糊和高斯模糊中实现的均值模糊(Box Blur)和高斯模糊(Gassian Blur),Kawase模糊的kernel会随迭代次数移动,而不是固定的:会对当前像素越来越远的四个角进行采样(黑点向四个方向偏移成四个采样点),且在两个相同大小的纹理之间进行乒乓式的Blit:(图来自高品质后处理:十种图像模糊算法的总结与实现 - 知乎 (zhihu.com))
Kawase模糊实现的效果跟高斯模糊差不多,且性能要优于高斯模糊。
2 新的模糊思想:双重模糊
2.1 Unity中实现Bloom的过程
上一篇博客中我用Unity自带Bloom实现效果跟高斯模糊实现的效果做了个对比,下面我们再Frame Debug一下,看看到底是怎样的一个过程:
可以发现Unity自带Bloom的实现是有一个先降采样再升采样的过程,跟之前我实现Bloom的方式多了一步升采样,得到的效果也会比仅降采样的效果好。
2.2 双重模糊思想
那么上面提到的先降采样再升采样的方法,就是一种新的模糊思想——双重模糊(Dual Kawase Blur,简称Dual Blur),它是SIGGRAPH 2015上ARM团队提出的一种衍生自Kawase Blur的模糊算法(是参考这篇文章的说法),所以会叫做Dual Kawase Blur。
但现在我认为与其说双重模糊是一种具体的模糊算法,不如说它是一种先降后升的模糊思想,它不仅可以基于Kawase Blur,还可以基于Box Blur、Tent Blur(两次Box Blur)或是Gaussian Blur,取决于在Blit的过程中选择哪种Blur算法。
根据实时渲染学习笔记—光晕效果(bloom)过程:将全分辨率原图采样为不同更小分辨率的纹理以扩大模糊范围,将这些图片升采样为原分辨率大小的纹理并叠加到原图上,实现大范围的Bloom效果。
升降采样的插值和滤波
为什么会有这个疑问呢?因为我发现,在c#脚本中,即使是分辨率不同的纹理之间的跨度也还是简单的:
Graphics.Blit(rt0, rt1, Material, 1); //假设rt0和rt1分辨率不相同
那么分辨率高->低or低->高到底是如何重建的?我一直很模糊,通过大量搜索(例如在做降采样处理时,是先滤波,还是先降采样,二者有区别吗?),我简单理解的生/降采样中插值和滤波之间的关系应该是:
- 降采样——先滤波(选择的滤波方法),后插值(抽取值)
- 升采样——先插值(内插值法,插入若干0值),后滤波
我不确定这样的说法是否绝对正确,不过这样想的话也是能解决我的困惑的。
2.3 优点
与之前相比这个方法得到的Bloom效果有这样三种优点:
模糊范围扩大得更好
这里对应着上述提到的缺点1性能问题,双重模糊的降采样很好得解决了这个问题,达到了模糊范围扩大又好控制、又节省性能的效果。这个时候如果你想说:好像上面的方法里也有用到降采样呀?两者有什么区别?我们不妨看看上面方法是如何体现降采样的:
int rtW = source.width / downSample;
int rtH = source.height / downSample;
//定义rt
RenderTexture rt0 = RenderTexture.GetTemporary(rtW, rtH, 0);
rt0.filterMode = FilterMode.Bilinear;
明不明显!这里相当于在进行Blit之前只是简单的对源图像降低了分辨率,仅此而已!并没有生成一个个mip层、没有形成一个像Mipmap一样的“图像金字塔”。
淡化了最终效果的边界感
降采样扩大了模糊范围后,如果只是单纯的把下采样的最高Mip层级叠加到的源图像上,得到的结果会是一种界限分明的效果,我们借用这篇文章里的只进行下采样的效果:
过渡得如此突兀显然不是我们想要的,于是就来到了双重模糊的第二个要点——升采样。我们不仅仅把最高Mip层叠加到原图,而是把每一层叠加到一起,这样过渡的就会非常自然,且中间部分该亮的也会亮度合适:(图还是引用上面提到的文章内容)
提升了性能
这种方法为什么可以提升性能?——我们可以在低分辨率图像上进行小范围滤波,进而达到在高分辨率图像上大范围滤波等价的效果,比较而言性能当然有所提升。
3 总结不同方法使用blur kernel
双重模糊的实现主要体现在两点:
- 降采样
- 升采样
我大概列举一下我能看到的一些文章里实现这两步的方法。
3.1 采用高斯模糊
高质量泛光(bloom)从理论到实战这篇文章中最开始降采样和升采样都是用的5X5的高斯滤波盒。
【Free Bird/URP教学】11.双重模糊 - 知乎 (zhihu.com)这篇文章也是采用的纵横的高斯模糊。
3.2 《使命召唤》的思路
《使命召唤:高级战争》中介绍了更好的双重模糊的思路,采用了优化后的降(下)采样和升(上)采样滤波器。
为了进一步提升性能介绍了更好的滤波盒,(下面的图片保存自高质量泛光(bloom)从理论到实战,实际出处:Advances in Real-Time Rendering in 3D Graphics and Games - SIGGRAPH 2014,详细PPT参考:NEXT GENERATION POST PROCESSING IN CALL OF DUTY: ADVANCED WARFARE)
降采样:更巧妙的kernel
该方法中,降采样采用了下面这种2X2这种采样模式,如动图所示一共采样了5组(剔除重合采样点一共有13个需要进行纹理查询的采样点),整个过程可以理解为5个“4次双线性纹理查询平均值”的加权平均和。文章实时渲染学习笔记—光晕效果(bloom)更加清楚了介绍降采样的方式,可自行查看。
上采样:tent kernel
(本小节的叙述主要参考了实时渲染学习笔记—光晕效果(bloom))
我们已经理解了所谓的上采样就是将降采样得到的
N个Mip层的图像还原到原图像分辨率大小,每将一个Mip层还原分辨率,还要进行一次滤波,意味着一共要进行N次高斯滤波,代价是很大的。
该方法选择了用简单的tent kernel,并采用了一种渐进的采样方式:图中一共有5个Mip层,从最高的第5Mip层出发,Mip5重构到Mip4的分辨率,再与原先的Mip4叠加;叠加后的Mip4重构到Mip3分表率,再与原先的Mip3叠加,以此类推。
更值得注意的是,他们提出了一个radius parameter来控制范围,卷积核的权重值不再是“一个萝卜一个坑了”,核中间有一个“洞”, 类似卷积里的空洞卷积(总结-空洞卷积(Dilated/Atrous Convolution)),更加降低了计算量。
3.3 采用Kawase模糊
参考了高品质后处理:十种图像模糊算法的总结与实现,基于两种不同的kernel:
其核心思路在于会在Blit的过程中对RT进行降采样和升采样,就像下图:
3.4 基于简单的Box Blur
【Shader】后处理之模糊算法这篇文章采用的是基于简单均值模糊的双重模糊。