首页
/ three.js LineGeometry 详解:用 Line2 实现顶点链折线的宽线条渲染

three.js LineGeometry 详解:用 Line2 实现顶点链折线的宽线条渲染

2026-09-07 11:17:53作者:冯爽妲Honey

LineGeometry 是 three.js addons 中位于 examples/jsm/lines/ 的一组"宽线条(fat line)"模块的核心几何类。它描述一条由连续顶点组成的折线(polyline),是 Line2 默认使用的几何载体,其文档与源码可见 docs/pages/LineGeometry.html.mdexamples/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 的线条材质,支持 linewidthworldUnitsvertexColorsdashedalphaToCoverage 等属性(uniform 定义见 examples/jsm/lines/LineMaterial.jsUniformsLib.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.htmlexamples/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.htmlexamples/webgl_lines_fat_raycasting.html 与对应 webgpu 示例。

四、构造函数与类型标记

new LineGeometry()

无参构造。构造函数只做两件事(见源码第 27–41 行):

constructor() {
	super();
	this.isLineGeometry = true;
	this.type = 'LineGeometry';
}

由于继承自 LineSegmentsGeometry,其构造函数已经初始化了渲染所需的模板顶点数据:一组覆盖一个单位宽矩形区域的 positionuv attribute,以及对应的索引(源码见 examples/jsm/lines/LineSegmentsGeometry.js 第 43–49 行),后续通过实例化 attribute 把每条线段"套用"到这些模板顶点上。

.isLineGeometry : boolean(只读)

类型测试标志,默认值为 true。运行时可用 geometry.isLineGeometry === true 判断对象是否为宽线折线几何。基类 LineSegmentsGeometry 还设置了 isLineSegmentsGeometry = true,可据此区分是"折线"还是"独立线段集合"。

五、填充数据的方法

LineGeometry 共公开四个可链式调用的方法(每个都返回 this,便于连续书写)。其中 setPositionssetColorssetFromPoints 覆盖或独立实现了数据转换逻辑,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 按每顶点顺序平铺的数据;基类 fromLineSegmentsexamples/jsm/lines/LineSegmentsGeometry.js 第 208–218 行)同样采用这一假设;
  • 目前实现只迁移位置,不迁移源颜色(源码注释"set colors, maybe"表明颜色迁移未实现)。

六、底层如何存储:实例化起点 / 终点机制

LineGeometry 自身不直接持有渲染数据,而是把转换好的"线段对"交给基类 LineSegmentsGeometry。理解底层存储有助于调试与性能分析。

examples/jsm/lines/LineSegmentsGeometry.jssetPositions(第 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 行)对称地生成 instanceColorStartinstanceColorEnd 两个交错 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 行):

  1. CatmullRomCurve3(由 GeometryUtils.hilbert3D 生成的希耳伯特三维曲线控制点)做细分采样,得到密集的折线顶点;
  2. 采样循环里同时把 color.setHSL( t, 1.0, 0.5 ) 的 HSL 结果按采样进度 t 压入颜色数组;
  3. geometry.setPositions( positions ) + geometry.setColors( colors ) 一次性写入;注意这里使用的是扁平数组版本而非 setFromPoints——对于成千上万个采样点,扁平数组避免为每个点创建 Vector3 对象;
  4. 交由 new Line2( geometry, matLine ) 渲染,matLinelinewidth: 5(默认按像素,worldUnits 为 true 时按世界单位)、vertexColors: truealphaToCoverage: true
  5. GUI 面板中还能实时切换 matLine.worldUnitsmatLine.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 上设置命中容差。

八、注意事项速查

  • 只能用于 WebGLRendererLineGeometry/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 行)。

九、进一步阅读

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

项目优选

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