Bloom效果需三步:提取亮部(阈值筛选)、高斯模糊(分离两Pass)、叠加原图;glBlitFramebuffer仅支持像素拷贝,无法实现亮度判断与模糊运算。

为什么直接 glBlitFramebuffer 不能直接做 Bloom
因为 Bloom 的核心是「提取亮部 + 模糊 + 叠加」,而 glBlitFramebuffer 只能做像素级硬拷贝,不支持亮度阈值筛选或高斯模糊。你看到的“泛光”其实是过曝区域被模糊后扩散的结果,必须自己控制渲染流程和中间纹理。
如何用 FBO 和多遍渲染实现 Bloom 主干流程
标准做法是三步:先渲染场景到 HDR 附件的 FBO;再用亮度阈值(如 max(r,g,b) > 1.0)提取亮部到一张小纹理;最后对这张小纹理做多次横向/纵向模糊(至少 5×5 等效),再放大叠加回原图。
- 必须用
GL_RGBA16F或GL_RGBA32F格式纹理存 HDR 颜色,否则亮部一截断就提不出有效辉光 - 降采样时建议用
GL_LINEAR缩放(不是GL_NEAREST),否则亮斑会碎成像素点 - 模糊阶段别在单 pass 里写 25 个采样——用两 pass 分离高斯(横向一次 + 纵向一次),每 pass 只采样 9 次,性能差一个数量级
- 最终叠加前,辉光纹理要乘一个系数(如
0.8),否则容易炸白;这个值得在运行时调,不是固定写死
Fragment Shader 里怎么安全提取亮度并避免 NaN
常见错误是直接写 float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); 却没检查 color 是否为 NaN 或 INF——尤其当用了未初始化的纹理坐标或除零时,整个辉光层会变黑块或全白噪点。
- 在提取亮度前加守卫:
if (!isfinite(color.r) || !isfinite(color.g) || !isfinite(color.b)) discard; - 阈值判断别用硬编码
> 1.0,改成可配置 uniformu_bloomThreshold,方便美术实时调参 - 别忘了伽马校正:HDR 渲染输出前通常已线性化,但 Bloom 叠加必须在同一线性空间下进行,否则辉光发灰或过艳
为什么用 compute shader 做模糊反而更麻烦
理论上 compute shader 能更好利用 GPU 并行性,但实际在 Bloom 这种小纹理(常缩到 1/4 或 1/8 分辨率)上,驱动开销和内存同步代价远超收益。而且 OpenGL 的 compute shader 调试难度高,glDispatchCompute 错一个参数就黑屏,还难定位。
- 除非你目标平台明确支持 OpenGL 4.3+ 且已有成熟 compute pipeline,否则坚持用传统 draw call + fullscreen quad 更稳
- 注意绑定纹理时别漏掉
glActiveTexture和glBindTexture配对,compute shader 里用layout(binding = 0)也得对应 glBindImageTexture 的 unit - 最易忽略的是:compute shader 写入的图像格式必须与纹理创建时一致,比如
GL_RGBA16F纹理不能绑定为rgba32fimage,否则行为未定义
Bloom 效果是否自然,80% 取决于阈值和模糊半径的匹配程度——调的时候别只盯着最终画面,要单独可视化「提取出的亮部纹理」和「模糊后的辉光纹理」,否则问题永远藏在中间步骤里。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











