拓冰建站拓冰建站
首页 / 资讯中心 / 正文

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

three.js BloomPass 详解基础泛光Bloom后处理通道的参数、原理与实战【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsBloomPass 是 three.js 提供的一个基础泛光辉光后处理通道通过对场景做一次高斯卷积模糊再与原画面做加法叠加让高亮区域产生柔和的光晕溢出效果。本文以 BloomPass 官方文档 为主线完整讲解其构造函数参数、属性与方法并结合 BloomPass.js 源码 剖析两次卷积分离模糊、核权重生成等底层实现最终给出可复制到EffectComposer管线中的实战用法。读完后你将掌握如何正确导入并配置 BloomPass、strength/kernelSize/sigma三个参数各自控制什么、以及它与 UnrealBloomPass 的选型区别。快速上手在 EffectComposer 中使用 BloomPassBloomPass 属于 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 与 sigmanew BloomPass( strength : number, kernelSize : number, sigma : number )文档定义的构造签名与默认值如下对应源码 constructor参数含义默认值源码作用strength泛光强度1作为 CombineShader 的strengthuniform决定最终叠加辉光的亮度倍率kernelSize卷积核大小采样点数25写入 shader defineKERNEL_SIZE_INT/KERNEL_SIZE_FLOAT决定每次卷积的采样循环次数sigma高斯函数标准差4传给buildKernel()控制模糊的扩散范围sigma 越大核尾部权重越分散模糊越柔和三个参数都是可选的因此new BloomPass()、new BloomPass( 0.75 )都是合法写法。源码中的参数落地方式strength的赋值在构造函数中直接写入 combine 材质的 uniformthis.combineUniforms UniformsUtils.clone( CombineShader.uniforms ); this.combineUniforms[ strength ].value strength;kernelSize则通过 shader 编译期宏传递注意浮点与整型各写了一份BloomPass.js#L90-L93defines: { 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 were 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; }从源码结构看有两个值得注意的细节核长度按 sigma 自适应2 * ceil( sigma * 3.0 ) 1意味着 sigma4 时理论核长为 25恰好等于上限kMaxKernelSize 25若传入更大的 sigma核长会被钳制在 25 以内。也就是说单纯调大 sigma 超过 4 并不能显著加宽模糊范围。构造函数的 kernelSize 决定采样次数sigma 决定核权重形状cKerneluniform 存放的是按 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-L237BloomPass.blurX new Vector2( 0.001953125, 0.0 ); BloomPass.blurY new Vector2( 0.0, 0.001953125 );0.001953125即1/512是卷积类后处理中常用的 UV 步进常量。从源码结构看这种两次 1D 卷积代替一次 2D 卷积的写法先横向后纵向把 25×25625 次采样降为 252550 次是该通道保持低成本的关键。.materialCombine : ShaderMaterial合成通道的材质使用文件内定义的 CombineShader片元逻辑非常简单——把模糊结果乘上 strength 后以加法混合叠加回画面uniform float strength; uniform sampler2D tDiffuse; varying vec2 vUv; void main() { vec4 texel texture2D( tDiffuse, vUv ); gl_FragColor strength * texel; }材质以AdditiveBlending且transparent: true创建BloomPass.js#L54-L63因此最终效果是原图 strength × 模糊图这也是 BloomPass 只适合做整体辉光而非选择性泛光的原因——它没有亮度阈值luminance threshold所有像素都参与辉光叠加。.combineUniforms / .convolutionUniforms : Object两个材质对应的 uniform 集合均为UniformsUtils.clone()的克隆结果可以在运行时动态修改。例如想让辉光随时间脉冲可以直接改bloomPass.combineUniforms[ strength ].value无需重建材质。内部渲染目标HalfFloatType 双缓冲构造函数还创建了两个内部 render targetBloomPass.js#L107-L110this._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默认值为false。Pass 基类中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 实际上不用它直接写 readBufferreadBuffer上一通道如 RenderPass的输出BloomPass 从中读取原始场景deltaTime距上帧的秒数本通道未使用该参数maskActive是否有 MaskPass 蒙版处于激活状态。完整流程分三步BloomPass.js#L127-L164X 向卷积取readBuffer.texture作为输入uImageIncrement BloomPass.blurX输出到_renderTargetXY 向卷积输入换成_renderTargetX.textureuImageIncrement BloomPass.blurY输出到_renderTargetY加法合成切换为materialCombine把_renderTargetY的模糊结果乘以 strength以加法混合叠加回readBuffer。此外还有一处与蒙版系统的配合细节若maskActive为 truerender 开头会renderer.state.buffers.stencil.setTest( false )关闭模板测试合成前再恢复BloomPass.js#L129-L158保证两遍卷积在全屏范围进行而最终的加法叠加仍受蒙版约束。.setSize() 与 .dispose().setSize( width : number, height : number )设置通道尺寸实现非常简单——只是同步两块内部 render targetBloomPass.js#L172-L177setSize( 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-L193dispose() { this._renderTargetX.dispose(); this._renderTargetY.dispose(); this.materialCombine.dispose(); this.materialConvolution.dispose(); this._fsQuad.dispose(); }选型建议BloomPass vs UnrealBloomPass文档开宗明义UnrealBloomPass 能产生更进阶的泛光但代价更高。结合源码可以给出一个清晰的对比维度BloomPassUnrealBloomPass模糊策略两次 1D 高斯卷积固定 25 点核mip map 链 多级不同半径模糊加权合成亮度阈值无全像素参与叠加支持threshold只有亮部产生辉光参数strength/kernelSize/sigmaresolution/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 资源。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门