You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Three.js中为任意3D形状绘制覆盖线条?

在任意Three.js几何体/GLB模型上绘制覆盖线条

我需要在任意几何形状(包括立方体、球体等规则形状,或是地形这类不规则形状,甚至GLB模型)上绘制覆盖线条。目前我已经手动指定顶点实现了立方体上的线条效果,代码如下:

const boxGeometry = new THREE.BoxGeometry(1.5,1.5,1.5,2,2,2);
const material = new THREE.MeshBasicMaterial({
  color:0xffffff,
  wireframe:true
});
const mesh = new THREE.Mesh(boxGeometry,material);
scene.add(mesh);

const lineGeometry = new THREE.BufferGeometry();
const lineGeometryVertices = new Float32Array([
  0,-0,.75,
  .75,-0,.75,
  .75,-0,-0
]);

const lineBufferAttribute = new THREE.BufferAttribute(lineGeometryVertices,3);
lineGeometry.setAttribute("position",lineBufferAttribute);

const lineMaterial = new THREE.LineBasicMaterial({
  color:0xff0000,
  linewidth:3
})

const line = new THREE.Line(lineGeometry,lineMaterial);
scene.add(line);

这是我得到的结果,也是我期望实现的效果

现在的核心问题是:如何获取任意形状上所需的线条顶点,从而实现覆盖线条的绘制?


解决方案

一、交互式拾取模型表面点(适合自定义路径)

通过Raycaster实现鼠标点击拾取模型表面的点,收集这些点作为线条的顶点,适合手动绘制自定义路径:

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const linePoints = [];
// 预先创建线条对象
let line = new THREE.Line(new THREE.BufferGeometry(), new THREE.LineBasicMaterial({ color: 0xff0000 }));
scene.add(line);

window.addEventListener('click', (event) => {
  // 转换鼠标坐标为Three.js标准化设备坐标
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);
  // targetMesh是你要绘制线条的目标模型
  const intersects = raycaster.intersectObject(targetMesh);

  if (intersects.length > 0) {
    const point = intersects[0].point.clone();
    linePoints.push(point.x, point.y, point.z);
    updateLineGeometry(linePoints);
  }
});

function updateLineGeometry(pointsArray) {
  const newGeometry = new THREE.BufferGeometry();
  newGeometry.setAttribute('position', new THREE.Float32BufferAttribute(pointsArray, 3));
  // 释放旧几何体资源
  line.geometry.dispose();
  line.geometry = newGeometry;
}

二、提取模型边缘顶点(适合沿模型边缘绘制)

如果要沿模型的现有边缘绘制线条,可以直接提取几何体的索引数据,生成去重后的边顶点数组:

function extractEdgesFromGeometry(geometry) {
  // 转换为非索引几何体(兼容索引/非索引格式)
  const nonIndexedGeo = geometry.toNonIndexed();
  const vertices = nonIndexedGeo.attributes.position.array;
  const indices = nonIndexedGeo.index.array;
  const edgeSet = new Set();
  const lineVertices = [];

  // 遍历三角面的边,避免重复添加同一条边
  for (let i = 0; i < indices.length; i += 3) {
    const a = indices[i];
    const b = indices[i + 1];
    const c = indices[i + 2];
    addEdge(a, b);
    addEdge(b, c);
    addEdge(c, a);
  }

  function addEdge(v1, v2) {
    // 用排序后的顶点ID作为唯一标识,防止重复边
    const key = v1 < v2 ? `${v1},${v2}` : `${v2},${v1}`;
    if (!edgeSet.has(key)) {
      edgeSet.add(key);
      lineVertices.push(
        vertices[v1 * 3], vertices[v1 * 3 + 1], vertices[v1 * 3 + 2],
        vertices[v2 * 3], vertices[v2 * 3 + 1], vertices[v2 * 3 + 2]
      );
    }
  }

  return new Float32Array(lineVertices);
}

// 使用示例
const targetMesh = scene.getObjectByName('your-target-mesh');
const lineVertices = extractEdgesFromGeometry(targetMesh.geometry);
const lineGeometry = new THREE.BufferGeometry();
lineGeometry.setAttribute('position', new THREE.BufferAttribute(lineVertices, 3));
const line = new THREE.LineSegments(lineGeometry, new THREE.LineBasicMaterial({ color: 0xff0000 }));
scene.add(line);

三、在模型表面生成自定义曲线(适合复杂路径)

如果需要生成曲线类的路径,可以先定义2D/3D曲线,再通过射线检测将曲线映射到模型表面:

// 定义一条二次贝塞尔曲线
const curve = new THREE.QuadraticBezierCurve3(
  new THREE.Vector3(-2, 0, 0),
  new THREE.Vector3(0, 2, 0),
  new THREE.Vector3(2, 0, 0)
);
const curveSamplePoints = curve.getPoints(50); // 采样50个点
const linePoints = [];
const raycaster = new THREE.Raycaster();
const targetMesh = scene.getObjectByName('your-target-mesh');

curveSamplePoints.forEach((point) => {
  // 从曲线点向模型方向发射射线(这里假设模型在Y轴负方向,可根据实际调整)
  raycaster.set(point, new THREE.Vector3(0, -1, 0));
  const intersects = raycaster.intersectObject(targetMesh);
  if (intersects.length > 0) {
    const surfacePoint = intersects[0].point;
    linePoints.push(surfacePoint.x, surfacePoint.y, surfacePoint.z);
  }
});

// 创建线条
const lineGeometry = new THREE.BufferGeometry();
lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(linePoints, 3));
const line = new THREE.Line(lineGeometry, new THREE.LineBasicMaterial({ color: 0xff0000 }));
scene.add(line);

四、GLB模型的特殊处理

加载GLB模型后,需要遍历子网格处理每个几何体,注意转换压缩或索引式几何体:

const loader = new THREE.GLTFLoader();
loader.load('your-model.glb', (gltf) => {
  const model = gltf.scene;
  scene.add(model);

  model.traverse((child) => {
    if (child.isMesh) {
      // 提取当前子网格的边缘顶点
      const lineVertices = extractEdgesFromGeometry(child.geometry);
      const lineGeometry = new THREE.BufferGeometry();
      lineGeometry.setAttribute('position', new THREE.BufferAttribute(lineVertices, 3));
      const line = new THREE.LineSegments(lineGeometry, new THREE.LineBasicMaterial({ color: 0xff0000 }));
      // 让线条跟随模型位置旋转缩放
      line.position.copy(child.position);
      line.rotation.copy(child.rotation);
      line.scale.copy(child.scale);
      scene.add(line);
    }
  });
});

内容的提问来源于stack exchange,提问作者mockingbird

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 01:27:21