Skip to content

Three.js - 绘制线条

在探讨了 Three.js 中的各种材质之后,让我们深入了解专门用于绘制线条的材质。线条是创建线框、轮廓、路径以及 3D 场景中各种几何图案的基础。

在 Three.js 中绘制线条的核心涉及两个主要组件:定义线条路径的几何体和定义其外观的材质。

THREE.BufferGeometry 是在 Three.js 中定义任何自定义形状的现代、高性能方式。要创建线条,您需要向它提供一系列点或顶点,线条将按顺序连接这些点。

import * as THREE from 'three';
// 1. 定义线条的点(顶点)
const points = [];
points.push(new THREE.Vector3(-10, 0, 0)); // 起点
points.push(new THREE.Vector3(0, 10, 0)); // 中间点
points.push(new THREE.Vector3(10, 0, 0)); // 终点
// 2. 从点创建 BufferGeometry
const geometry = new THREE.BufferGeometry().setFromPoints(points);

在这里,THREE.Vector3(x, y, z) 创建一个 3D 空间中的点。我们创建这些向量的数组,然后使用 .setFromPoints() 方法高效地构建我们的几何体。

注意: THREE.Line 在每对连续顶点之间绘制线段。它不会自动连接最后一个顶点到第一个顶点来闭合循环。对于闭合的线条,您必须在数组末尾再次添加第一个点。

Three.js 提供了两种主要的线条材质:

// 用于实心、连续的线条
const material = new THREE.LineBasicMaterial({ color: 0x00ff00 });
// 用于虚线
const dashedMaterial = new THREE.LineDashedMaterial({
color: 0xffea00,
linewidth: 1, // 注意:这个参数大部分情况下会被忽略,详见下文
scale: 1,
dashSize: 3, // 虚线段的长度
gapSize: 1, // 虚线段之间间隙的长度
});

一旦您有了几何体和材质,就可以使用 THREE.Line 将它们组合起来:

const line = new THREE.Line(geometry, material);
scene.add(line);
// 对于虚线,您必须计算线条距离
const dashedLine = new THREE.Line(geometry, dashedMaterial);
dashedLine.computeLineDistances();
scene.add(dashedLine);

一个常见的困惑点是 LineBasicMaterial 和 LineDashedMaterial 上的 linewidth 属性。由于现代 GPU 渲染线条的方式(特别是在底层的 WebGL API 中)存在限制,在大多数平台上,将 linewidth 设置为大于 1 的值将无效。

如果您需要渲染具有可控粗细的线条,您必须使用更高级的技术。一个流行的解决方案是使用 Three.js 示例中的 Line2,它通过细网格构建线条。这种方法以稍微复杂的设置和性能为代价,提供了更多的视觉控制。

以下示例演示了创建基本线条并使用现代 lil-gui 库实时操作其顶点。这展示了一种比每次更改都重新创建几何体更有效的更新几何体的方法。

此示例假设使用现代 JavaScript 开发环境。您可以使用 Vite 等工具快速设置项目。

  1. 初始化您的项目: npm create vite@latest my-threejs-app -- --template vanilla
  2. 安装依赖项: cd my-threejs-app 然后 npm install three lil-gui
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import GUI from 'lil-gui';
/**
* 基础设置
*/
// GUI
const gui = new GUI();
// 场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a1a);
// 相机
const sizes = {
width: window.innerWidth,
height: window.innerHeight
};
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100);
camera.position.set(0, 5, 20);
scene.add(camera);
// 渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
// 控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
/**
* 线条绘制
*/
// 1. 定义点
const points = [];
points.push(new THREE.Vector3(-10, 0, 0));
points.push(new THREE.Vector3(0, 10, -5));
points.push(new THREE.Vector3(10, 0, 0));
// 2. 创建几何体
const geometry = new THREE.BufferGeometry().setFromPoints(points);
// 3. 创建材质
const material = new THREE.LineBasicMaterial({ color: 0x00ff00 });
// 4. 创建线条
const line = new THREE.Line(geometry, material);
scene.add(line);
/**
* 用于实时更新的 GUI
*/
function updateLine() {
// 更高效:更新现有几何体的 position
line.geometry.setFromPoints(points);
}
points.forEach((point, i) => {
const folder = gui.addFolder(`Point ${i + 1}`);
folder.add(point, 'x', -15, 15, 0.1).name('X').onChange(updateLine);
folder.add(point, 'y', -15, 15, 0.1).name('Y').onChange(updateLine);
folder.add(point, 'z', -15, 15, 0.1).name('Z').onChange(updateLine);
folder.open();
});
/**
* 响应式处理
*/
window.addEventListener('resize', () => {
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix();
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
/**
* 动画循环
*/
const tick = () => {
controls.update();
renderer.render(scene, camera);
window.requestAnimationFrame(tick);
};
tick();

要创建虚线,只需更换材质并记住在 Line 对象上调用 .computeLineDistances()。这个额外的步骤对于 Three.js 正确计算如何在线条长度上应用虚线和间隙是必要的。

// ... (设置与上文相同)
// 创建虚线材质
const dashedMaterial = new THREE.LineDashedMaterial({
color: 0xffea00,
dashSize: 1,
gapSize: 0.5
});
// 创建 Line 对象
const dashedLine = new THREE.Line(geometry, dashedMaterial);
// 虚线的关键步骤
dashedLine.computeLineDistances();
scene.add(dashedLine);

绘制线条是一项多功能的技能。您可以使用它来:

  • 可视化数据: 在 3D 空间中创建折线图。
  • 描绘对象轮廓: 绘制复杂模型的线框,用于艺术或调试目的。
  • 创建辅助对象: 在场景中绘制网格、坐标轴或其他辅助线。
  • 说明路径: 显示动画中对象的轨迹。

尝试添加更多点、创建闭合循环,甚至在 tick 函数中随着时间动画点的position,以实现动态效果。