👏

three.jsで太い線を描画する実験

2024/04/07に公開

three.jsで線を描画する場合はTHREE.LineTHREE.LineBasicMaterialを使いますが、linewidthを指定しても太さが変わりません。
代わりとなるTHREE.MeshLineはメンテされてないみたいです。

ではどうすれば良いかと調べた所、THREE.TubeGeometry が良さそうなので試してみました。
コードは以下の感じです。

const vertexList = [
  -1, 0, 0,
   1, 0.5, 0
];

// 通常の線
const lineGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(vertexList);
lineGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff, linewidth: 1 });
const line = new THREE.Line(lineGeometry, lineMaterial);
line.position.set(0, 0, 0.6);
scene.add(line);

// TubeGeometryの線
const tubularSegments = 2;
// 線の太さ
const radius = 0.02;
const radialSegments = 10;
const points: THREE.Vector3[] = [];
for (let i = 0; i < vertexList.length; i += 3) {
  points.push(new THREE.Vector3(vertexList[i], vertexList[i + 1], vertexList[i + 2]));
}
const path = new THREE.CatmullRomCurve3(points, true);
const geometry = new THREE.TubeGeometry(path, tubularSegments, radius, radialSegments);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);


青線:THREE.Line、赤線:THREE.TubeGeometry

デメリットは頂点数が増える事ですね。このコード例だと頂点数は99でした。
あとTransformControlsなどで移動した線の始点と終点の座標は以下で取れます。

const tubeGeometry = mesh.geometry as THREE.TubeGeometry;
console.log(tubeGeometry.parameters.path.getPoints(2));
// [{x: -1, y: 0, z: 0}, {x: 1, y: 0.5, z: 0}, {x: -1, y: 0, z: 0}]
// またはtubeGeometry.parameters.path.toJSON()

ただ、このコードだと始点と終点の2点のみの直線の場合しか使えません。
使い勝手が微妙なので以下のようにTHREE.Lineをpositionずらしで複数描画もありかも。

const vertices = new Float32Array([-1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, -1, 0, 1]);

// 通常の線
const sampleGeometry = new THREE.BufferGeometry();
sampleGeometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
const sampleLine = new THREE.LineSegments(sampleGeometry, new THREE.LineBasicMaterial({ color: 0x0000ff }));
sampleLine.position.z = 1.2;
scene.add(sampleLine);

// 太らせる線
const parentGeometry = new THREE.BufferGeometry();
parentGeometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
const parentLine = new THREE.LineSegments(parentGeometry, new THREE.LineBasicMaterial({ color: 0xff0000 }));
scene.add(parentLine);

// FIXME: この値は自分の環境に合わせて調整する
const positions = [0.001, 0.002, 0.003, -0.001, -0.002, -0.003];
for (let i = 0; i < positions.length; i++) {
  const childLine = parentLine.clone();
  childLine.position.set(positions[i], positions[i], positions[i]);
  parentLine.add(childLine);
}

2024/04/09追記
three.js exampleのlines_fatがありました。LineMaterialで太くできます。
positionについては、以下の頂点を持ってました(どのような線でも変わらない値?)

positionからは始点・終点の座標が取れなさそうですが、instanceStartの方なら取れそうです。

参考ページ:
three.jsのlinewidth効かへんメモ
fat lineのサンプル

Discussion