ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

three.js 后期处理 SSAARenderPass 全解析:基于相机抖动与多重累加的超级采样抗锯齿实现

three.js 后期处理 SSAARenderPass 全解析:基于相机抖动与多重累加的超级采样抗锯齿实现 three.js 后期处理 SSAARenderPass 全解析基于相机抖动与多重累加的超级采样抗锯齿实现【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsSSAARenderPass 是 three.js examples/jsm 后期处理体系中的一款手动超级采样抗锯齿Supersample Anti-AliasingPass它不依赖渲染高分辨率中间缓冲而是对同一场景以相机亚像素抖动camera jitter逐采样重新渲染并累加从而获得平滑抗锯齿结果。本文以 SSAARenderPass 源码 及其 API 文档为主线结合 EffectComposer、Pass 基类 与官方演示 webgl_postprocessing_ssaa.html完整讲解它的构造参数、六大公开属性、核心渲染流程、去偏unbiased原理与资源释放方式帮助你把它正确接入自己的后处理管线并理解其精度与性能取舍。一、SSAARenderPass 是什么在传统 WebGL 管线中抗锯齿通常由 GPU 层面的 MSAA 或更高分辨率内部缓冲完成。SSAARenderPass 走的是一条软路线每一帧不再渲染一次而是按采样数量把场景渲染 N 次每次渲染前对相机视口做一次微小的亚像素偏移抖动再把 N 次渲染结果按权重累加最终得到一张等效于多倍分辨率采样的平滑图像。对应源码注释examples/jsm/postprocessing/SSAARenderPass.js与 API 文档均如此定义This manual approach to SSAA re-renders the scene ones for each sample with camera jitter and accumulates the results.从类继承关系看SSAARenderPass继承自Pass基类实现在 Pass.js定义了enabled、needsSwap、renderToScreen、clear等通用行为并覆写了render()、setSize()、dispose()三个方法。SSAARenderPass属于插件式addon模块并非 three.js 核心构建的一部分使用时必须显式导入。它同样被注册在 Addons.js 的统一导出清单中。二、最小接入示例配合 EffectComposer 使用API 文档给出的最小用法非常简洁SSAARenderPass 源码头部示例import { EffectComposer } from three/addons/postprocessing/EffectComposer.js; import { SSAARenderPass } from three/addons/postprocessing/SSAARenderPass.js; const composer new EffectComposer( renderer ); const ssaaRenderPass new SSAARenderPass( scene, camera ); ssaaRenderPass.sampleLevel 3; // 2^3 8 个采样 composer.addPass( ssaaRenderPass ); function animate() { composer.render(); // 替代 renderer.render() }几个关键注意点导入路径使用映射别名three/addons/postprocessing/SSAARenderPass.js对应仓库中的真实路径 examples/jsm/postprocessing/SSAARenderPass.js动画循环中应调用composer.render()而非renderer.render()。由 EffectComposer.render 的源码可见它会按passes数组顺序逐一执行每个enabled的 pass并在需要时交换内部 read/write 缓冲最后一个启用的 pass 会被自动设置为输出到屏幕若管线中还有色彩空间 / 色调映射需求通常在 SSAA pass 之后追加一个OutputPass官方示例即如此。三、构造函数与参数签名来自 API 文档 与 构造器源码new SSAARenderPass( scene, camera, clearColor, clearAlpha )参数类型默认值含义sceneScene—要被反复重绘的场景cameraCamera—用于渲染的相机透视 / 正交均可clearColornumber \| Color \| string0x000000每采样渲染前清屏用的清除色即背景色clearAlphanumber0清除的 Alpha 值const pass new SSAARenderPass( scene, camera ); const pass2 new SSAARenderPass( scene, camera, 0xffffff, 1.0 ); // 白色不透明背景在 构造器实现 中构造时除了登记公开属性外还会预建三样内部资源克隆CopyShader的 uniforms 与着色器构造一个ShaderMaterial_copyMaterial注意其关键配置transparent: true、blending: AdditiveBlending、premultipliedAlpha: true、关闭深度读写depthTest/depthWrite: false用该材质实例化一个全屏四边形_fsQuadFullScreenQuad也定义在 Pass.js延迟初始化_sampleRenderTarget首帧渲染时才创建。四、属性详解6 个公开属性.scene : Scene要渲染的场景对应构造参数scene见 源码 L45-L51。.camera : Camera参与渲染的相机对应构造参数camera。pass 会通过camera.setViewOffset(...)对它实施亚像素抖动见 源码 L52-L59。.clearColor : number | Color | string每次采样渲染场景前设置的清除色。源码中表现为renderer.setClearColor( this.clearColor, this.clearAlpha )L232。由于它是逐采样写入颜色缓冲的背景色会影响最终边缘像素的混合结果。默认0x000000。.clearAlpha : number与clearColor配套的清除 Alpha默认0。官方演示 GUI 提供 0~1 实时调节。.sampleLevel : number采样档位。语义是采样数 2^n即sampleLevel 4时是2^4 16个采样。默认4。注意它与直接填写采样数不同这是一个对数刻度。从 render 中的选择逻辑 可以看到它会被钳制在[0, 5]const jitterOffsets _JitterVectors[ Math.max( 0, Math.min( this.sampleLevel, 5 ) ) ];即有效档位与采样数对应关系为sampleLevel采样数说明01无抗锯齿仅清屏 单次渲染12轻微抗锯齿24入门级38常用均衡档416默认档效果较好532最高档性能开销大.stencilBuffer : boolean是否启用模板缓冲。默认false。该属性用于创建内部采样渲染目标_sampleRenderTarget见 L167且一旦完成首次渲染后就不能再修改——因为渲染目标创建后不会再被重建。如果你的场景需要与模板测试 /MaskPass配合请在渲染前把此项设为true。.unbiased : boolean是否启用去偏去舍入误差。默认true。源码中的注释说明了动机L220-L229如果每个采样都使用完全相等的权重多次累加会把定点/低精度舍入误差反复叠加在 8-bit 每通道的 RGBA8 缓冲上表现最明显会形成带状条纹 banding。unbiased开启后每个采样的权重会在基础权重附近做均匀居中分布的微小扰动使各次舍入误差相互抵消。官方演示页的说明文字直接指出关闭 unbiased就能看到由舍入误差累积导致的色带见 webgl_postprocessing_ssaa.html 页面注释。五、核心算法render() 逐行拆解render()是 SSAARenderPass 的灵魂签名与 Pass 基类的抽象声明 保持一致render( renderer, writeBuffer, readBuffer, deltaTime, maskActive )其中writeBuffer是本 pass 的输出目标readBuffer是上一 pass 的结果SSAA 累加模式下会被用作尺寸 / 相机 view 的参考。主流程位于 源码 L163-L271可按如下阶段理解。阶段 1延迟创建采样渲染目标if ( ! this._sampleRenderTarget ) { this._sampleRenderTarget new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType, stencilBuffer: this.stencilBuffer } ); this._sampleRenderTarget.texture.name SSAARenderPass.sample; }关键点采样目标使用HalfFloatTypeRGBA16F 半浮点。文档对此的表述是By default RGBA16F is used这是精度设计的基础——半浮点缓冲能承载多采样累加过程中的中间值避免过早截断。模板缓冲开关则取this.stencilBuffer。阶段 2准备工作与相机 view 快照renderer.autoClear false; // 全程手动控制清除 renderer.getClearColor( this._oldClearColor ); // 暂存原清除色以便还原 const oldClearAlpha renderer.getClearAlpha(); const baseSampleWeight 1.0 / jitterOffsets.length; // 每个采样的基础权重 1/N const roundingRange 1 / 32; // unbiased 的扰动幅度随后基于readBuffer构造viewOffset描述对象并把相机原有的camera.view如果启用了 view offset合并进去L184-L197。这保证了用户自定义的 view offset 抖动与 SSAA 自身的 jitter 叠加是安全的。阶段 3逐采样渲染 抖动 权重累加核心循环核心循环位于 L199-L248for ( let i 0; i jitterOffsets.length; i ) { const jitterOffset jitterOffsets[ i ]; // 3.1 相机亚像素抖动把整数网格 [-8,8) 按 1/16 映射到 [-0.5,0.5) 像素 this.camera.setViewOffset( viewOffset.fullWidth, viewOffset.fullHeight, viewOffset.offsetX jitterOffset[ 0 ] * 0.0625, viewOffset.offsetY jitterOffset[ 1 ] * 0.0625, // 0.0625 1/16 viewOffset.width, viewOffset.height ); // 3.2 计算本采样权重 let sampleWeight baseSampleWeight; if ( this.unbiased ) { const uniformCenteredDistribution ( - 0.5 ( i 0.5 ) / jitterOffsets.length ); sampleWeight roundingRange * uniformCenteredDistribution; } // 3.3 用 clearColor 清空采样目标渲染本采样画面 this._copyUniforms[ opacity ].value sampleWeight; renderer.setClearColor( this.clearColor, this.clearAlpha ); renderer.setRenderTarget( this._sampleRenderTarget ); renderer.clear(); renderer.render( this.scene, this.camera ); // 3.4 把本采样结果按权重加法混合进 writeBuffer或屏幕 renderer.setRenderTarget( this.renderToScreen ? null : writeBuffer ); if ( i 0 ) { renderer.setClearColor( 0x000000, 0.0 ); // 首个采样前把累加缓冲清零 renderer.clear(); } this._fsQuad.render( renderer ); // 全屏四边形加法混合dst sampleWeight * texel }理解这段代码的三个要点抖动步长_JitterVectors中存的是整数坐标注释L276-L280说明它假设一个[-8, 8)的整数网格使用前必须乘以1/16映射到[-0.5, 0.5)像素范围——这正是代码中0.0625 1/16的由来权重取值不开unbiased时每个采样严格按1/N平均加权总权重和为 1等价于多帧平均混合方式_copyMaterial使用了AdditiveBlending全屏四边形把opacity × 采样纹理像素加到目标缓冲上。因为 copy 材质是premultipliedAlpha且输出的是带透明通道的累加结果色带问题才会在 8-bit 目标上显现——这正是unbiased选项存在的原因。阶段 4还原相机与渲染器状态循环结束后必须物归原主L250-L269若相机原本启用了 view offset则恢复为原始 offset否则调用camera.clearViewOffset()清除抖动恢复renderer.autoClear、renderer的原清除色与清除 Alpha。采样抖动表_JitterVectors抖动表定义在文件底部L281-L311共 6 组、对应sampleLevel0~5级别 0[0,0]1 个偏移级别 1对角线两点级别 24 点旋转对称级别 38 点级别 416 点级别 532 点覆盖最均匀。这些点采用旋转对称 / 网格错位布局源码注释引用的原采样模式可追溯至微软 DirectX 相关文档目的是让各采样的子像素覆盖尽量均匀避免同一方向的系统性偏差。六、官方演示webgl_postprocessing_ssaa.html 实战分析官方演示 examples/webgl_postprocessing_ssaa.html 是理解本 pass 的最佳活教材值得关注以下设计1. 管线结构L176-L183场景是 120 个随机散布的InstancedMesh彩色球体光照为 4 盏点光 / 环境光组合。后处理管线为composer new EffectComposer( renderer ); composer.setPixelRatio( 1 ); // 固定为 1性能考虑 ssaaRenderPassP new SSAARenderPass( scene, cameraP ); // 透视相机 pass ssaaRenderPassO new SSAARenderPass( scene, cameraO ); // 正交相机 pass composer.addPass( ... ); composer.addPass( new OutputPass() );2. 同一场景挂两个 SSAA pass透视 正交通过ssaaRenderPassP.enabled/ssaaRenderPassO.enabled在两者之间切换用来验证 SSAARenderPass 对PerspectiveCamera与OrthographicCamera都适用。3. 实时调参覆盖全部核心属性GUI 提供了unbiased开关、sampleLevelLevel 0~5即 1~32 采样、clearColor黑/白/蓝/绿/红、clearAlpha0~1、viewOffsetX相机 view offset 偏移等控件L47-L56并在动画循环中把参数同步到两个 passL239-L246。读者可据此直观观察提高 sampleLevel 时边缘更平滑、性能下降关闭 unbiased 时出现色带。4. 窗口缩放onWindowResize中同时调用renderer.setSize()与composer.setSize()由 EffectComposer 统一把尺寸同步给所有 pass。若本地已 clone 本仓库可通过仓库根目录的静态服务如utils/server.js直接打开该页面查看实际效果。七、性能与适用场景基于源码的推断结合上述逐采样渲染逻辑可以给出以下有源码依据的取舍说明渲染成本是逐采样的每帧场景渲染次数 2^sampleLevel。默认 sampleLevel4 意味着每帧渲染 16 遍场景 16 次全屏混合对复杂场景代价较高。官方示例注释建议把 composer 的 pixelRatio 固定为 1webgl_postprocessing_ssaa.html L177即提示该算法开销大、应避免再叠加 DPR 放大画面运动时的噪声特性由于每帧的抖动模式一致静态画面获得稳定平滑结果若要进一步利用时域信息可关注仓库中同样继承Pass的 TAARenderPass.js它扩展了时间性抗锯齿思路与本 pass 是姊妹实现精度敏感场景优先保持 unbiasedtrue当最终输出是 RGBA88-bit/通道缓冲时关闭 unbiased 会出现明显的带状条纹官方示例即以此演示该特性只有当渲染目标是 RGBA16F 等更高精度且你确需严格控制权重总和时才应评估是否关闭半浮点内部目标pass 内部采样目标固定使用HalfFloatTypeL167因此无需外部配置即可在累加过程中保持足够中间精度。八、setSize 与 dispose尺寸同步与资源释放.setSize( width, height )对应 Pass 基类的 setSize。实现见 源码 L146-L150仅当内部采样目标已创建时才把_sampleRenderTarget调整为新的宽高WebGLRenderTarget.setSize会重建后备缓冲。该目标未创建时无需调整因为首次渲染时会以当时 readBuffer 的尺寸为准创建。EffectComposer 在addPass/insertPass/setSize时都会以物理像素 逻辑尺寸 × pixelRatio调用每个 pass 的setSize见 EffectComposer 实现因此窗口缩放场景中无需手动调用。.dispose()释放 GPU 相关资源见 源码 L125-L138按顺序执行三件事若采样目标已创建则_sampleRenderTarget.dispose()并置空引用_copyMaterial.dispose()释放拷贝混合材质_fsQuad.dispose()释放全屏四边形几何体。它覆写了 Pass 基类的 dispose()。官方文档建议当 pass 不再被使用时务必调用。注意它只负责自身资源不包含 scene/camera 的管理若后续还要重建同名 pass由于_sampleRenderTarget引用已被置空首次渲染会自动按新尺寸重新创建不存在复用旧目标的残留问题。九、总结SSAARenderPass 用多重渲染 相机亚像素抖动 加权加法累加这一朴素而可靠的软件方案为 three.js 后期处理管线提供了不依赖高分辨率内部缓冲的超采样抗锯齿能力。用好它只需抓住四件事通过EffectComposer.addPass()接入必要时追加OutputPass用sampleLevel以 2 的幂次控制采样数0~5 档、默认 4 即 16 采样在 RGBA8 输出下务必保持unbiased true以避免舍入误差色带不再使用时调用dispose()释放采样目标与全屏材质。更深层的调用链、抖动表与权重去偏公式均可在仓库源码 examples/jsm/postprocessing/SSAARenderPass.js、基类 Pass.js、管线 EffectComposer.js 与完整演示 webgl_postprocessing_ssaa.html 中逐行核对本文全部实现细节均可追溯至上述文件。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表