首页
/ three.js DecoratedTorusKnot4a 解析:参数化环面纽结曲线的导入、采样与 TubeGeometry 建模实战

three.js DecoratedTorusKnot4a 解析:参数化环面纽结曲线的导入、采样与 TubeGeometry 建模实战

2026-09-06 18:05:01作者:尤辰城Agatha

DecoratedTorusKnot4a 是 three.js 官方 addon(附加模块)CurveExtras.js 中提供的一条参数化三维装饰环面纽结曲线,继承自 Curve 基类,仅需在构造时传入一个 scale 缩放值即可得到一条平滑、闭合、形态复杂的样条路径。本文以 docs/pages/DecoratedTorusKnot4a.html.md 文档为主线,结合其源码实现与官方示例,完整讲解它的导入方式、构造与属性、getPoint 采样原理,以及如何用 TubeGeometry 把它变成可渲染的 3D 管道模型,让你在特效路径、样条扫掠建模等场景中直接复用这类参数曲线。

webgl_geometry_extrude_splines 示例中可交互切换 DecoratedTorusKnot4a 等参数曲线进行挤管渲染的运行截图

背景定位:CurveExtras 中的“装饰环面纽结”曲线族

在 three.js 中,Curve 是一条可采样的数学路径抽象,凡继承它的类都需要实现 getPoint( t, optionalTarget ) 来回答“曲线上参数 t 对应的三维坐标在哪”。官方把大量趣味参数曲线集中收纳在 examples/jsm/curves/CurveExtras.js 这一个 addon 文件中,文件头部注释就写明它是 "A bunch of parametric curves"(一组参数曲线),包括:

  • GrannyKnot(外婆结)
  • HeartCurve(心形曲线)
  • VivianiCurve(维维亚尼曲线)
  • TrefoilKnot(三叶纽结)、TorusKnotCinquefoilKnot(五叶纽结)
  • TrefoilPolynomialKnotFigureEightPolynomialKnot
  • DecoratedTorusKnot4aDecoratedTorusKnot4bDecoratedTorusKnot5aDecoratedTorusKnot5c

其中 DecoratedTorusKnot4a(装饰环面纽结 4a)与 4b / 5a / 5c 构成一组“装饰化环面纽结”子族。该文件中每个类的声明都以 @augments Curve 标注,且彼此共享相同的接口约定,说明整族曲线面向同一类应用场景:把一条参数化路径交给管状几何体或相机轨迹去使用。

导入方式:addon 必须显式 import

DecoratedTorusKnot4a 属于 three.js 的 addon(附加模块),并未被包含在核心构建中,使用前必须像 docs/pages/DecoratedTorusKnot4a.html.md 文档给出的那样显式导入:

import { DecoratedTorusKnot4a } from 'three/addons/curves/CurveExtras.js';

这一点可以通过 examples/jsm/curves/CurveExtras.js 末尾的导出列表得到印证——该文件一次性导出 GrannyKnotHeartCurveVivianiCurveTrefoilKnotTorusKnot 以及 DecoratedTorusKnot4a 等共 14 个类,任何期望使用这些曲线的代码都需要按 addon 规范从 three/addons/curves/CurveExtras.js 引入。在本地以裸 import 使用 addon 时,需要配置 import map 把 threethree/addons/ 映射到本地构建产物(官方示例的做法可见下文)。

构造函数与 scale 属性

构造签名

new DecoratedTorusKnot4a( scale : number )

根据文档,scale 为“The curve's scale”(曲线的缩放倍率)。仓库源码中构造函数的实际签名是带默认参数的:

constructor( scale = 40 ) {
    super();
    this.scale = scale;
}

这里需要注意一处文档与源码的差异:本文档 Constructor 小节把参数默认值标注为 1,而 Properties 小节与源码实现(examples/jsm/curves/CurveExtras.js#L492-L504)均明确默认值为 40。以源码为准,不传参时的实际默认缩放是 40,即 new DecoratedTorusKnot4a()new DecoratedTorusKnot4a( 40 ) 等价。构造函数内仅做一件事:调用父类构造并把 scale 存到实例属性上。

.scale 属性

decoratedTorusKnot4a.scale : number

该属性保存曲线的缩放倍率,默认值 40。它只在采样阶段参与计算(见下文 getPointmultiplyScalar( this.scale )),因此构造后修改 scale 会立即影响后续所有采样结果,可把它当作曲线尺寸的“一键缩放”旋钮。也正因为缩放仅作用于最终坐标,创建曲线后再改变 scale 完全安全,不必重新构造实例。

深入源码:getPoint 的实现与参数化数学

DecoratedTorusKnot4a 作为 Curve 的子类,必须实现 .getPoint(),文档标注该方法 Overrides(重写自)Curve#getPoint。仓库中的真实实现位于 examples/jsm/curves/CurveExtras.js#L513-L525

getPoint( t, optionalTarget = new Vector3() ) {

    const point = optionalTarget;

    t *= Math.PI * 2;

    const x = Math.cos( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
    const y = Math.sin( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
    const z = 0.35 * Math.sin( 5 * t );

    return point.set( x, y, z ).multiplyScalar( this.scale );

}

方法签名约定

.getPoint( t : number, optionalTarget : Vector3 ) : Vector3
  • t:插值因子,表示曲线上某处位置,取值必须位于闭区间 [0, 1]0 对应曲线起点,1 对应终点。
  • optionalTarget(可选):结果写入的目标向量。从源码可见它被当作局部变量复用,若省略则默认新建一个 Vector3
  • 返回值:曲线在该参数处的位置向量。

数学结构拆解

θ = 2π·t,把三个分量拆开看:

半径调制:R(θ) = 1 + 0.6 · ( cos(5θ) + 0.75 · cos(10θ) )
x(θ)      = R(θ) · cos(2θ)
y(θ)      = R(θ) · sin(2θ)
z(θ)      = 0.35 · sin(5θ)

由此可以得到三个结构性结论:

  1. x/y 平面做两重回转cos(2θ)sin(2θ) 表示平面上相对主循环有两倍的角频率,纵向 z 与半径 R 分别以 5 倍、10 倍谐波调制,本质是一条 (2, 5) 型环面纽结的装饰化变体——先按 2、5 两个互质周期缠绕出纽结骨架,再叠加上 10 倍高频的“装饰花瓣”。这是它与普通 TorusKnot 曲线的核心区别:多了一层高频余弦装饰项。
  2. 曲线天然闭合:由于所有三角函数项的角度都是 2θ、5θ、10θ(均为 的整数倍),t=0t=1 位置严格重合,且各阶导数连续。因此它是一条光滑闭合曲线,这为使用 closed: true 的管道几何体或循环相机轨迹提供了条件。
  3. 可推出的坐标量程:由 |cos| ≤ 1 可推出 R(θ) 落在 [1 − 0.6×1.75, 1 + 0.6×1.75] = [−0.05, 2.05] 区间,z 落在 [−0.35, 0.35] 区间,即单位曲线 x/y 大致在 ±2.05 内、z 在 ±0.35 内。因此当 scale 取默认值 40 时,最终曲线的包络约在 x/y 方向 ±82、z 方向 ±14 的场景单位内;需要更大或更小的模型体积时,直接调整 scale 即可按比例缩放。

从基类继承的采样体系

文档特别标注 getPoint 重写自 [Curve#getPoint]DecoratedTorusKnot4aCurve 基类的协作是整个使用链的根基。Curve 基类 提供了在 getPoint 之上的完整派生采样体系:

方法 作用 内部调用
.getPoint( t, optionalTarget ) 按参数 t(线性参数,非弧长)取点 子类实现,本曲线已重写
.getPointAt( u, optionalTarget ) 按弧长比例 u 取点(需先建立弧长表) 间接调用 getPoint
.getPoints( divisions ) divisions 等分 t 返回点列 循环调用 getPoint( d / divisions )
.getSpacedPoints( divisions ) 按弧长等距返回点列 循环调用 getPointAt
.computeFrenetFrenet... 计算 Frenet 标架(切线/法线/副法线) TubeGeometry 内部使用

也就是说,你只需要在子类里精确实现 getPoint 这一个纯数学方法,three.js 就会自动“赠送”弧长参数化、等距采样、长度计算等整套工具。例如:

const curve = new DecoratedTorusKnot4a();

const samples = curve.getPoints( 200 );      // 等 t 采样 201 个点,适合描线
const spaced  = curve.getSpacedPoints( 200 ); // 弧长等距采样,适合粒子流、动画轨迹
const pos     = new THREE.Vector3();
curve.getPoint( 0.5, pos );                  // 取中点,复用外部向量避免 GC

实战:用 TubeGeometry 把曲线变成管道模型

DecoratedTorusKnot4a 最常见的用法是作为“路径”交给 TubeGeometry(管状几何体)沿曲线扫掠出三维体。TubeGeometry 的构造签名(见 TubeGeometry.js#L45):

new TubeGeometry( path, tubularSegments = 64, radius = 1, radialSegments = 8, closed = false )

一个最小可运行的组合示例:

import * as THREE from 'three';
import { DecoratedTorusKnot4a } from 'three/addons/curves/CurveExtras.js';

const scene = new THREE.Scene();

// 1. 构造曲线(scale 默认 40,包络约 ±80 场景单位)
const curve = new DecoratedTorusKnot4a();

// 2. 沿曲线挤管:因为曲线闭合,closed 建议置 true 以生成无缝管道
const tubeGeometry = new THREE.TubeGeometry(
    curve,   // 路径曲线
    200,     // tubularSegments:沿路径的采样段数,越大越平滑
    2,       // radius:管道半径
    8,       // radialSegments:截面圆细分数量
    true     // closed:曲线是闭合的
);

const mesh = new THREE.Mesh(
    tubeGeometry,
    new THREE.MeshLambertMaterial( { color: 0xff00ff } )
);
scene.add( mesh );

TubeGeometry 内部会调用 path.computeFrenetFrames() 获得路径上每个采样点的切线/法线/副法线标架,再逐点铺设圆形截面(见 TubeGeometry.js#L66-L72),所以曲线在数学上越光滑(DecoratedTorusKnot4a 各阶连续),挤出的管道表面越顺滑。

参考官方示例与可调参数

这条曲线正是官方示例 webgl_geometry_extrude_splines.html(spline extrusion,样条挤出)的备选路径之一。示例把全部曲线实例放入一个字典:

const splines = {
    ...
    DecoratedTorusKnot4a: new Curves.DecoratedTorusKnot4a(),
    DecoratedTorusKnot4b: new Curves.DecoratedTorusKnot4b(),
    DecoratedTorusKnot5a: new Curves.DecoratedTorusKnot5a(),
    DecoratedTorusKnot5c: new Curves.DecoratedTorusKnot5c(),
    ...
};

示例通过 lil-gui 暴露了四组与本曲线直接相关的实时调节参数,可作为调试此类曲线时的参考经验值:

GUI 参数 取值范围(步长) 作用 触发行为
spline 全部曲线名 切换路径曲线 重建 TubeGeometry
scale 2 ~ 10(步进 2) 整体网格缩放(mesh.scale 调用 setScale()
extrusionSegments 50 ~ 500(步进 50) 对应 tubularSegments,沿路径分段数 重建几何体
radiusSegments 2 ~ 12(步进 1) 截面细分数 重建几何体
closed 布尔 管道是否闭合 重建几何体

此外该示例还演示了把一条曲线同时当作相机飞行轨迹:一个内嵌的 splineCamera(透视相机)沿管道路径运动,CameraHelper 实时显示其视锥。这说明 DecoratedTorusKnot4a 这类参数曲线不仅能喂给 TubeGeometry 建模,还能直接充当过场动画、巡游相机的路径源,只需用 .getPointAt( t ) 或基类的弧长采样接口即可让相机匀速沿曲线行进。

同族曲线的快速对比与选型

CurveExtras.js 的同一段源码中,紧挨着 DecoratedTorusKnot4a 定义了 DecoratedTorusKnot4bDecoratedTorusKnot5a/5c,它们的骨架写法完全一致(构造默认 scale = 40getPointt *= Math.PI * 2),区别只在调制系数:

曲线 平面回转 半径/装饰项 纵向振荡 特征
DecoratedTorusKnot4a 1 + 0.6(cos5θ + 0.75cos10θ) 0.35sin5θ 装饰最“蓬松”,含 10 倍高频瓣
DecoratedTorusKnot4b 1 + 0.45cos3θ + 0.4cos9θ 0.2sin9θ 装饰更收敛,高频瓣更细
DecoratedTorusKnot5a 1 + 0.5(cos5θ + 0.4cos20θ) 0.35sin15θ 平面回转加倍,形态更密

可见“数字 + 字母”后缀只是该子族的命名序号(区分同一骨架的不同装饰频率组合),使用时可按视觉形态直接选择,接口与用法完全一致,随时可在同一段渲染代码里互换验证。

使用注意事项小结

  1. t 必须限定在 [0, 1]getPoint 的入参代表曲线上的插值位置,超出范围虽不会崩溃,但采样点会沿周期曲线继续绕行,容易产生意外的自交路径,建议按文档约定在区间内取值。
  2. scaleTubeGeometry 的半径互相独立:曲线 scale 决定路径整体大小,TubeGeometryradius 决定管道粗细,二者配合时注意换算视觉比例(示例中 GUI 的 scale 是对 mesh.scale 做二次整体缩放,与曲线的 scale 是两个不同层级)。
  3. 闭合曲线的管道DecoratedTorusKnot4a 本身是闭合光滑曲线,但 TubeGeometryclosed 参数默认是 false;想要无缝管道记得显式传 true,否则首尾会出现开口。
  4. 对象复用避免内存抖动:若在动画循环里高频调用 getPoint,务必传入复用的 optionalTarget 向量,源码中该向量会被直接 set 覆写并返回,可有效避免每帧产生新 Vector3 对象。

参考源码路径

登录后查看全文
热门项目推荐
相关项目推荐