首页
/ three.js TubeGeometry 详解:沿 3D 曲线扫掠生成管道网格几何体

three.js TubeGeometry 详解:沿 3D 曲线扫掠生成管道网格几何体

2026-09-08 21:57:28作者:凌朦慧Richard

TubeGeometry 是 three.js 提供的一种按曲线路径"扫掠"(extrude)出管道状网格的内置几何体,它接受任意一条 Curve 子类定义的 3D 曲线作为骨架,围绕曲线生成具有半径、法线与 UV 的圆管表面。本文以 官方文档页 TubeGeometry.html.md 为核心骨架,结合 TubeGeometry 源码Curve 基类实现,完整讲解其构造参数、默认值、Frenet 标架生成算法、.fromJSON 序列化约束以及实际项目中的调优方法,读完后你可以直接用任意曲线(正弦曲线、Catmull-Rom 样条、贝塞尔曲线等)在 three.js 中生成电线、管道、绳索、隧道或样条路径护栏等几何体。

TubeGeometry 概述与继承关系

文档开篇用一句话定义了它的职责:"Creates a tube that extrudes along a 3D curve"——即沿一条三维曲线拉伸出一个管道状几何体。曲线的走向决定了管道的轴线,而管道的截面始终是一个以曲线为圆心、半径为 radius 的圆环。

从类型体系上看,文档头部标注的继承链为:

EventDispatcher → BufferGeometry → TubeGeometry

单元测试 test/unit/src/geometries/TubeGeometry.tests.js 中对这一关系做了验证:TubeGeometry 的实例 instanceof BufferGeometrytrue,且实例的 type 字段为字符串 'TubeGeometry'。在源码中,实例被设置了自己的 this.type = 'TubeGeometry'(见 src/geometries/TubeGeometry.js),并在构造时依次写入 indexpositionnormaluv 四个 buffer 属性,因此它可以被 Mesh 直接使用,配合受光照影响的 MeshStandardMaterialMeshPhongMaterial 等材质即可显示正确的明暗与纹理。

仓库还附带一个交互演示入口:官方文档页 TubeGeometry.html 内置了 geometry-browser.html#TubeGeometry 的 iframe 预览,源码注释中的 @demo 也指向该页面,可直接在浏览器中拖拽观察不同参数组合的效果。

官方代码示例

文档给出的最小可运行示例通过继承 THREE.Curve 自定义了一条正弦曲线作为管道路径:

class CustomSinCurve extends THREE.Curve {
	getPoint( t, optionalTarget = new THREE.Vector3() ) {
		const tx = t * 3 - 1.5;
		const ty = Math.sin( 2 * Math.PI * t );
		const tz = 0;
		return optionalTarget.set( tx, ty, tz );
	}
}

const path = new CustomSinCurve( 10 );
const geometry = new THREE.TubeGeometry( path, 20, 2, 8, false );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );

这个示例的关键点在于:TubeGeometry 自己并不关心路径的"形状函数",它只要求你传入一个实现了 Curve 接口的对象(最核心的是 getPoint(t, optionalTarget) 方法,返回曲线上参数 t ∈ [0,1] 处对应的三维坐标)。示例中的自定义曲线在参数平面上画出一段正弦波(t * 3 - 1.5 把 x 坐标映射到 [-1.5, 1.5]),TubeGeometry 就会沿着这段正弦波生成直径 4(半径 2)、轴向 20 段、截面 8 段的管道。注意示例中 new CustomSinCurve( 10 ) 传入的 10 并没有对应的构造函数形参,它被该类忽略,正弦形状完全由 getPoint 内部决定——实际开发中可自行扩展构造函数接收振幅、波长等参数。

构造参数详解

文档给出了构造签名:

new TubeGeometry( path, tubularSegments, radius, radialSegments, closed )

五个参数的默认值在 构造函数定义处(默认实参)中直接体现,汇总如下:

参数 类型 默认值 含义
path Curve QuadraticBezierCurve3 决定管道中轴线的三维曲线
tubularSegments number 64 沿曲线(轴向)划分的段数
radius number 1 管道截面圆环半径(世界单位)
radialSegments number 8 截面圆周上的分段数
closed boolean false 管道首尾是否闭合连成环

各参数逐一展开:

path —— 曲线的默认值并非简单的"无参数曲线",源码里它是一条起点 (-1,-1,0)、终点 (1,1,0)、控制点 (-1,1,0)QuadraticBezierCurve3。当你不传 path 直接 new THREE.TubeGeometry() 时,得到的就是沿该贝塞尔曲线的管道。生产环境中更多使用 CatmullRomCurve3,它可通过一组点自动插值出平滑曲线,非常适合表现随意扭曲的管线;three.js 还内置了其他可用的三维/二维曲线类(统一从 Curves.js 导出):ArcCurveCatmullRomCurve3CubicBezierCurveCubicBezierCurve3EllipseCurveLineCurveLineCurve3QuadraticBezierCurveQuadraticBezierCurve3SplineCurve。更复杂的多段路径可用 CurvePath 组合,但需注意后文 .fromJSON 对其并不支持。

tubularSegments —— 沿曲线方向的细分段数。管道会被切成若干圈环(ring),段数越多曲线轮廓越平滑,顶点数与三角形数随之线性增长。

radius —— 截面圆环半径。注意它是整条管道上的常量;若需要半径沿路径渐变(如胶囊、喇叭口),TubeGeometry 本身不提供该能力,需要自定义几何体或在构造后逐顶点改写 position。

radialSegments —— 截面圆周上的分段数,直接决定管道"棱角感"。默认 8 即可看到明显的八棱柱效果;设为 3~4 可获得低多边形风格管道。

closed —— 是否把管道首尾相接成闭环。为 true 时(如圆环路径)首尾无缝焊接;为 false 时保留两个开口端。

path 曲线与弧长均匀采样机制

TubeGeometrypath 的调用方式决定了它适用于任意弧长分布不均的曲线。在 generateSegment 实现 中,每个截面位置使用:

P = path.getPointAt( i / tubularSegments, P );

getPointAt(u) 与直接调 getPoint(t) 不同:它先把参数 u 通过曲线内部维护的弧长查找表换算成对应弧长位置的参数 t(见 Curve.getUtoTmapping)。也就是说,每个管道截面都落在"弧长等分点"上,而不是曲线参数等分点——即使曲线的参数化不均匀,管道截面间距在视觉上依然是均匀的。这一特性写在了源码注释里:"we use getPointAt to sample evenly distributed points from the given path"。

因此当你自定义 path 时,只需重写 getPoint,其余如弧长计算、等距采样、切线求解(getTangent/getTangentAt)、包围盒估算等都由 Curve 基类 提供;若曲线为闭合环,应确保 getPoint(0)getPoint(1) 重合,并把 closed 设为 true

底层生成算法:Frenet 标架与逐环扫掠

TubeGeometry 的构造过程在 src/geometries/TubeGeometry.js 中清晰可分五步:

第一步:计算 Frenet 标架。 构造函数首先调用 path.computeFrenetFrames( tubularSegments, closed )。该算法实现在 Curve.js 的 computeFrenetFrames(源码注释引用了印第安纳大学 TR425 技术报告):先在每个采样点求切线(tangent),再用"沿最小切线分量方向选取初始法线、随后逐点叉积递推"的方式,构造出一组互相垂直并随曲线平滑转动的 tangents / normals / binormals。这保证管道在拐弯时不发生额外扭转(可类比铁路铁轨的平行移动)。结果被保留到几何体实例上作为公开内部成员:

this.tangents = frames.tangents;
this.normals = frames.normals;
this.binormals = frames.binormals;

第二步:逐段生成截面顶点。 generateBufferData() 对每个截面调用 generateSegment(i):用 getPointAt 取截面中心点 P,再取该处法线 N 与副法线 B,绕截面按角度 v = j / radialSegments * 2π 布点:

normal = ( cos * N + sin * B ).normalize();
vertex = P + radius * normal;

由于 NB 都与切线垂直,圆环半径方向恰好就是管道表面的外法线方向,因而同一份法线数据可直接写入 normal 属性供光照使用,顶点与法线一一对应、天然平滑着色。

第三步:处理闭合。 循环体先生成 i = 0 … tubularSegments - 1 的截面,随后依据 closed 补最后一段:

  • closed = false:再生成 i = tubularSegments 的截面,即曲线终点处的收口环(管道两端为开口);
  • closed = true:重复生成 i = 0 的截面,使末尾环与起始环位置重合,仅 UV 不同,从而让首尾三角形网格无缝对接。

这一步对应的正是源码注释:"if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ)"。

第四步:生成 UV。 UV 被独立在一个函数中按 u = i / tubularSegmentsv = j / radialSegments 均匀铺展,因此整条管道可直接贴图,且闭合管道沿轴向接缝处也能做到纹理连续。

第五步:生成索引。 相邻两圈环之间逐格构建两个三角形 (a, b, d)(b, c, d)

由此可推导出网格规模:顶点数为 (tubularSegments + 1) × (radialSegments + 1),三角形数为 tubularSegments × radialSegments × 2。例如默认值(64、1、8)将产生 585 个顶点、1024 个三角形,数量可控、性能开销很小。

属性与序列化

.parameters : Object

文档指出 .parameters 保存了构造时所用的参数快照(pathtubularSegmentsradiusradialSegmentsclosed),构造后修改它不会改变已生成的几何体——几何数据在构造瞬间已固化到 GPU buffer 中,需要变更时应重新 new 一个实例。此外 TubeGeometry 重写了 copy(source),在复制父类 BufferGeometry 数据之外,用 Object.assign({}, source.parameters)parameters 做浅拷贝,避免两个实例共享同一引用。

.fromJSON( data ) : TubeGeometry

静态工厂方法 fromJSON 用于从序列化 JSON 恢复实例。序列化侧由实例方法 toJSON 完成——它在父类 JSON 基础上追加 data.path = this.parameters.path.toJSON(),把整条曲线也序列化进去;反序列化侧则依据 data.path.typeCurves 注册表中查表重建曲线:

static fromJSON( data ) {
	return new TubeGeometry(
		new Curves[ data.path.type ]().fromJSON( data.path ),
		data.tubularSegments, data.radius, data.radialSegments, data.closed
	);
}

源码注释对适用范围给出了明确限制:仅对内置曲线(如 CatmullRomCurve3、各贝塞尔曲线)有效;用户自定义的 Curve 子类以及 CurvePath 组合路径无法被反序列化,因为它们的类型名不在 Curves 注册表中。如果 data.path.type 查表失败,构造函数将抛出类型错误。因此跨会话持久化带自定义曲线的管道时,需自行处理曲线重建逻辑。

质量与性能调优建议

综合参数含义与网格规模公式,可按需组合:

  • 截面圆滑度由 radialSegments 决定:默认 8 是明显的八边形,追求精细可用 16~32;做低多边形/风格化渲染时可降到 4~6 以大幅削减顶点。
  • 轴向平滑度由 tubularSegments 决定:曲线越曲折、弯道越急,需要越高的轴向分段;直线或缓弯可用较少段数。仓库示例 webgl_geometry_extrude_splines.html 中为复杂的 GrannyKnot 样条设置了 tubularSegments = 100radialSegments = 3radius = 2closed = true,即在轴向舍得细分、在圆周方向保持低模,兼顾曲线细节与整体性能,这一取舍思路很有参考价值。
  • 闭合选项要与路径形状匹配:用 closed = true 时曲线终点必须与起点重合(如 CatmullRomCurve3 设置 closed 构造参数),否则焊接处会形成不自然的折叠。
  • 资源释放TubeGeometry 自身不引入额外 GPU 资源管理,复用 BufferGeometrydispose() 即可;上述示例删除旧网格时也调用了 geometry.dispose()

综合实战示例

下面用一个 CatmullRomCurve3 让管道穿过 5 个随机控制点形成流畅的自由管线,并对两种典型用法给出可运行代码:

import * as THREE from 'three';

// 1) 用 CatmullRomCurve3 平滑穿过控制点,生成自由弯管
const points = [];
for ( let i = 0; i < 5; i ++ ) {
	points.push( new THREE.Vector3(
		( i - 2 ) * 2, Math.sin( i * 1.2 ) * 3, Math.cos( i * 1.2 ) * 3
	) );
}
const closedPath = new THREE.CatmullRomCurve3( points, true, 'catmullrom', 0.5 );

// 2) 让管道沿曲线首尾相连成闭环
const tube = new THREE.TubeGeometry( closedPath, 128, 0.25, 16, true );

// 3) 组合材质与网格;若使用受光材质,normal 属性会被自动用于光照
const material = new THREE.MeshStandardMaterial( { color: 0xff6600, roughness: 0.4 } );
const mesh = new THREE.Mesh( tube, material );
scene.add( mesh );

// 不再使用时释放
// tube.dispose();

在浏览器里直接观察最便捷的方式是打开仓库中的官方演示:运行 examples 下的相关页面 可以对比不同样条曲线(含多种 CatmullRomCurve3 打结路径)下 TubeGeometry 的实时效果,GUI 中可交互调节 extrusionSegmentsradiusSegmentsclosed,与上文参数语义一一对应。

结合源码与测试再校验

综上,TubeGeometry 的价值在于把"任意 3D 曲线 + 半径"这一高度抽象的输入转化为标准、可直接渲染的网格数据。掌握其五个参数的含义、Frenet 帧的扫掠原理与 fromJSON 的序列化边界,就足以在项目中灵活构造从简单电线到复杂结绳的各类管道几何体。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395