three.js 彩色畸变节点 ChromaticAberrationNode 解析:用 TSL 实现镜头色散后处理
three.js 的 TSL(Three Shading Language)提供了一组显示类后处理节点,其中 ChromaticAberrationNode 用于模拟真实相机镜头产生的"色边"(chromatic aberration)效果:将场景渲染结果的红、绿、蓝三个通道按径向距离分别偏移采样,从而在画面边缘形成彩色的分离光晕。本文基于该节点的 API 文档与仓库源码,完整讲解它的继承体系、构造参数、着色器实现细节,并给出可直接运行的后处理接法。
1. 节点定位与继承关系
ChromaticAberrationNode 是 TSL 显示(display)模块下的一个 addon,继承链为:
EventDispatcher → Node → TempNode → ChromaticAberrationNode
其中 TempNode 是关键的一层:它是一个临时变量类型的节点基类。从源码可以看出,当节点在依赖图中被多处引用时,TempNode#build 会把表达式结果缓存到一个 nodeVar 中,避免重复计算(hasDependencies 通过 usageCount > 1 判断):
// src/nodes/core/TempNode.js
hasDependencies( builder ) {
return builder.getDataFromNode( this ).usageCount > 1;
}
build( builder, output ) {
const buildStage = builder.getBuildStage();
if ( buildStage === 'generate' ) {
// ... 若存在 propertyName 直接返回缓存变量;
// 否则若被多处依赖,则把 snippet 写入 lineFlowCode 并缓存
}
return super.build( builder, output );
}
对于彩色畸变节点这类内部要对同一张纹理做多次采样的实现,TempNode 的缓存机制保证了它被复用(例如同时接入多个输出分支)时不会重复生成着色器计算。节点构造时调用 super( 'vec4' ),声明其输出类型为 vec4,即标准的 RGBA 颜色。
2. 导入方式
该节点属于 addon,需要显式导入。TSL 函数形式的入口为 chromaticAberration:
import { chromaticAberration } from 'three/addons/tsl/display/ChromaticAberrationNode.js';
在 three npm 包中,该路径对应 examples/jsm/tsl/display/ChromaticAberrationNode.js(仓库内源码文件)。在本地开发时,示例 HTML 通过 importmap 将其映射到 ./jsm/ 目录,例如 examples/webgpu_postprocessing_ca.html:
<script type="importmap">
{
"imports": {
"three": "../build/three.webgpu.js",
"three/webgpu": "../build/three.webgpu.js",
"three/tsl": "../build/three.tsl.js",
"three/addons/": "./jsm/"
}
}
</script>
3. 构造函数与 TSL 函数签名
3.1 构造函数
new ChromaticAberrationNode( textureNode : TextureNode, strengthNode : Node, centerNode : Node, scaleNode : Node )
四个参数均为节点:
| 参数 | 类型 | 说明 |
|---|---|---|
| textureNode | TextureNode | 效果的输入,即要施加色散的纹理(通常是上一个后处理 Pass 的输出) |
| strengthNode | Node | 效果强度,可传任意标量节点,支持运行时动态修改 |
| centerNode | Node | 畸变中心点(vec2 UV 坐标),效果从该点向外呈径向发散 |
| scaleNode | Node | 以中心为基准的分级缩放因子 |
3.2 TSL 工厂函数
通常不直接 new,而是使用同文件导出的 TSL 函数。其签名与默认值为:
chromaticAberration( node, strength = 1.0, center = null, scale = 1.1 ) : ChromaticAberrationNode
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| node | Node<vec4> | 必填 | 效果输入节点;可以是 Pass 输出、纹理或其他 TSL 节点 |
| strength | Node | number | 1.0 |
强度;越大色彩分离越明显 |
| center | Node | Vector2 | null(屏幕中心 0.5, 0.5) |
畸变中心点 |
| scale | Node | number | 1.1 |
分级缩放因子 |
工厂函数内部会把普通值统一包装为节点对象,这也是 TSL 的典型模式——参数既可以写死数值,也可以换成 uniform 节点实现运行时可调:
// examples/jsm/tsl/display/ChromaticAberrationNode.js
export const chromaticAberration = ( node, strength = 1.0, center = null, scale = 1.1 ) => {
return nodeObject(
new ChromaticAberrationNode(
convertToTexture( node ),
nodeObject( strength ),
nodeObject( center ),
nodeObject( scale )
)
);
};
两个关键包装函数值得注意:
nodeObject:定义于 src/nodes/tsl/TSLCore.js,把任意值包装成ShaderNodeObject,使其具备节点的可链式运算能力;convertToTexture:定义于 src/nodes/utils/RTTNode.js,确保输入一定是可采样的纹理节点——若输入已是 SampleNode/TextureNode 则原样返回,若是 PassNode 则取其纹理,否则自动包一层 RTT(Render To Texture):
export const convertToTexture = ( node, ...params ) => {
if ( node.isSampleNode || node.isTextureNode ) return node;
if ( node.isPassNode ) return node.getTextureNode();
return rtt( node, ...params );
};
这意味着你可以把任意 TSL 节点(甚至整条渲染 Pass)直接喂给 chromaticAberration,采样所需的纹理化过程是自动完成的。
4. 实例属性
构造完成后,四个参数以节点属性形式保存在实例上,可在构建着色器前后读取或替换:
| 属性 | 类型 | 说明 |
|---|---|---|
| .textureNode | TextureNode | 效果输入纹理节点 |
| .strengthNode | Node | 强度节点 |
| .centerNode | Node | 中心点节点 |
| .scaleNode | Node | 分级缩放节点 |
由于它们都是节点引用,只要换成 uniform() 创建的 uniform 节点,就能在 CPU 侧随时修改值而无需重新编译管线(见第 6 节示例)。
5. setup 方法与着色器实现原理
5.1 setup 方法
.setup( builder : NodeBuilder ) : ShaderCallNodeInternal
该方法负责生成效果的 TSL 着色器代码,重写了 TempNode#setup(文档中写作 TempNode#setup)。其返回的节点成为 RenderPipeline 输出链中的一次"函数调用"。
5.2 内部算法:三步径向畸变 + 分通道采样
setup 内部定义了一个命名着色器函数 ApplyChromaticAberration,通过 setLayout 声明了类型签名(输入 uv: vec2, strength: float, center: vec2, scale: float,输出 vec4),核心逻辑可以归纳为三层:
// 第一步:计算像素相对中心的径向偏移
const offset = uv.sub( center );
const distance = offset.length();
// 第二步:通道级缩放 —— 红向外扩、绿不动、蓝向内缩
const redScale = float( 1.0 ).add( scale.mul( 0.02 ).mul( strength ) );
const greenScale = float( 1.0 );
const blueScale = float( 1.0 ).sub( scale.mul( 0.02 ).mul( strength ) );
// 第三步:附加的色散偏移(正比于 强度 × 距中心距离)
const aberrationStrength = strength.mul( distance );
const rOffset = offset.mul( aberrationStrength ).mul( float( 0.01 ) );
const gOffset = offset.mul( aberrationStrength ).mul( float( 0.0 ) );
const bOffset = offset.mul( aberrationStrength ).mul( float( - 0.01 ) );
随后按三个通道各自的最终 UV 分别采样输入纹理,并把 alpha 通道从原始 UV 处取回:
const finalRedUV = redUV.add( rOffset );
const finalGreenUV = greenUV.add( gOffset );
const finalBlueUV = blueUV.add( bOffset );
const r = textureNode.sample( finalRedUV ).r;
const g = textureNode.sample( finalGreenUV ).g;
const b = textureNode.sample( finalBlueUV ).b;
const a = textureNode.sample( uv ).a;
return vec4( r, g, b, a );
从源码结构看,该算法有三个值得注意的设计点:
- 色散量随半径增长:
aberrationStrength = strength * distance意味着越靠近画面四角,RGB 分离越大,符合真实镜头"边缘色散更明显"的光学特征,中心区域几乎不受影响; - 红蓝对称、绿色为基准:红色通道按
1 + 0.02 * scale * strength向外放大、蓝色按相反方向缩小,绿色保持原 UV,配合 ±0.01 的径向偏移,形成以绿为基准、红蓝向两侧分流的经典色边; - alpha 保持原始采样:透明度不被畸变,避免边缘出现半透明光晕污染,保证与后续合成 Pass 的兼容性。
uv 的来源也做了兜底处理:优先取 textureNode.uvNode(输入纹理自带的 UV),否则回退到 TSL 内建的 uv() 属性节点(即 src/nodes/accessors/UV.js 中基于 attribute('uv') 的 vec2 节点),因此该节点既可用于全屏后处理,也可用于纹理空间内的局部效果。
6. 实战:在 RenderPipeline 中接入彩色畸变
仓库自带的完整示例是 examples/webgpu_postprocessing_ca.html(在示例索引 examples/files.json 中登记为 webgpu_postprocessing_ca)。其核心接法如下,展示了"场景 Pass → 输出 Pass → 彩色畸变 Pass"的完整链条,以及用 uniform 节点实现运行时调参:
import * as THREE from 'three/webgpu';
import { pass, renderOutput, uniform } from 'three/tsl';
import { chromaticAberration } from 'three/addons/tsl/display/ChromaticAberrationNode.js';
const renderer = new THREE.WebGPURenderer( { antialias: true } );
await renderer.init();
// 后处理管线
const renderPipeline = new THREE.RenderPipeline( renderer );
renderPipeline.outputColorTransform = false;
// scene pass
const scenePass = pass( scene, camera );
const outputPass = renderOutput( scenePass );
// uniform 节点:CPU 侧可随时修改 .value,无需重编译
const staticStrength = uniform( 1.5 ); // 示例中强度取 1.5(GUI 范围 0~3)
const staticCenter = uniform( new THREE.Vector2( 0.5, 0.5 ) );
const staticScale = uniform( 1.2 ); // 示例中 scale 取 1.2(GUI 范围 0.5~2)
const caPass = chromaticAberration( outputPass, staticStrength, staticCenter, staticScale );
// 用开关切换是否启用水色散
renderPipeline.outputNode = caPass;
示例中的可调参数范围(由 GUI 配置给出,可作为取值参考):
| 参数 | 示例初值 | GUI 可调范围 |
|---|---|---|
| strength | 1.5 | 0 ~ 3 |
| center.x / center.y | 0.5 / 0.5 | -1 ~ 1 |
| scale | 1.2 | 0.5 ~ 2 |
示例还演示了两个实用技巧:
- 动态开关效果:
renderPipeline.outputNode = enabled ? caPass : outputPass; renderPipeline.needsUpdate = true;,即通过切换输出节点实现 A/B 对比,不需要销毁重建 Pass; - 场景内容:示例场景由金属质感的彩色几何体(金属度 0.8、粗糙度 0.2 的多种颜色)加 GridHelper 与粒子组成——高饱和、高对比的物体正好用于观察色边效果,这也是调参时的一个好场景参照。
一个完整的可运行骨架(省略场景搭建):
import * as THREE from 'three/webgpu';
import { pass, renderOutput, uniform } from 'three/tsl';
import { chromaticAberration } from 'three/addons/tsl/display/ChromaticAberrationNode.js';
const renderer = new THREE.WebGPURenderer();
document.body.appendChild( renderer.domElement );
await renderer.init();
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 45, innerWidth / innerHeight, 0.1, 200 );
camera.position.set( 0, 15, 40 );
const pipeline = new THREE.RenderPipeline( renderer );
const outputPass = renderOutput( pass( scene, camera ) );
const strength = uniform( 1.0 );
const scale = uniform( 1.1 );
pipeline.outputNode = chromaticAberration( outputPass, strength, null, scale );
renderer.setAnimationLoop( () => {
strength.value = 1.0 + Math.sin( performance.now() * 0.001 ) * 0.5; // 呼吸式色散
pipeline.render();
};
其中 center 传 null 时使用默认的屏幕中心(0.5, 0.5)。
7. 注意事项与适用前提
- 仅适用于 WebGPU/TSL 体系:该节点基于
three/webgpu与three/tsl的节点构建流程工作,示例均以WebGPURenderer+RenderPipeline为宿主; - 输入自动纹理化:依赖
convertToTexture,传入普通节点时可能引入一次 RTT,接在pass()之后则直接复用 Pass 纹理,无额外开销; - 强度与比例常量:0.02 / 0.01 等偏移系数是硬编码在着色器函数中的,
strength与scale控制的是其缩放比例;从源码结构看,如需更激进的色散效果,需要调大strength(示例 GUI 上限为 3); - alpha 不参与畸变:透明度始终来自原始 UV,若你的 Pass 输出依赖 alpha 混合,行为是稳定可预期的;
- 复用安全性:继承自 TempNode,同一节点被多处引用时自动走变量缓存路径,不会重复展开采样表达式。
8. 相关资源
- 节点 API 文档:docs/pages/ChromaticAberrationNode.html.md
- TSL 函数总览(含
chromaticAberration签名表):docs/TSL.md - 节点实现源码:examples/jsm/tsl/display/ChromaticAberrationNode.js
- 官方示例:examples/webgpu_postprocessing_ca.html
- TempNode 缓存机制:src/nodes/core/TempNode.js
- 输入纹理化:src/nodes/utils/RTTNode.js
- 同模块其他显示效果(bloom、boxBlur、dotScreen 等)可在 src/nodes/display 目录与 TSL 函数总览中继续查找
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 StartedRust0627
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
