three.js ExtrudeGeometry 完全指南:用 Shape 轮廓生成可倒角、可沿路径扫掠的三维几何体
ExtrudeGeometry(拉伸几何体)是 three.js 中把二维平面轮廓(Shape)"拉出厚度"变成三维实体的核心工具,广泛用于 3D 文字牌匾、建筑体块、徽章图标、管状管道以及任意带孔轮廓实体等场景。本文基于 官方文档页面 展开,并结合仓库内 源码实现 与其配套单元测试与官方示例,系统讲解它的构造方式、全部拉伸与倒角(bevel)参数、UV 生成机制、沿 3D 曲线扫掠的进阶用法,以及 JSON 序列化往返的底层细节。读完你即可在 WebGL 场景中从零构造出可控的拉伸几何体,并理解每个参数在源码层面如何影响顶点与面片的生成。
功能与继承关系
ExtrudeGeometry 从二维 Shape(可以是一个,也可以是数组)出发,按一定厚度沿 Z 轴(直线拉伸)或沿一条三维样条曲线(路径扫掠)生成具有顶盖、底面与侧壁的封闭网格。它继承自 BufferGeometry,其继承链为:
EventDispatcher → BufferGeometry → ExtrudeGeometry
因此它拥有 BufferGeometry 提供的全部能力:setAttribute、computeVertexNormals、addGroup、toJSON、dispose 等。从源码 this.type = 'ExtrudeGeometry'(ExtrudeGeometry.js)可以看出其类型标识,继承关系也被单元测试中 object instanceof BufferGeometry === true 的断言所验证。
在几何体工厂集合(src/geometries/Geometries.js)中它通过 export * 统一导出,因此可直接 import { ExtrudeGeometry } from 'three' 使用,也可通过 new THREE.ExtrudeGeometry(...) 访问。
快速上手:矩形拉伸示例
文档首页给出的最小可用示例(同样内嵌于源码 JSDoc,见 ExtrudeGeometry.js):
const length = 12, width = 8;
const shape = new THREE.Shape();
shape.moveTo( 0, 0 );
shape.lineTo( 0, width );
shape.lineTo( length, width );
shape.lineTo( length, 0 );
shape.lineTo( 0, 0 );
const geometry = new THREE.ExtrudeGeometry( shape );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
moveTo/lineTo 以当前坐标系为单位绘制 2D 多边形路径,默认参数(深度 depth = 1、开启倒角 bevelEnabled = true)下会生成一块边缘带圆滑倒角的矩形板。若想获得平整边缘的立方体块,可显式关闭倒角:
const geometry = new THREE.ExtrudeGeometry( shape, {
depth: 8,
bevelEnabled: false,
steps: 1
} );
构造器与参数总览
new ExtrudeGeometry( shapes : Shape | Array.<Shape>, options : ExtrudeGeometry~Options )
- shapes:单个
Shape或多个Shape组成的数组。源码在构造时先将入参规整为数组再逐个处理:shapes = Array.isArray( shapes ) ? shapes : [ shapes ],每个 Shape 都会经过一次addShape()完整流程(ExtrudeGeometry.js)。 - options:拉伸设置对象。未传入时默认使用
{},所有参数走源码默认值(见 ExtrudeGeometry.js),下表汇总了完整参数项、默认值与含义:
| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
curveSegments |
number | 12 |
轮廓曲线上用于近似曲线的取样点数量,值越大曲线越平滑、顶点越多 |
steps |
number | 1 |
沿拉伸深度(或扫掠路径)方向细分的层段数 |
depth |
number | 1 |
沿 Z 轴拉伸的深度(仅直线拉伸时生效) |
bevelEnabled |
boolean | true |
是否在轮廓边缘生成倒角 |
bevelThickness |
number | 0.2 |
倒角沿 Z 轴(即向原轮廓内部纵深方向)的厚度 |
bevelSize |
number | bevelThickness - 0.1 |
倒角从轮廓边缘向外(XY 平面法线方向)扩展的距离 |
bevelOffset |
number | 0 |
倒角在 XY 平面内从轮廓边缘算起的起始偏移距离 |
bevelSegments |
number | 3 |
倒角的分层数(越大越圆润) |
extrudePath |
Curve(Curve3) | null |
供 Shape 沿其扫掠的三维曲线路径,沿路径拉伸时不支持倒角 |
UVGenerator |
Object | WorldUVGenerator |
提供自定义 UV 生成函数的对象(generateTopUV / generateSideWallUV) |
逐项参数详解与源码行为
curveSegments:当 Shape 中包含曲线(圆弧、样条、二次/三次贝塞尔)时,曲线会被离散成若干个点。文档描述为"曲线上的取样点数量"。它同时传递给 shape.extractPoints( curveSegments ),用于控制 Shape 轮廓(含孔洞)的取点密度(ExtrudeGeometry.js)。
steps:拉伸方向的分层数。直线拉伸时相邻两层间距为 depth / steps * s(ExtrudeGeometry.js);沿路径扫掠时对应路径上取样点数量——源码通过 extrudePath.getSpacedPoints( steps ) 在路径上均匀取 steps + 1 个点(ExtrudeGeometry.js)。steps 越大,侧面越顺滑,但顶点数线性增长。
depth:仅对直线拉伸有意义,控制 Z 方向拉伸总高度。默认 1,若 depth: 0 且关闭倒角,可得到近似平面但合法(正反面朝向相反)的双面片结构。
倒角五参数(bevelEnabled / bevelThickness / bevelSize / bevelOffset / bevelSegments):这是 ExtrudeGeometry 最有特色的能力,用于把生硬的棱边修出 45° 切角乃至圆角。源码中的取值规律为:
- 当
bevelEnabled = false时,其余倒角参数会被强制清零:bevelSegments = 0; bevelThickness = 0; bevelSize = 0; bevelOffset = 0(ExtrudeGeometry.js)。 - 倒角层的几何位置按三角函数插值计算(ExtrudeGeometry.js):
const t = b / bevelSegments;
const z = bevelThickness * Math.cos( t * Math.PI / 2 ); // Z 方向厚度
const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; // XY 平面扩缩
可见厚度按余弦衰减、向外扩缩量按正弦递增,从而形成过渡自然的锥形倒角;b 从 0 扫到 bevelSegments 即产生"层"。
extrudePath:传一条三维曲线(Curve,典型如 CatmullRomCurve3)后,Shape 会像牙膏一样沿曲线方向扫掠出管状实体。源码为该分支做了两件关键事:
- 通过
extrudePath.getSpacedPoints( steps )取路径点; - 调用
extrudePath.computeFrenetFrames( steps, isClosed )计算 Frenet 活动标架(法线/副法线/切线),把二维 Shape 的x、y分别映射到法线与副法线方向(ExtrudeGeometry.js)。
UVGenerator:传入自定义对象可接管 UV 映射。默认实现 WorldUVGenerator 提供两个方法:generateTopUV 直接把顶面/底面的世界 XY 坐标当作 UV;generateSideWallUV 则依据侧壁面法向选择 XZ 或 YZ 平面展开为 UV(ExtrudeGeometry.js)。因此默认 UV 并非归一化的 0–1,若希望纹理按单位比例平铺,可基于默认实现自定义。
源码级原理:顶点、面、UV 与材质分组如何生成
深入 addShape 实现 可以看清整个网格生成管线,大致分六步:
- 方向统一:调用
ShapeUtils.isClockWise检测轮廓绕向,非顺时针轮廓会被reverse(),保证内外环方向一致、面朝向稳定(ExtrudeGeometry.js)。 - 重叠点清理:
mergeOverlappingPoints会把距离在1e-10量级(按点坐标幅值缩放)的相邻重合点原地剔除,防止退化三角面(ExtrudeGeometry.js)。 - 倒角方向向量:为每个顶点计算使其轮廓向内/外平移的单位位移向量
getBevelVec,它基于相邻两条边做"左移 1 单位的平行线求交"得到,并针对共线边、尖角做了特判防止尖刺(ExtrudeGeometry.js)。 - 平面三角化:把轮廓与孔洞交给
ShapeUtils.triangulateShape做耳切法三角剖分,得到顶/底盖面的三角索引(ExtrudeGeometry.js)。 - 生成各层顶点:依次写入底面、中部
steps层、倒角层的顶点;每次写入通过内部函数v()/addVertex()追加到统一verticesArray,层与层之间以vlen(单层顶点数)为步长偏移(ExtrudeGeometry.js)。 - 生成三角面与 UV:
buildLidFaces()生成顶/底盖面,buildSideFaces()按四边形的两个对角拆三角形生成侧壁面;随后调用 UVGenerator 补全每张面的 UV 坐标(ExtrudeGeometry.js)。
值得关注的是材质分组(groups):addShape 会分别执行 scope.addGroup( ..., 0 )(盖面)与 scope.addGroup( ..., 1 )(侧壁面)(ExtrudeGeometry.js),因此可以直接给 Mesh 传材质数组,例如让盖面与侧壁使用不同材质。构造收尾处调用 this.computeVertexNormals() 统一计算逐顶点法线(ExtrudeGeometry.js),整个构造过程默认不产生索引(index 为空),以非索引三角形列表存储。
使用带孔洞(Holes)的 Shape
ExtrudeGeometry 天然支持"内孔",只要在 Shape 上追加孔洞路径即可。以官方 webgl_geometry_shapes 示例 中的笑脸造型为例:
const smileyShape = new THREE.Shape();
// ... 绘制面部外轮廓 ...
smileyShape.holes.push( smileyEye1Path ); // 左眼
smileyShape.holes.push( smileyEye2Path ); // 右眼
smileyShape.holes.push( smileyMouthPath ); // 嘴
const geometry = new THREE.ExtrudeGeometry( smileyShape, extrudeSettings );
示例实际使用的拉伸参数为 { depth: 8, bevelEnabled: true, bevelSegments: 2, steps: 2, bevelSize: 1, bevelThickness: 1 }(webgl_geometry_shapes.html)。源码中孔洞会作为独立轮廓参与绕向修正与三角化(holes.forEach( mergeOverlappingPoints )),并对孔洞顶点计算各自的"外扩"倒角方向(孔洞外扩对应实体收缩,见 expandedHoleVertices 逻辑),保证带孔造型的倒角在几何上依然正确。
进阶实战:沿 3D 路径扫掠
这是 extrudePath 参数的经典用法。官方示例 webgl_geometry_extrude_shapes.html 展示了完整的参数组合:把三角形、星形等平面轮廓沿闭合或随机样条扫掠成三维轨迹体。
沿一条闭合样条扫掠的完整配置:
// 1. 构造三维路径(闭合的 CatmullRom 样条)
const closedSpline = new THREE.CatmullRomCurve3( [
new THREE.Vector3( - 60, - 100, 60 ),
new THREE.Vector3( - 60, 20, 60 ),
new THREE.Vector3( - 60, 120, 60 ),
new THREE.Vector3( 60, 20, - 60 ),
new THREE.Vector3( 60, - 100, - 60 )
] );
closedSpline.curveType = 'catmullrom';
closedSpline.closed = true;
// 2. 构造二维截面轮廓(三角形)
const pts = [], count = 3;
for ( let i = 0; i < count; i ++ ) {
const l = 20;
const a = 2 * i / count * Math.PI;
pts.push( new THREE.Vector2( Math.cos( a ) * l, Math.sin( a ) * l ) );
}
const shape = new THREE.Shape( pts );
// 3. 沿路径拉伸(注意:路径拉伸不支持倒角)
const extrudeSettings = {
steps: 100, // 路径上的取样密度
bevelEnabled: false, // 必须关闭,源码会自动强制关闭
extrudePath: closedSpline
};
const geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
const mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: 0xb00000 } ) );
scene.add( mesh );
同示例还对同一轮廓分别演示了直线拉伸 + 开启倒角的对照配置(webgl_geometry_extrude_shapes.html):
const extrudeSettings3 = {
depth: 20,
steps: 1,
bevelEnabled: true,
bevelThickness: 2,
bevelSize: 4,
bevelSegments: 1
};
使用提示:路径拉伸路径上的扫掠密度由 steps 控制,steps 越大轨迹越平滑(示例中分别用了 100、200),但顶点开销同步上升;若路径闭合,需将样条的 closed 属性置 true,否则收尾会出现接缝。若目标是"沿路径生成中空圆管",可改看 TubeGeometry 扫掠示例 中 new THREE.TubeGeometry( extrudePath, tubularSegments, radius, radialSegments, closed ) 的用法——它与 ExtrudeGeometry 的路径扫掠互为补充:前者以固定圆形截面扫掠,后者以任意二维轮廓扫掠。
Properties 属性
.parameters : Object
保存构造时传入的 { shapes, options }(ExtrudeGeometry.js),用于几何体自描述。文档特别强调:实例化后修改 .parameters 不会改变几何体本身——因为网格已在构造期一次性生成,参数仅作存档与序列化用途。
序列化:toJSON 与 fromJSON
ExtrudeGeometry 继承并重写了 toJSON(),额外把 Shape 引用序列化为 uuid 列表、把 options 整体浅拷贝到 data.options;若存在 extrudePath,则调用 options.extrudePath.toJSON() 输出路径曲线(ExtrudeGeometry.js)。由于 Shape 本体通常通过 scene.toJSON() 的 geometries/shapes 映射表存储,因此 toJSON 输出中只保留引用关系。
对应的工厂方法:
ExtrudeGeometry.fromJSON( data : Object, shapes : Array.<Shape> ) : ExtrudeGeometry
静态方法 fromJSON 从 JSON 反序列化出新实例:它根据 data.shapes 里的 uuid 从传入的 shapes 数组中还原每个 Shape,并把 data.options.extrudePath 依其 type(如 CatmullRomCurve3)通过 new Curves[ type ]().fromJSON( extrudePath ) 重建为真正的曲线对象,最后 return new ExtrudeGeometry( geometryShapes, data.options )(ExtrudeGeometry.js)。所有曲线类型统一注册在 Curves.js 工厂中。
此外该类还覆写了 copy( source ),用 Object.assign 浅拷贝源实例的 parameters(ExtrudeGeometry.js),保证克隆出的几何体在参数层面可自描述。
测试与常见注意事项
仓库的单元测试覆盖了三类基础断言:继承自 BufferGeometry、可正常实例化、type 字段为 'ExtrudeGeometry'。若要自定义测试,可将相关扩展用例置于 test/unit/src/geometries/ 下运行 QUnit 测试套件。
结合源码与实际使用,还需注意几个易踩点:
- 路径扫掠与倒角互斥:传入
extrudePath时,无论bevelEnabled是否为true,源码都会强制bevelEnabled = false(ExtrudeGeometry.js),所以别期待"沿曲线且带倒角"的组合。 steps与curveSegments都是成本开关:前者增加层数、后者增加每层顶点数,二者叠加会显著抬高顶点总量,注意与bevelSegments一起控制。- 闭合路径需显式声明:
computeFrenetFrames依据isCatmullRomCurve3 ? closed : false判断路径是否闭合(ExtrudeGeometry.js),非CatmullRomCurve3的路径类型需自行保证语义。 - UV 为世界坐标量纲:默认 UV 未归一化,纹理映射表现与模型单位尺度直接相关;需要铺贴一致纹理时可考虑自定义
UVGenerator。 - 法线已自动计算:构造完成后几何体即带有平滑后的顶点法线,直接用于
MeshStandardMaterial/MeshLambertMaterial即可获得正确光照;如希望硬边效果,可自行computeVertexNormals()或拆离顶点。
如需查阅类文档全文、源码与配套用例,可分别查看 ExtrudeGeometry 文档页、ExtrudeGeometry 源码、单元测试,并在 webgl_geometry_extrude_shapes 示例 与 webgl_geometry_shapes 示例 中动手调整参数体验效果。
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