
three.js 后期处理中的 SMAA 抗锯齿SMAAPass 完整使用与源码解析【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsSMAAPass 是 three.js 后期处理post-processing体系中的**亚像素形态学抗锯齿Subpixel Morphological AntialiasingSMAA**通道基于 WebGL 移植的 SMAA v2.81x Medium 预设、颜色边缘检测实现。本文将围绕官方文档 SMAAPass 参考页讲解其在 EffectComposer 中的接入方式、色彩空间约束必须在 OutputPass 之前执行、构造函数与三个核心方法并结合 SMAAPass.js、SMAAShader.js 与 webgl_postprocessing_smaa 示例 源码深入剖析其内部三阶段渲染管线。读完本文你将能够正确地把 SMAA 集成进自己的 three.js 渲染链并理解它与 FXAA 的取舍、为何要保持线性色彩空间以及三个 GPU 中间缓冲的作用。SMAAPass 与 FXAAPass在后期处理链中的定位差异SMAASubpixel Morphological Antialiasing是一种基于图像形态学分析的边缘抗锯齿算法它不依赖额外的几何信息而是通过检测图像中的边缘图案pattern计算混合权重再对边缘像素做加权混合。相比常见的快速近似抗锯齿 FXAASMAA 在视觉上通常能保留更多细节、锯齿消除更干净代价是更高的计算开销。官方文档给出了一个关键区别与 FXAAPass 不同SMAAPass在linear-srgb线性色彩空间中工作因此必须被放置在 OutputPass 之前执行。要理解这条约束需要先看 OutputPass.js 的类注释OutputPass 负责在渲染链末尾加入色调映射tone mapping与色彩空间转换sRGB transfer它会从渲染器中提取renderer.toneMapping、renderer.toneMappingExposure与renderer.outputColorSpace见 render 方法。因此链中靠前阶段都工作在线性 HDR 数据上一旦经过 OutputPass数据被转换到 sRGB 显示空间SMAAPass 的混合操作假定输入为线性数据所以它必须加在 OutputPass之前而像 FXAA 这类以 sRGB 输入为前提的通道官方注释则说明它需要加在 OutputPass之后参见 OutputPass.js 中 If a pass requires sRGB input (e.g. like FXAA)。典型且推荐的渲染链顺序如下const composer new EffectComposer( renderer ); composer.addPass( new RenderPass( scene, camera ) ); composer.addPass( new SMAAPass() ); // SMAA 在 OutputPass 之前、工作在线性色彩空间 composer.addPass( new OutputPass() ); // 色调映射 sRGB 转换作为链的最后一环最小接入示例与 Addon 导入方式SMAAPass 属于 examples/jsm 下的addon附加模块不会打包进核心 three.js 模块必须显式导入参见 官方安装文档中关于 Addons 的说明。在构建工具或 importmap 场景下按官方命名空间three/addons/导入import { EffectComposer } from three/addons/postprocessing/EffectComposer.js; import { RenderPass } from three/addons/postprocessing/RenderPass.js; import { SMAAPass } from three/addons/postprocessing/SMAAPass.js; import { OutputPass } from three/addons/postprocessing/OutputPass.js;导入后在 EffectComposer 中追加通道即可完成最基本接入const smaaPass new SMAAPass(); composer.addPass( smaaPass );更完整的真实接入可以参考仓库内示例 webgl_postprocessing_smaa.html它先添加RenderPass渲染线框立方体与砖墙纹理立方体组成的场景再依次添加smaaPass与outputPass并在动画循环中通过smaaPass.enabled开关实时对比 SMAA 开/关效果见 第 141 行窗口尺寸变化时则同步调用composer.setSize( width, height )通知各通道重设内部缓冲见 第 111-122 行。构造函数new SMAAPass()构造签名与继承关系new SMAAPass()构造函数不接收任何参数创建一个新的 SMAA 后期处理通道。它的继承链为Pass抽象基类 → SMAAPassPass.js 定义了所有后期处理通道共有的属性与方法骨架enabled是否被 composer 处理默认true、needsSwap渲染后是否交换读写缓冲默认true、clear渲染前是否清空缓冲默认false、renderToScreen结果是否直接输出到屏幕默认false通道链的最后一环会自动上屏、抽象方法setSize、render与dispose。SMAAPass 覆写了其中三个render、setSize与dispose。构造时分配的 GPU 资源虽然构造函数参数为空但内部一次性构建了大量资源理解这些有助于掌握内存占用与dispose()的必要性。从 SMAAPass.js 构造函数源码 可以看到两个中间渲染目标_edgesRT与_weightsRT均为HalfFloatType半精度浮点、不附带深度缓冲depthBuffer: false初始尺寸 1×1之后由setSize调整。它们分别保存边缘检测图与混合权重图对应纹理命名为SMAAPass.edges与SMAAPass.weights见 第 36-46 行。两张查找纹理_areaTexture面积纹理160×560 量级与_searchTexture搜索纹理66×33 量级它们的图像数据以 base64 PNG 形式内嵌在代码中由_getAreaTexture()与_getSearchTexture()两个内部方法返回 data URL 并通过Image异步解码见 第 51-82 行。_areaTexture使用LinearFilter双线性过滤、flipY: false且不生成 mipmap_searchTexture则强制NearestFilter点采样以精确读取搜索步长。三份 ShaderMaterial 与着色器分别对应 SMAA 的三个子阶段——_materialEdges、_materialWeights、_materialBlend见 第 84-119 行着色器源码来自 SMAAShader.js 中导出的SMAAEdgesShader、SMAAWeightsShader、SMAABlendShader。一个复用的全屏四边形_fsQuad new FullScreenQuad( null )因为三个子阶段渲染目标一致全屏四边形只需渲染时切换 material 即可复用一个四边形实例见 第 121 行。FullScreenQuad本身定义在 Pass.js 中内部使用OrthographicCamera与一个覆盖视口的三角形网格执行一次无顶点缓冲的全屏绘制。此外构造时还会把_weightsRT.texture绑定到tDiffuse、_areaTexture绑定到tArea、_searchTexture绑定到tSearch见 第 99-101 行。对 WebGL2 或高 DPI 场景而言这两张半浮点渲染目标会带来约2 × 宽 × 高 × 2 字节的额外显存占用需要接入时有所预期。三阶段渲染管线render() 方法内部原理方法签名.render( renderer : WebGLRenderer, writeBuffer : WebGLRenderTarget, readBuffer : WebGLRenderTarget, deltaTime : number, maskActive : boolean )参数语义继承自 Pass.render 的抽象定义由 EffectComposer 自动调用参数类型说明rendererWebGLRenderer渲染器实例writeBufferWebGLRenderTarget写缓冲本通道的渲染目标输出位置readBufferWebGLRenderTarget读缓冲本通道从此处读取上一个通道的结果deltaTimenumber时间增量秒。注意当前实现并未使用它见下文源码注释maskActiveboolean通道掩码是否激活。当前实现同样未使用内部执行流程从 render 源码 看SMAAPass在渲染链中实际只占一个Pass槽位但其内部会连续执行三次全屏渲染三阶段 SMAA第 1 阶段——边缘检测Edges把readBuffer.texture绑到边缘着色器的tDiffuse用_materialEdges渲染到内部_edgesRT。对应 GLSL 函数为SMAAColorEdgeDetectionPS它逐像素比较当前点与其左/上/右/下及次近邻的颜色差RGB 分量逐通道取最大超过阈值SMAA_THRESHOLD默认0.1定义于 SMAAShader.js则判定为边缘还包含局部对比度自适应逻辑见 SMAAShader.js 边缘着色器。第 2 阶段——权重计算Weights把第 1 阶段产生的_edgesRT.texture作为输入连同两张查找纹理一起用_materialWeights渲染到内部_weightsRT。对应SMAABlendingWeightCalculationPS沿检测到的边缘方向做最多SMAA_MAX_SEARCH_STEPS默认8步见 第 139-144 行 defines的搜索配合SMAAArea面积查表与距离开方补偿输出每个像素的混合权重见 SMAAShader.js 权重着色器。第 3 阶段——邻域混合Blend把原始场景图readBuffer.texture绑定到tColor权重图作为tDiffuse用_materialBlend输出。对应SMAANeighborhoodBlendingPS若当前像素权重和趋近于 0dot 1e-5则原样返回颜色否则沿最大权重方向取反色像素并插值混合见 SMAAShader.js 混合着色器。第 3 阶段是决定输出目标的一步见 第 158-173 行如果renderToScreen true直接把结果渲染到屏幕renderer.setRenderTarget( null )否则渲染进writeBuffer交给链条中下一个通道同时若this.clear为真会先清空目标。值得注意的两点实现细节前两个中间阶段各自判断this.clear用于清理内部_edgesRT/_weightsRT中上一帧残留内容。该 pass 的 render 方法把参数注释为render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ )——即deltaTime与maskActive目前在实现中并未参与运算这与其文档签名保留参数一致见 第 136 行。SMAA 与色彩空间的历史细节在 three.js 的 WebGL 移植中混合阶段特意加入了一次Gamma 校正混合前把颜色提升到 2.2 次幂、混合后再还原代码注释为 WebGL port note: Added gamma correction见 SMAAShader.js 第 471-475 行。这也是文档强调本 pass 在 linear-srgb 中运算、须置于 OutputPass 之前的实践原因——若传入已经过 sRGB 转换的数据双重的 gamma 处理与后续颜色管理会让结果失真。setSize()分辨率自适应方法签名.setSize( width : number, height : number )当渲染器或画布尺寸变化含windowresize、composer.setSize时EffectComposer 会调用各通道的setSize。SMAAPass 的实现做了三件事见 setSize 源码把内部两个中间渲染目标缩放到目标尺寸_edgesRT.setSize( width, height )、_weightsRT.setSize( width, height )——确保中间图与原图分辨率一致这是边缘精确对齐的前提为三个材质统一更新resolution均匀量new Vector2( 1 / width, 1 / height )。SMAA 各着色器全部用该像素倒数分辨率把采样坐标换算成 UV 偏移例如边缘着色器顶点阶段根据resolution计算 ±1、±2 像素的邻域采样点见 SMAAShader.js 第 39-60 行因此分辨率错误会直接导致锯齿误检或混合错位resolution的默认值在着色器中为( 1 / 1024, 1 / 512 )见 第 35 行 等仅作为构造初值实际总会在运行时被setSize覆盖为真实画布尺寸的倒数。dispose()释放 GPU 资源方法签名.dispose()当 SMAA 通道在应用中不再被使用时应主动调用dispose()以释放其占用的 GPU 资源。从 dispose 源码 可以看出它依次释放了两个渲染目标_edgesRT、_weightsRT含其纹理两张查找纹理_areaTexture、_searchTexture三个 ShaderMaterial_materialEdges、_materialWeights、_materialBlend全屏四边形_fsQuad。该方法覆写自 Pass#dispose基类中为空实现见 Pass.js。典型场景是动态切换抗锯齿方案如从 SMAA 切回 FXAA/关闭 AA、卸载场景或销毁整个 composer 时调用随后把通道从 composer 中移除避免泄漏。切勿与三个中间阶段共用的全屏四边形重复释放发生冲突——_fsQuad是 SMAAPass 自身创建的实例其dispose()内部会释放共享三角形几何见 Pass.js。总结接入清单与注意事项在 WebGL 渲染管线中启用 SMAA你需要记住以下要点导入路径使用three/addons/postprocessing/SMAAPass.js显式导入 addon。链内位置SMAA 工作在线性 sRGB 数据上务必放在OutputPass之前只有 FXAA 这类需要 sRGB 输入的通道才应放在OutputPass之后。推荐顺序RenderPass → SMAAPass → OutputPass完整代码可对照 webgl_postprocessing_smaa 示例。三阶段代价每次render会内部执行边缘检测、权重计算、邻域混合共三次全屏 pass并额外占用两张半浮点中间渲染目标与两张内嵌查找纹理相对 FXAA 更费但换来了更精细的边缘处理。可调参数阈值SMAA_THRESHOLD、搜索步数SMAA_MAX_SEARCH_STEPS等 defines 定义在 SMAAShader.jsedges与 第 139-144 行weights预设为 SMAA 1x Medium可通过修改对应 ShaderMaterial 的 defines 调整但会改变画质与性能权衡。尺寸同步窗口变化时应调用composer.setSize( width, height )否则内部中间缓冲分辨率失配会造成错误结果。资源回收通道不再使用时调用smaaPass.dispose()释放渲染目标、查找纹理、材质与全屏四边形。如果需要深入阅读推荐直接查看本文引用的三份关键源码SMAAPass.js通道编排、SMAAShader.jsSMAA 算法本体、OutputPass.js色彩空间约束的源头以及完整的官方文档页 SMAAPass。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考