three.js LineGeometry 详解:用 Line2 实现顶点链折线的宽线条渲染
LineGeometry 是 three.js addons 中位于 examples/jsm/lines/ 的一组"宽线条(fat line)"模块的核心几何类。它描述一条由连续顶点组成的折线(polyline),是 Line2 默认使用的几何载体,其文档与源码可见 docs/pages/LineGeometry.html.md 与 examples/jsm/lines/LineGeometry.js。读完本文你将掌握:LineGeometry 与底层 LineSegmentsGeometry 的继承关系与数据布局、四种填充数据方法(setFromPoints / setPositions / setColors / fromLine)的用法与源码实现原理,以及如何配合 Line2 + LineMaterial 在真实示例中渲染出任意线宽、支持顶点色与射线拾取的粗线条。
一、LineGeometry 是什么:从继承链看定位
LineGeometry 的继承链(文档头部明确给出)为:
InstancedBufferGeometry → LineSegmentsGeometry → LineGeometry
InstancedBufferGeometry:three.js 核心提供的实例化几何基类,同一套模板顶点可被重复实例化渲染;LineSegmentsGeometry:一组"线段对"(每一段由独立的起点、终点两个顶点定义),见 examples/jsm/lines/LineSegmentsGeometry.js;LineGeometry:在基类之上进一步抽象,把"折线顶点链"自动拆分成基类所需的"线段对",省去使用者手动复制共享顶点的工作。
一句话概括两者差异:
| 类 | 输入语义 | 分段数量 |
|---|---|---|
LineSegmentsGeometry |
一段一段互不相连的线段(每 6 个数值一组,起点 + 终点) | 顶点数 / 2 |
LineGeometry |
一条首尾相接的折线(每 3 个数值一个顶点) | 顶点数 − 1 |
LineGeometry 的核心价值在于输入的是连续的折线顶点,输出的是渲染所需的独立线段对——折线上相邻两段共享的拐点会被自动复制为两份(同时作为前一段的终点与后一段的起点),这正是 line width 着色器以"每段为一个实例、各自挤出线宽"方式绘制时所需的数据形态。
二、何时使用:与 Line2 / LineMaterial 的配合
LineGeometry 本身只是几何数据,渲染它需要配合 addons 中的另外两个模块:
- Line2:真正的宽线条对象,默认构造即携带
new LineGeometry()与一个随机颜色的LineMaterial(见 examples/jsm/lines/Line2.js 第 37 行默认参数); - LineMaterial:基于 ShaderMaterial 的线条材质,支持
linewidth、worldUnits、vertexColors、dashed、alphaToCoverage等属性(uniform 定义见 examples/jsm/lines/LineMaterial.js 的UniformsLib.line)。
一个最小可运行的组合(源码示意出自 LineGeometry 类注释与 examples/webgl_lines_fat.html 第 100–120 行):
import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
import { Line2 } from 'three/addons/lines/Line2.js';
const positions = [ - 10, 0, 0, 0, 5, 0, 10, 0, 0 ]; // 3 个顶点
const colors = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; // 每个顶点一个颜色
const geometry = new LineGeometry();
geometry.setPositions( positions );
geometry.setColors( colors );
const material = new LineMaterial( {
color: 0xffffff,
linewidth: 5, // worldUnits=false 时单位是像素,true 时是世界单位
vertexColors: true,
} );
const line = new Line2( geometry, material );
line.computeLineDistances(); // 虚线模式需要
scene.add( line );
需要注意:Line2 这套模块只适用于 WebGLRenderer。源码注释明确说明,当使用 WebGPURenderer 时应改从 lines/webgpu/Line2.js 导入对应实现(WebGPU 分支位于 examples/jsm/lines/webgpu)。此外 examples/webgpu_lines_fat.html 与 examples/webgpu_lines_fat_raycasting.html 展示了 WebGPU 渲染器下的等价用法。
三、导入方式
LineGeometry 属于 three.js addons(示例扩展),不包含在核心构建中,必须显式导入。实际路径为:
import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
(原文档示例中导入名误写为 LineLineGeometry2,正确导出名就是 LineGeometry,见 examples/jsm/lines/LineGeometry.js 第 155 行 export { LineGeometry };。)
仓库内所有真实使用点均按上述路径导入,例如 examples/webgl_lines_fat.html、examples/webgl_lines_fat_raycasting.html 与对应 webgpu 示例。
四、构造函数与类型标记
new LineGeometry()
无参构造。构造函数只做两件事(见源码第 27–41 行):
constructor() {
super();
this.isLineGeometry = true;
this.type = 'LineGeometry';
}
由于继承自 LineSegmentsGeometry,其构造函数已经初始化了渲染所需的模板顶点数据:一组覆盖一个单位宽矩形区域的 position 与 uv attribute,以及对应的索引(源码见 examples/jsm/lines/LineSegmentsGeometry.js 第 43–49 行),后续通过实例化 attribute 把每条线段"套用"到这些模板顶点上。
.isLineGeometry : boolean(只读)
类型测试标志,默认值为 true。运行时可用 geometry.isLineGeometry === true 判断对象是否为宽线折线几何。基类 LineSegmentsGeometry 还设置了 isLineSegmentsGeometry = true,可据此区分是"折线"还是"独立线段集合"。
五、填充数据的方法
LineGeometry 共公开四个可链式调用的方法(每个都返回 this,便于连续书写)。其中 setPositions、setColors、setFromPoints 覆盖或独立实现了数据转换逻辑,fromLine 则直接从已有的 THREE.Line 迁移几何。
setFromPoints( points : Array<Vector3|Vector2> ) : LineGeometry
最直观的入口:直接传入一个由 Vector3(或 Vector2)构成的点数组,代码示例(原文档即给出):
const points = [
new THREE.Vector3( - 10, 0, 0 ),
new THREE.Vector3( 0, 5, 0 ),
new THREE.Vector3( 10, 0, 0 ),
];
const geometry = new LineGeometry();
geometry.setFromPoints( points );
源码实现要点(第 112–135 行):
const length = points.length - 1;
const positions = new Float32Array( 6 * length );
for ( let i = 0; i < length; i ++ ) {
positions[ 6 * i ] = points[ i ].x;
positions[ 6 * i + 1 ] = points[ i ].y;
positions[ 6 * i + 2 ] = points[ i ].z || 0;
positions[ 6 * i + 3 ] = points[ i + 1 ].x;
positions[ 6 * i + 4 ] = points[ i + 1 ].y;
positions[ 6 * i + 5 ] = points[ i + 1 ].z || 0;
}
super.setPositions( positions );
逐条拆解:
- 对 N 个点生成 N − 1 个线段对,输出缓冲区大小为
6 * (N - 1)个 float; - 第 i 段复用
points[i]与points[i + 1],即相邻两段共享拐点,拐点在缓冲区中被写入两次; - 支持
Vector2:二维点没有z,读取结果为undefined,通过|| 0补零,因此 2D 数据会落在 z = 0 平面上。这是源码中直接可见的实现事实; - 数据最终由基类
setPositions以"实例化起点 + 实例化终点"的形式存储。
setPositions( array : Float32Array | Array ) : LineGeometry
接收扁平化的顶点坐标数组,每 3 个数值构成一个顶点 (x, y, z)。例如三个顶点写为:
geometry.setPositions( [ - 10, 0, 0, 0, 5, 0, 10, 0, 0 ] );
源码实现要点(第 50–73 行):先计算 length = array.length - 3,再分配 new Float32Array( 2 * length ),然后每轮迭代把连续两个顶点(当前顶点 i..i+2 与下一顶点 i+3..i+5)成对写入:
points[ 2 * i ] = array[ i ];
points[ 2 * i + 1 ] = array[ i + 1 ];
points[ 2 * i + 2 ] = array[ i + 2 ];
points[ 2 * i + 3 ] = array[ i + 3 ];
points[ 2 * i + 4 ] = array[ i + 4 ];
points[ 2 * i + 5 ] = array[ i + 5 ];
- 注释"converts [ x1,y1,z1, x2,y2,z2, ... ] to pairs format"直接点明用途:把折线顶点链改写为基类要求的"每 6 个数值 = 一对起点/终点"格式;
- 数组长度应为 3 的倍数(每个顶点 3 个分量);要形成至少一条可见线段,需要至少 2 个顶点(即长度 ≥ 6);
- 覆盖(Override)自 LineSegmentsGeometry#setPositions。
setColors( array : Float32Array | Array ) : LineGeometry
为几何设置逐顶点颜色,颜色通道顺序为 (r, g, b),且红色分量也走同一套"成对复制"逻辑:把 [r1,g1,b1, r2,g2,b2, ...] 转换为线段对所需的 (rgb rgb) 格式(源码第 81–104 行,逻辑与 setPositions 完全对称)。用法如第一节示例所示,长度应与 setPositions 传入的顶点数匹配(每个顶点 3 个颜色分量)。
geometry.setColors( [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ] ); // 与顶点一一对应的渐变
使用时需配合材质侧的 vertexColors: true(见第一节 LineMaterial 配置),否则颜色不会显示。覆盖自 LineSegmentsGeometry#setColors。
fromLine( line : Line ) : LineGeometry
从已有的 THREE.Line(或任意带 position attribute 的线条几何)迁移数据:
const line = new THREE.Line( someBufferGeometry, new THREE.LineBasicMaterial() );
const geometry = new LineGeometry().fromLine( line );
源码实现(第 143–153 行)只做一件事:
const geometry = line.geometry;
this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
- 直接读取源几何的
attributes.position.array并交给setPositions转换; - 注释 "assumes non-indexed" 是关键前提:源几何必须是非索引(无 index)、position 按每顶点顺序平铺的数据;基类
fromLineSegments(examples/jsm/lines/LineSegmentsGeometry.js 第 208–218 行)同样采用这一假设; - 目前实现只迁移位置,不迁移源颜色(源码注释"set colors, maybe"表明颜色迁移未实现)。
六、底层如何存储:实例化起点 / 终点机制
LineGeometry 自身不直接持有渲染数据,而是把转换好的"线段对"交给基类 LineSegmentsGeometry。理解底层存储有助于调试与性能分析。
在 examples/jsm/lines/LineSegmentsGeometry.js 的 setPositions(第 97–125 行)中:
- 输入数组被包装为一个
InstancedInterleavedBuffer,stride(步长)为 6、每个实例 1 组数据,即每 6 个 float 对应一条线段的完整几何信息; - 同一缓冲区被切分成两个交错 attribute:
instanceStart(偏移 0,每实例 3 个分量 = 线段起点)与instanceEnd(偏移 3,每实例 3 个分量 = 线段终点); this.instanceCount = this.attributes.instanceStart.count,把实例数量设为线段条数(折线顶点数 − 1);- 随后自动计算包围盒
computeBoundingBox()与包围球computeBoundingSphere()(前者取所有起点终点的并集 Box3,后者从包围盒中心求最大半径)。
setColors(第 134–154 行)对称地生成 instanceColorStart 与 instanceColorEnd 两个交错 attribute。这些 attribute 名正是 LineMaterial.js 的 GLSL 顶点着色器(第 40–44 行)中声明的 attribute vec3 instanceStart; attribute vec3 instanceEnd; 以及颜色实例 attribute——线段在顶点着色器中被逐实例挤出成带线宽的四边形,这就是"fat line"渲染管线的数据源头。LineMaterial 提供的 linewidth(线宽)、resolution(视口分辨率)与 worldUnits(是否按世界单位衰减)uniform 都服务于该挤出过程。
七、实战示例:从曲线采样到渲染再到拾取
仓库中 LineGeometry 最典型的实战用法体现在 lines_fat 系列示例中。
webgl_lines_fat:样条曲线上的宽线渐变
examples/webgl_lines_fat.html 的完整链路(第 80–120 行):
- 用
CatmullRomCurve3(由GeometryUtils.hilbert3D生成的希耳伯特三维曲线控制点)做细分采样,得到密集的折线顶点; - 采样循环里同时把
color.setHSL( t, 1.0, 0.5 )的 HSL 结果按采样进度 t 压入颜色数组; geometry.setPositions( positions )+geometry.setColors( colors )一次性写入;注意这里使用的是扁平数组版本而非setFromPoints——对于成千上万个采样点,扁平数组避免为每个点创建Vector3对象;- 交由
new Line2( geometry, matLine )渲染,matLine的linewidth: 5(默认按像素,worldUnits为 true 时按世界单位)、vertexColors: true、alphaToCoverage: true; - GUI 面板中还能实时切换
matLine.worldUnits与matLine.linewidth观察宽度变化,或与原生gl.LINE_STRIP渲染的THREE.Line对比(对比对象构造见同文件第 125–135 行)。
webgl_lines_fat_raycasting:宽线条的射线拾取
examples/webgl_lines_fat_raycasting.html 展示了如何对 Line2 做拾取:
const raycaster = new THREE.Raycaster();
raycaster.params.Line2 = {};
raycaster.params.Line2.threshold = 0;
随后该示例对同一条样条曲线分别用 LineGeometry(连续折线)与 LineSegmentsGeometry(独立线段)构建 Line2/LineSegments2 对象(第 159–187 行),演示 threshold 阈值下命中检测与"可视化命中范围"的半透明阈值线。这验证了 LineGeometry 与射线系统集成时无需额外配置几何本身,只需在 raycaster.params.Line2 上设置命中容差。
八、注意事项速查
- 只能用于 WebGLRenderer:
LineGeometry/Line2/LineMaterial这套 addon 面向 WebGL;WebGPU 场景使用 examples/jsm/lines/webgpu 下的对应实现。 - 数据必须对齐:
setPositions/setColors的扁平数组分别为每个顶点 3 个分量,折线至少需要两个顶点才产生线段;颜色数量要与顶点数量一致。 - fromLine 假定非索引几何:从
THREE.Line迁移前确认其几何没有使用 index,否则数据会错位。 - 方法可链式调用:四个数据方法均返回几何自身引用,可写作
geometry.setPositions(p).setColors(c)。 - 补帧虚线:使用虚线材质时记得对
Line2调用computeLineDistances()(见 examples/webgl_lines_fat.html 第 118 行)。
九、进一步阅读
- examples/jsm/lines/LineGeometry.js:本文主角的实现源码;
- examples/jsm/lines/LineSegmentsGeometry.js:基类与底层实例化缓冲区布局;
- examples/jsm/lines/Line2.js 与 examples/jsm/lines/LineMaterial.js:宽线条对象与材质;
- 配套 API 文档:docs/pages/Line2.html.md、docs/pages/LineSegmentsGeometry.html.md、docs/pages/LineMaterial.html.md;
- 可运行示例:examples/webgl_lines_fat.html(颜色渐变宽线)、examples/webgl_lines_fat_raycasting.html(射线拾取)、examples/webgl_lines_fat_wireframe.html(配合
WireframeGeometry2)及对应 WebGPU 版本。
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