首页
/ three.js BloomPass 详解:基础泛光(Bloom)后处理通道的参数、原理与实战

three.js BloomPass 详解:基础泛光(Bloom)后处理通道的参数、原理与实战

2026-09-06 15:41:47作者:江焘钦

BloomPass 是 three.js 提供的一个基础泛光(辉光)后处理通道,通过对场景做一次高斯卷积模糊再与原画面做加法叠加,让高亮区域产生柔和的"光晕溢出"效果。本文以 BloomPass 官方文档 为主线,完整讲解其构造函数参数、属性与方法,并结合 BloomPass.js 源码 剖析两次卷积分离模糊、核权重生成等底层实现,最终给出可复制到 EffectComposer 管线中的实战用法。读完后你将掌握:如何正确导入并配置 BloomPass、strength/kernelSize/sigma 三个参数各自控制什么、以及它与 UnrealBloomPass 的选型区别。

快速上手:在 EffectComposer 中使用 BloomPass

BloomPass 属于 three.js 的 addon(附加模块),需要从 three/addons 路径显式导入,不能从 three 主包直接引入:

import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';

最简用法(与文档示例一致):

const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) );

const effectBloom = new BloomPass( 0.75 );
composer.addPass( effectBloom );

composer.addPass( new OutputPass() );

仓库中 webgl_points_dynamic.html 给出了一个完整的管线编排示例,BloomPass 被放在 RenderPass 之后、FilmPass 之前:

const renderModel = new RenderPass( scene, camera );
const effectBloom = new BloomPass( 0.75 );
const effectFilm  = new FilmPass();
const outputPass  = new OutputPass();

composer = new EffectComposer( renderer );
composer.addPass( renderModel );
composer.addPass( effectBloom );
composer.addPass( effectFilm );
composer.addPass( outputPass );

另一个参考是 webgl_materials_video.html,其中使用了更强调的 new BloomPass( 1.3 ),可对比不同 strength 取值对辉光强度的影响。

构造函数:strength、kernelSize 与 sigma

new BloomPass( strength : number, kernelSize : number, sigma : number )

文档定义的构造签名与默认值如下(对应源码 constructor):

参数 含义 默认值 源码作用
strength 泛光强度 1 作为 CombineShader 的 strength uniform,决定最终叠加辉光的亮度倍率
kernelSize 卷积核大小(采样点数) 25 写入 shader define KERNEL_SIZE_INT / KERNEL_SIZE_FLOAT,决定每次卷积的采样循环次数
sigma 高斯函数标准差 4 传给 buildKernel(),控制模糊的"扩散范围";sigma 越大,核尾部权重越分散,模糊越柔和

三个参数都是可选的,因此 new BloomPass()new BloomPass( 0.75 ) 都是合法写法。

源码中的参数落地方式

strength 的赋值在构造函数中直接写入 combine 材质的 uniform:

this.combineUniforms = UniformsUtils.clone( CombineShader.uniforms );
this.combineUniforms[ 'strength' ].value = strength;

kernelSize 则通过 shader 编译期宏传递,注意浮点与整型各写了一份(BloomPass.js#L90-L93):

defines: {
    'KERNEL_SIZE_FLOAT': kernelSize.toFixed( 1 ),
    'KERNEL_SIZE_INT': kernelSize.toFixed( 0 )
}

sigma 不参与 shader,而是在 CPU 端由 buildKernel() 生成归一化的一维高斯核:

function buildKernel( sigma ) {

    // We loop off the sqrt(2 * pi) * sigma term, since we're going to normalize anyway.
    const kMaxKernelSize = 25;
    let kernelSize = 2 * Math.ceil( sigma * 3.0 ) + 1;

    if ( kernelSize > kMaxKernelSize ) kernelSize = kMaxKernelSize;

    const halfWidth = ( kernelSize - 1 ) * 0.5;
    const values = new Array( kernelSize );
    let sum = 0.0;
    for ( let i = 0; i < kernelSize; ++ i ) {
        values[ i ] = gauss( i - halfWidth, sigma );
        sum += values[ i ];
    }
    // normalize the kernel
    for ( let i = 0; i < kernelSize; ++ i ) values[ i ] /= sum;
    return values;
}

从源码结构看有两个值得注意的细节:

  1. 核长度按 sigma 自适应2 * ceil( sigma * 3.0 ) + 1 意味着 sigma=4 时理论核长为 25,恰好等于上限 kMaxKernelSize = 25;若传入更大的 sigma,核长会被钳制在 25 以内。也就是说,单纯调大 sigma 超过 4 并不能显著加宽模糊范围。
  2. 构造函数的 kernelSize 决定采样次数,sigma 决定核权重形状cKernel uniform 存放的是按 sigma 计算好的权重数组(BloomPass.js#L77),而 shader 循环上限由 kernelSize 宏决定(见下文 ConvolutionShader 的实现)。若 kernelSize 小于 buildKernel 生成的数组长度,多余的权重不会参与采样。

内部结构:两块 shader 与两块纹理

.materialConvolution : ShaderMaterial

卷积模糊通道的材质,基于 ConvolutionShader。其片元着色器执行 1D 卷积:

vec2 imageCoord = vUv;
vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 );

for( int i = 0; i < KERNEL_SIZE_INT; i ++ ) {
    sum += texture2D( tDiffuse, imageCoord ) * cKernel[ i ];
    imageCoord += uImageIncrement;
}

gl_FragColor = sum;

每帧渲染时 cKernel 固定为 sigma 生成的高斯权重,而采样步进方向 uImageIncrement 在 X/Y 两遍之间切换。BloomPass 在类静态属性中预定义了两个步进向量(BloomPass.js#L236-L237):

BloomPass.blurX = new Vector2( 0.001953125, 0.0 );
BloomPass.blurY = new Vector2( 0.0, 0.001953125 );

0.0019531251/512,是卷积类后处理中常用的 UV 步进常量。从源码结构看,这种"两次 1D 卷积代替一次 2D 卷积"的写法(先横向后纵向)把 25×25=625 次采样降为 25+25=50 次,是该通道保持低成本的关键。

.materialCombine : ShaderMaterial

合成通道的材质,使用文件内定义的 CombineShader,片元逻辑非常简单——把模糊结果乘上 strength 后以加法混合叠加回画面:

uniform float strength;
uniform sampler2D tDiffuse;
varying vec2 vUv;

void main() {
    vec4 texel = texture2D( tDiffuse, vUv );
    gl_FragColor = strength * texel;
}

材质以 AdditiveBlendingtransparent: true 创建(BloomPass.js#L54-L63),因此最终效果是"原图 + strength × 模糊图",这也是 BloomPass 只适合做整体辉光而非选择性泛光的原因——它没有亮度阈值(luminance threshold),所有像素都参与辉光叠加。

.combineUniforms / .convolutionUniforms : Object

两个材质对应的 uniform 集合,均为 UniformsUtils.clone() 的克隆结果,可以在运行时动态修改。例如想让辉光随时间脉冲,可以直接改 bloomPass.combineUniforms[ 'strength' ].value,无需重建材质。

内部渲染目标:HalfFloatType 双缓冲

构造函数还创建了两个内部 render target(BloomPass.js#L107-L110):

this._renderTargetX = new WebGLRenderTarget( 1, 1, { type: HalfFloatType, depthBuffer: false } );
this._renderTargetX.texture.name = 'BloomPass.x';
this._renderTargetY = new WebGLRenderTarget( 1, 1, { type: HalfFloatType, depthBuffer: false } );
this._renderTargetY.texture.name = 'BloomPass.y';

_renderTargetX_renderTargetY 分别承接 X/Y 两遍卷积的结果,采用 HalfFloatType 半浮点格式以保留高亮区细节,并且关闭了 depthBuffer(全屏 quad 不需要深度)。初始尺寸为 1×1,由 setSize() 在实际使用时扩展。

.needsSwap : boolean —— 覆写为 false 的含义

文档中 .needsSwap 被标记为"Overwritten to disable the swap",默认值为 falsePass 基类needsSwap 默认为 true,即 EffectComposer 在每帧执行完一个 pass 后会交换 read/write 缓冲:

// EffectComposer.js
pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive );
if ( pass.needsSwap ) { /* 交换读写缓冲 */ }

而 BloomPass 在 render() 末尾是直接写回 readBuffer 的:

// Render original scene with superimposed blur to texture
this._fsQuad.material = this.materialCombine;
this.combineUniforms[ 'tDiffuse' ].value = this._renderTargetY.texture;

renderer.setRenderTarget( readBuffer );
if ( this.clear ) renderer.clear();
this._fsQuad.render( renderer );

也就是说,该通道以"就地加法叠加"的方式把模糊结果加到上一通道的输出上,输出直接落在 readBuffer,若再执行 swap 反而会把结果写进错误的缓冲。needsSwap = false 正是保证"叠加在原画之上"这一语义正确的前提。这个写法也让 BloomPass 天然适合插在任意 RenderPass 之后,不影响后续 pass(如 FilmPass、FocusShader)读取当前帧画面。

.render() 的完整执行流程

render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) 的签名与 Pass#render 保持一致,文档参数说明如下:

  • renderer:渲染器;
  • writeBuffer:通道的写缓冲,通常作为本通道的渲染目标——但如前所述,BloomPass 实际上不用它,直接写 readBuffer;
  • readBuffer:上一通道(如 RenderPass)的输出,BloomPass 从中读取原始场景;
  • deltaTime:距上帧的秒数,本通道未使用该参数;
  • maskActive:是否有 MaskPass 蒙版处于激活状态。

完整流程分三步(BloomPass.js#L127-L164):

  1. X 向卷积:取 readBuffer.texture 作为输入,uImageIncrement = BloomPass.blurX,输出到 _renderTargetX
  2. Y 向卷积:输入换成 _renderTargetX.textureuImageIncrement = BloomPass.blurY,输出到 _renderTargetY
  3. 加法合成:切换为 materialCombine,把 _renderTargetY 的模糊结果乘以 strength,以加法混合叠加回 readBuffer

此外还有一处与蒙版系统的配合细节:若 maskActive 为 true,render 开头会 renderer.state.buffers.stencil.setTest( false ) 关闭模板测试,合成前再恢复(BloomPass.js#L129-L158),保证两遍卷积在全屏范围进行,而最终的加法叠加仍受蒙版约束。

.setSize() 与 .dispose()

.setSize( width : number, height : number )

设置通道尺寸,实现非常简单——只是同步两块内部 render target(BloomPass.js#L172-L177):

setSize( width, height ) {
    this._renderTargetX.setSize( width, height );
    this._renderTargetY.setSize( width, height );
}

通常不需要手动调用:EffectComposer.setSize() 会遍历所有 pass 并传入乘以 pixelRatio 后的有效尺寸(EffectComposer.js#L317-L330)。在 resize 事件中只需调用 composer.setSize( window.innerWidth, window.innerHeight ) 即可。

.dispose()

释放本实例分配的 GPU 资源,文档建议在通道不再使用时调用。实现上会依次 dispose 两块 render target、两个材质以及内部 FullScreenQuadBloomPass.js#L183-L193):

dispose() {
    this._renderTargetX.dispose();
    this._renderTargetY.dispose();
    this.materialCombine.dispose();
    this.materialConvolution.dispose();
    this._fsQuad.dispose();
}

选型建议:BloomPass vs UnrealBloomPass

文档开宗明义:UnrealBloomPass 能产生更进阶的泛光,但代价更高。结合源码可以给出一个清晰的对比:

维度 BloomPass UnrealBloomPass
模糊策略 两次 1D 高斯卷积(固定 25 点核) mip map 链 + 多级不同半径模糊加权合成
亮度阈值 无,全像素参与叠加 支持 threshold,只有亮部产生辉光
参数 strength / kernelSize / sigma resolution / strength / radius / threshold
成本 低,两次全屏卷积 较高,需多张 mip 纹理
依赖 独立 需开启 tone mapping 才能正确表现

从实现成本看,BloomPass 每帧只做 3 次全屏 quad 绘制(X 卷积、Y 卷积、加法合成),且不产生 mip 链,因此适合对帧率敏感或只需"轻微柔光"的场景;而需要"霓虹灯、阳光过曝"这类阈值型、多级辉光的视觉时,应选用 UnrealBloomPass.js

小结

  • BloomPass 是一个"低成本泛光"通道:sigma 生成高斯核 → X/Y 两次 1D 卷积 → 按 strength 加法叠加回原图;
  • 三个构造参数中,strength 控制辉光亮度、sigma 控制模糊柔和度(且受 25 点核长钳制)、kernelSize 控制卷积采样次数;
  • needsSwap = false + 直接写回 readBuffer 的设计,使它成为可安全插入管线中段的叠加型通道;
  • 运行 composer.setSize() 会自动同步其内部缓冲尺寸;通道弃用时记得调用 dispose() 释放 GPU 资源。
登录后查看全文
热门项目推荐
相关项目推荐