three.js 后处理全屏抗锯齿 FXAAPass 使用指南:继承体系、setSize 与 FXAA 着色器原理
FXAAPass 是 three.js 官方提供的一种快速近似抗锯齿(Fast Approximate Anti-Aliasing,FXAA)后处理通道,用于消除画面边缘的锯齿伪影。它适合作为 EffectComposer 后期处理链的最后一环,以极低的性能开销平滑 3D 渲染输出的几何边缘。读完本文,你将掌握 FXAAPass 的导入方式、与 EffectComposer 的组合用法、其 setSize 覆写行为,以及其底层 FXAAShader 的亮度取样、对比度判定与边缘混合等核心实现原理。
一、FXAAPass 在通道继承体系中的定位
FXAAPass 是一个无需任何构造参数的成品后处理通道,其完整的继承链为:
Pass → ShaderPass → FXAAPass
- 基类 Pass(
examples/jsm/postprocessing/Pass.js)定义了所有后处理通道共有的接口,如enabled、needsSwap、clear、renderToScreen标志位,以及抽象的setSize、render、dispose方法。 - 中间层 ShaderPass 负责把「着色器对象」包装成一个后处理通道:它会克隆 shader 的 uniforms、构建
ShaderMaterial、并通过FullScreenQuad渲染全屏四边形。因此任何以 GLSL 着色器定义的效果(如 FXAA)都可以复用这一套管线。 - FXAAPass 则是在
ShaderPass之上,直接使用内置的 FXAA 着色器实例化出一个开箱即用的抗锯齿通道。
从 FXAAPass 源码 可以看到其实现非常精简——构造函数只做了一件事:把 FXAAShader 交给父类 ShaderPass,其余渲染逻辑全部由父类承担:
class FXAAPass extends ShaderPass {
constructor() {
super( FXAAShader );
}
// 仅覆写了 setSize,用于同步着色器中的分辨率 uniform
setSize( width, height ) {
this.material.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height );
}
}
二、快速上手:导入并加入后期处理链
与 three.js 核心 API 不同,FXAAPass 属于 addon(附加模块),必须显式导入。官方文档给出的模块路径为 three/addons/postprocessing/FXAAPass.js:
import { FXAAPass } from 'three/addons/postprocessing/FXAAPass.js';
导入后只需两行即可接入渲染流程:
const fxaaPass = new FXAAPass();
composer.addPass( fxaaPass );
其中 composer 是一个 EffectComposer 实例。一个完整、可运行的最小链路通常包含 RenderPass(把场景渲染进离屏缓冲)、OutputPass(色调映射与色彩空间输出)以及位于末端的 FXAAPass:
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import { FXAAPass } from 'three/addons/postprocessing/FXAAPass.js';
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) );
composer.addPass( new OutputPass() );
const fxaaPass = new FXAAPass();
composer.addPass( fxaaPass );
// 渲染循环内
composer.render();
在经由 importmap 配置的裸模块环境中,示例 HTML 通常写成:
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import { FXAAPass } from 'three/addons/postprocessing/FXAAPass.js';
// ...
</script>
也可以从 three/addons/ 根入口 examples/jsm/Addons.js 统一引入(该文件已对 FXAAPass 做了再导出)。在支持 ES Module 的构建工具环境中,仓库本身还提供了 src/Three.js 等核心入口,但后处理 addon 始终以显式模块路径单独引用。
官方对比示例参考
仓库中的示例页 examples/webgl_postprocessing_fxaa.html 展示了 FXAA 的官方用法与效果验证方式:它在同一画布内通过 scissor 测试分屏渲染了两个 EffectComposer——左侧 composer1 仅含 RenderPass + OutputPass(无抗锯齿),右侧 composer2 在此基础上追加 fxaaPass,方便直接肉眼对比边缘差异。其中关键代码片段为:
const renderPass = new RenderPass( scene, camera );
renderPass.clearAlpha = 0;
fxaaPass = new FXAAPass();
const outputPass = new OutputPass();
composer1 = new EffectComposer( renderer );
composer1.addPass( renderPass );
composer1.addPass( outputPass );
composer2 = new EffectComposer( renderer );
composer2.addPass( renderPass );
composer2.addPass( outputPass );
// FXAA 应在色调映射并转换到 sRGB 显示色彩空间之后、接近链路末端时应用
composer2.addPass( fxaaPass );
该示例在动画循环内通过 renderer.setScissorTest( true ) 配合 renderer.setScissor( ... ) 让两个 composer 分别渲染到画布的左右两半(见 examples/webgl_postprocessing_fxaa.html 中 animate() 的实现)。场景本身由 100 个带平面着色的红色四面体 InstancedMesh 组成,红色几何体在白色背景上的清晰轮廓非常适合暴露边缘锯齿,便于检验 FXAA 效果。若想本地查看,可在仓库根目录使用 python -m http.server 之类静态服务器托管后打开该 HTML(需 GPU 与 WebGL 支持)。
三、构造函数
new FXAAPass()
创建一个新的 FXAA 后处理通道。该构造函数不接受任何参数,也无配置项;它只是把内置着色器对象 FXAAShader 传入父类 ShaderPass 完成初始化。因此实例创建后即可直接交给 EffectComposer:
const fxaaPass = new FXAAPass();
ShaderPass 构造函数在执行 super( FXAAShader ) 时会发生以下动作(见 ShaderPass.js):
- 将
shader视为普通着色器对象(而非已存在的ShaderMaterial),用UniformsUtils.clone( shader.uniforms )克隆其 uniforms; - 新建一个名为
FXAAShader(取自shader.name)的ShaderMaterial; - 记录默认采样纹理 uniform 名为
textureID = 'tDiffuse'; - 用该材质实例化一个内部
FullScreenQuad,作为全屏渲染的载体。
四、方法:setSize( width, height )
setSize( width : number, height : number ) : void
设置通道(pass)的尺寸,width 与 height 为要设置的宽高(像素值)。文档将其标注为对 ShaderPass#setSize 的覆写(ShaderPass 本身继承自 Pass,基类中该方法是空实现),而 FXAAPass 的重写逻辑只有一行:
this.material.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height );
其含义是:FXAA 算法需要在片元着色器中把「像素偏移量」换算成 UV 坐标增量,因此必须知道每个像素在整张纹理中占多大比例。resolution uniform 存的就是「单个像素的宽高(1/width, 1/height)」,着色器后续用它乘以采样偏移即可获得纹理坐标步长。
在实际调用中,该方法由 EffectComposer 自动驱动:当调用 composer.setSize( width, height ) 时,EffectComposer.js 会计算出考虑了像素比的真实缓冲尺寸 effectiveWidth = width * _pixelRatio,并依次调用链路上每个通道的 pass.setSize( effectiveWidth, effectiveHeight )。因此:
- 若使用了
renderer.setPixelRatio( dpr )与composer.setPixelRatio( dpr ),FXAAPass 得到的将是物理像素尺寸,resolution也会随之正确更新; - 窗口
resize后务必同步调用composer.setSize( container.offsetWidth, container.offsetHeight ),否则抗锯齿采样步长与画面尺寸失配,FXAA 会失效或产生模糊。
由于 FXAAPass 自身只维护一个 uniforms 引用,且每次渲染都会由 ShaderPass.render() 把 readBuffer 的纹理重新赋给 tDiffuse(见 ShaderPass.js),所以即使全屏四边形被多个通道复用,效果依旧正确。
五、底层支撑:FXAAShader 与关键 uniform
FXAAPass 的实现完全依托 examples/jsm/shaders/FXAAShader.js 中的着色器对象 FXAAShader。它继承了 NVIDIA FXAA 算法,代码注释中标注了参考来源为 NVIDIA 的 FXAA 白皮书与 Jasper Flick 的 C# 实现、Dave Hoskins 的 GLSL 移植版。其结构如下:
const FXAAShader = {
name: 'FXAAShader',
uniforms: {
'tDiffuse': { value: null },
'resolution': { value: new Vector2( 1 / 1024, 1 / 512 ) }
},
vertexShader: /* glsl */`...`,
fragmentShader: /* glsl */`...`
};
uniforms 一览:
| uniform | 类型 | 默认值 | 作用 |
|---|---|---|---|
tDiffuse |
sampler2D |
null |
输入纹理,即上一个后处理通道的 readBuffer.texture,由 ShaderPass.render() 每帧注入 |
resolution |
Vector2 |
( 1/1024, 1/512 ) |
单个像素在 UV 空间中的宽高(1/width, 1/height),由 FXAAPass.setSize() 维护 |
默认值 ( 1 / 1024, 1 / 512 ) 只是一个占位初值——只要走 EffectComposer 的正常流程,setSize 会立刻用真实尺寸覆盖它。因此不要试图在构造 FXAAPass 后手动修改 resolution,应通过 composer.setSize() 或窗口尺寸来驱动。
片元着色器内部的可调参数
FXAA 的核心逻辑全部位于 fragmentShader 中。该着色器在文件开头定义了两组算法常量:
#define EDGE_STEP_COUNT 6
#define EDGE_GUESS 8.0
#define EDGE_STEPS 1.0, 1.5, 2.0, 2.0, 2.0, 4.0
float _ContrastThreshold = 0.0312; // 像素对比度阈值(绝对值)
float _RelativeThreshold = 0.063; // 相对阈值(乘以上下文中最高亮度)
float _SubpixelBlending = 1.0; // 子像素混合强度
_ContrastThreshold与_RelativeThreshold共同决定哪些像素需要处理:只有当邻域亮度对比l.contrast同时超过「绝对阈值」和「相对阈值 × 邻域最高亮度」(见ShouldSkipPixel函数)时才认定该像素位于边缘,否则直接返回原色。阈值越大,抗锯齿作用越保守、画面越锐利;越小则边缘覆盖越广、但也可能让文本等细节变糊。_SubpixelBlending控制「子像素混合」的强度,用于对跨越多个像素的细线(如 45° 斜线)做补偿平滑,取值 1.0 表示完整启用。
这些变量虽然以字面量内嵌在 GLSL 中(FXAAPass 没有把它们暴露为可配置参数),但理解它们有助于你直接改写 FXAAShader 或调整画质/性能取向。
六、FXAA 算法在着色器中的执行流程
结合 FXAAShader.js 的片元着色器,可以梳理出 FXAA 对每个像素执行的完整流水线,这也是理解 FXAAPass「为什么能做到单次全屏采样即完成抗锯齿」的关键:
- 亮度取样(
SampleLuminanceNeighborhood):对当前像素m及其 8 邻域(上n、下s、左w、右e与 4 个对角)采样,并按dot( rgb, vec3( 0.3, 0.59, 0.11 ) )的权重计算亮度(该权重对应人眼对 R/G/B 通道的感知灵敏度)。随后求出邻域最高亮度、最低亮度与对比度。 - 跳过判定(
ShouldSkipPixel):若对比度不足(低于前文阈值公式),判定该像素不在边缘上,直接返回原始颜色——这正是 FXAA 比逐像素 MSAA 廉价得多的原因:绝大多数内部像素被提前跳过。 - 子像素混合因子(
DeterminePixelBlendFactor):基于 3×3 邻域亮度的均值偏离程度,估算该像素落在「次像素级细线」上的概率,得到pixelBlend。 - 边缘方向判定(
DetermineEdge):分别累加水平方向(n + s − 2m项)与垂直方向的二阶梯度能量,较大的方向被认定为边缘走向,并据此决定沿哪个轴向搜索、以哪个邻域为「对侧亮度」。 - 边缘端点到端点距离搜索(
DetermineEdgeBlendFactor):以半像素为起点,沿边缘走向两侧按EDGE_STEPS(1.0、1.5、2.0、2.0、2.0、4.0 倍步长)逐步推进,最多走EDGE_STEP_COUNT = 6步,直到找到亮度越过gradientThreshold的端点;若始终未越界则再外推EDGE_GUESS = 8.0步作为假设端点。随后用两端点距离推导出沿边缘的偏移比例edgeBlend。 - 混合输出(
ApplyFXAA):取max( pixelBlend, edgeBlend )作为最终混合强度,让采样中心沿着边缘垂直方向移动该比例对应的像素步长,再重新采样一次颜色作为输出。这样相当于把边缘两侧的颜色按几何比例做了平均,从而在轮廓上产生平滑过渡。
整个算法只依赖 tDiffuse 与 resolution 两个 uniform、对每个像素做有限次数的纹理采样,不生成或依赖几何体的多重采样信息,因此对任意渲染内容(含后期特效叠加后的画面)都能直接生效。
七、继承自 ShaderPass 的默认行为
因为 FXAAPass 没有重写父类的渲染逻辑,它在通道链中的表现由 ShaderPass 决定,使用时值得注意以下几点:
- 纹理采样契约:
ShaderPass默认把名为tDiffuse的 uniform 绑定到readBuffer.texture。FXAAShader 恰好遵循该约定,所以 FXAAPass 无需额外配置即可取得上一个通道的输出。 - 渲染到屏幕:
Pass.renderToScreen默认为false。EffectComposer会自动把链路上最后一个通道设为输出到屏幕(其内部机制见EffectComposer.render()对末位 pass 的renderToScreen处理)。若把 FXAAPass 放在链路中间(而非最后),它只会写入writeBuffer,供后续通道继续消费。 - 释放资源:继承自
ShaderPass.dispose()的方法会同时释放material与内部FullScreenQuad的 GPU 资源(ShaderPass.js)。当不再需要 FXAAPass 时应调用fxaaPass.dispose()。 - 全屏四边形:所有基于
ShaderPass的通道共享由 Pass.js 提供的单一三角形面片几何体(由FullscreenTriangleGeometry生成、随FullScreenQuad复用),内存开销极低。
八、FXAAPass 与着色器/TSL 生态中的其他 FXAA 实现
在 three.js 仓库中,FXAA 还存在多个层级的具体实现:
FXAAPass+FXAAShader(本文主角):面向传统 WebGL 渲染器与EffectComposer后处理链的 addon 实现,位于 examples/jsm/postprocessing/FXAAPass.js 与 examples/jsm/shaders/FXAAShader.js。SMAAPass:同一后处理目录下另提供基于 SMAA 算法的通道 examples/jsm/postprocessing/SMAAPass.js。相比单次全屏处理的 FXAA,SMAA 需要额外的边缘检测与混合纹理等步骤,链路与资源占用更复杂;FXAA 的优势在于实现与依赖最少、执行开销低。- TSL 节点实现:文档目录中还包含面向节点/TSL 风格的 FXAA 节点文档 docs/pages/FXAANode.html.md,服务于新版 WebGPU/TSL 渲染体系,与本文基于
EffectComposer的通道式用法属于不同管线,可按渲染后端选择。
选型时的实用参考:若目标是 WebGL + EffectComposer、希望在链路末尾对任意场景低成本去锯齿,FXAAPass 是最直接的答案;官方示例将它与 OutputPass 搭配并置于其后,正是为了在低动态范围与 sRGB 显示空间中对画面边缘做最终平滑(见 webgl_postprocessing_fxaa.html 中的注释说明)。
九、总结
FXAAPass 是一个「小而全」的官方后处理通道:对外只需 new FXAAPass() + composer.addPass() 两步即可启用;对内它通过覆写 setSize 维护着色器所需的像素分辨率、复用 ShaderPass 的材质与全屏四边形管线,并把 NVIDIA FXAA 的亮度对比度判定、子像素混合、边缘方向检测与端点搜索等算法完整封装在 FXAAShader 中。理解这套「通道对象 — 着色器对象 — uniform 契约」的分层关系,不仅能让你正确使用 FXAAPass,也为自定义后处理通道(继承 ShaderPass 并覆写 setSize 同步自身 uniform)提供了可直接照搬的模板。
相关资源索引
- 本文对应的 API 文档页:docs/pages/FXAAPass.html.md
- 通道实现源码:examples/jsm/postprocessing/FXAAPass.js
- 着色器源码(算法本体):examples/jsm/shaders/FXAAShader.js
- 基类与中间层:examples/jsm/postprocessing/Pass.js、examples/jsm/postprocessing/ShaderPass.js
- 组合器:examples/jsm/postprocessing/EffectComposer.js
- 官方对比示例:examples/webgl_postprocessing_fxaa.html
- 同目录可对比方案:examples/jsm/postprocessing/SMAAPass.js,以及 TSL 方向的 docs/pages/FXAANode.html.md
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00