高斯模糊性能优化

本文来自一次工作里碰到的问题,客户要求我们使用高斯模糊处理某些画面,但由于使用高斯模糊的区域大、多,帧率要求高,且从技术上看最好在窗口合成器中实现,从而导致了较为严重的性能问题,一方面 GPU 使用率飙升,另一方面系统帧率严重下降。因此我专门找到了一种于 2019 年在 SIGGRAPH 上提出的高斯模糊优化算法,代替我们原本的算法实现。从最终效果上来看,优化算法降低了 66% 的 GPU 性能消耗,CPU 消耗波动忽略,而高斯模糊的处理时间也降到了三分之一,效果极好,让原本几乎不能用的场景变得流畅。下面首先介绍传统高斯模糊算法。

常见高斯模糊方法

假设被模糊图片分辨率宽为 W,高为 H,假设高斯模糊核大小为 S,半径为 R(不包含中心点,即 S=2×R+1)。
按照高斯模糊原始用法,渲染单个像素需要对周围 S×S 个像素进行采样,并乘以对应的符合高斯分布的权值,公式为:
G(x,y)=12πσexp(x2+y22σ2)
其中 xy 为被采样像素到中心像素的横、纵距离,因此渲染一帧的时间复杂度为 O(W×H×S2)
但通过严格的数学证明,二维高斯模糊可以分为先横向后纵向,或先纵向后横向的两次一维模糊,两次模糊的核大小不变且相同,权值符合如下分布
G(x,y)=12πσexp(x22σ2)
因此渲染单个像素,仅需对其横向 S 个和纵向 S 个像素共 2S 个像素进行采样,因此渲染一帧的时间复杂度降为 O(W×H×S)
但要注意的是,该方法需要一个中间缓冲(纹理)记录第一次一维模糊的结果,因此会额外增加 O(W×H) 空间复杂度的消耗。

NonuniformBlur

由于高斯模糊的结果受核大小 Sσ 影响,二者越大图像越模糊,后者对性能没有影响,但前者的大小会显著影响程序性能,因此引入一种以高性能为目标的高斯模糊近似优化方法,该方法在《The Power of Box Filters: Real-time Approximation to Large Convolution Kernel by Box-filtered Image Pyramid 》[1] 提出,如下为太长不看版(本人总结):
高斯模糊的性能消耗大头在于对周围像素的采样频率,频率较大时会造成较高消耗。而 mipmap 各个层级的像素是原始纹理某个区域的像素平均值,因此文章作者使用 mipmap 这一数据结构来近似高斯模糊,并将采样频率降到较低水平。
同时,mipmap 在现代GPU上有较快的硬件生成速度(用计算着色器生成速度也很快),因此大尺度高斯模糊替换为本方法后,性能消耗会得到显著降低。
pGaussian 为一个像素高斯模糊结果,s(x,y) 为被采样像素值,l 为 mipmap 层数(注意,和 OpenGL ES 的层数是反过来的),wG(l) 为在 mipmap 第 l 层的权值,Sbox(l) 为在 mipmap 第 l 层采样的结果,公式为:
pGaussian=+G(x,y)s(x,y)dxdy=0+wG(l)Sbox(l)dl
其中
wG(l)16lln44π2σ4exp(4l2πσ2)
或者为
pGaussian=l=0lmaxwG(l)Sbox(l)l=0lmaxwG(l)
从公式可以看到,采样频率不再与高斯核大小有关,只与图像分辨率有关,单个像素的采样频率为 log2max(W,H)+1,而每张图片模糊的时间复杂度为 O(W×H×(1+log2max(W,H)))
同时,由于生成 mipmap,所以会额外增加三分之一的原始图片内存。
对一个 1920*720 的图片使用上述方法进行模糊,σ 为 5,在运行时生成着色器如下所示:

#version 320 es
precision mediump float;
in vec2 texCoordVsOut;
uniform sampler2D srcTex;
out vec4 fragColor;
void main()
{
  vec4 color = vec4(0.f, 0.f, 0.f, 0.f);
  color += textureLod(srcTex, texCoordVsOut,11.f) * 0.000000;
  color += textureLod(srcTex, texCoordVsOut,10.f) * 0.000000;
  color += textureLod(srcTex, texCoordVsOut,9.f) * 0.000000;
  color += textureLod(srcTex, texCoordVsOut,8.f) * 0.000000;
  color += textureLod(srcTex, texCoordVsOut,7.f) * 0.000000;
  color += textureLod(srcTex, texCoordVsOut,6.f) * 0.000000;
  color += textureLod(srcTex, texCoordVsOut,5.f) * 0.089084;
  color += textureLod(srcTex, texCoordVsOut,4.f) * 0.739691;
  color += textureLod(srcTex, texCoordVsOut,3.f) * 0.156954;
  color += textureLod(srcTex, texCoordVsOut,2.f) * 0.013316;
  color += textureLod(srcTex, texCoordVsOut,1.f) * 0.000898;
  color += textureLod(srcTex, texCoordVsOut,0.f) * 0.000057;
  fragColor = vec4(color.rgb, 1.0f);
}

可以看到,不仅 render pass 从原来的两次降为1次,单个像素采样频率也只有12次。

NonuniformBlur再优化

从优化方案生成的着色器可以看到部分权重为0,因此还能剔除这些权重的采样来降低性能消耗(可以按小于0.0039进行剔除,因为0.0039对应8位子像素归一化后一个色阶的大小,即1/256,因此权重小于0.0039意味着该层对最终结果的影响不足一个色阶,可以忽略)。
故而优化后的着色器为

#version 320 es
precision mediump float;
in vec2 texCoordVsOut;
uniform sampler2D srcTex;
out vec4 fragColor;
void main()
{
vec4 color = vec4(0.f, 0.f, 0.f, 0.f);
color += textureLod(srcTex, texCoordVsOut,5.f) * 0.089084;
color += textureLod(srcTex, texCoordVsOut,4.f) * 0.739691;
color += textureLod(srcTex, texCoordVsOut,3.f) * 0.156954;
color += textureLod(srcTex, texCoordVsOut,2.f) * 0.013316;
fragColor = vec4(color.rgb, 1.0f);
}

可以看到,采样频率降低为4次。

参考文献

[1] Xu T, Ren X, Wu E. The Power of Box Filters: Real-time Approximation to Large Convolution Kernel by Box-filtered Image Pyramid[M]//SIGGRAPH Asia 2019 Technical Briefs. 2019: 1-4.

posted @   绝对精神的自我展开  阅读(360)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
点击右上角即可分享
微信分享提示