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

Vue.js+Three.js:基于选中面顶点渲染替换三角形

解决Three.js中替换射线检测命中面为不同颜色三角形的问题

嘿,我来帮你搞定这个需求!你已经成功获取到了射线命中面的三个顶点,接下来只需要创建一个独立的三角形网格,用不同颜色的材质渲染它,然后把它放到和原面完全一致的位置就行。下面是具体的实现步骤和修改后的代码:

核心思路

  • 每次检测到新的命中面时,先移除之前的高亮三角形(避免场景里堆一堆冗余对象)
  • 用获取到的三个顶点创建新的三角形几何体
  • 给这个三角形设置一个显眼的材质(比如半透明红色)
  • 把这个三角形作为原STL模型的子对象,这样它会自动继承原模型的旋转、位置等变换,不用手动转换坐标

修改后的完整代码

<template>
  <div id="scene-container" ref="sceneContainer" class="scene-container"></div>
</template>

<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";

export default {
  name: "HelloWorld",
  data() {
    return {
      container: null,
      scene: null,
      camera: null,
      controls: null,
      renderer: null,
      mouse: null,
      raycaster: null,
      objName: "testobj",
      highlightMesh: null, // 保存当前高亮的三角形网格
      targetMesh: null // 保存加载后的STL模型对象
    };
  },
  methods: {
    init() {
      // 初始化容器
      this.container = this.$refs.sceneContainer;
      // 初始化射线检测工具
      this.raycaster = new THREE.Raycaster();
      this.mouse = new THREE.Vector2();

      // 鼠标点击/移动事件(这里改成mousemove实现悬停高亮,要点击高亮改回mousedown即可)
      const onMouseMove = (event) => {
        event.preventDefault();
        // 把鼠标坐标转换为Three.js需要的标准化设备坐标(-1到+1)
        this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      };
      window.addEventListener("mousemove", onMouseMove, false);

      // 初始化相机
      const fov = 60;
      const aspect = this.container.clientWidth / this.container.clientHeight;
      const camera = new THREE.PerspectiveCamera(fov, aspect);
      camera.position.set(0, 20, 75);
      this.camera = camera;

      // 初始化场景
      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0xdddddd);

      // 添加灯光
      const ambientLight = new THREE.HemisphereLight(0xffffff, 0x222222, 1);
      const mainLight = new THREE.DirectionalLight(0xffffff, 4.0);
      mainLight.position.set(10, 10, 10);
      this.scene.add(ambientLight, mainLight);

      let hlight = new THREE.AmbientLight(0xffffff, 1.3);
      this.scene.add(hlight);

      let light = new THREE.PointLight(0xffffff, 1, 1000);
      light.position.set(0, 300, 500);
      this.scene.add(light);

      // 初始化轨道控制器
      this.controls = new OrbitControls(this.camera, this.container);

      // 初始化渲染器
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.gammaFactor = 2.2;
      this.renderer.outputEncoding = THREE.sRGBEncoding;
      this.renderer.physicallyCorrectLights = true;
      document.getElementById("scene-container").appendChild(this.renderer.domElement);

      // 窗口 resize 处理
      const me = this;
      window.addEventListener("resize", onWindowResize, false);
      function onWindowResize() {
        me.camera.aspect = window.innerWidth / window.innerHeight;
        me.camera.updateProjectionMatrix();
        me.renderer.setSize(window.innerWidth, window.innerHeight);
      }

      // 加载STL模型
      let loader = new STLLoader();
      loader.load("/three-assets/RobotExpressive.stl", function (geometry) {
        let material = new THREE.MeshLambertMaterial({
          color: 0x131313, // 修正原代码的不完整颜色值
          wireframe: false,
          transparent: false,
          vertexColors: false,
        });
        const mesh = new THREE.Mesh(geometry, material);
        mesh.rotation.x = -0.5 * Math.PI;
        mesh.position.set(0, 0, 0);
        mesh.name = me.objName;
        me.targetMesh = mesh; // 保存模型对象方便后续操作
        me.scene.add(mesh);
      });

      // 启动渲染循环
      this.renderer.setAnimationLoop(() => {
        this.render();
      });
    },
    render() {
      this.raycaster.setFromCamera(this.mouse, this.camera);
      // 只检测我们的目标模型,避免检测灯光等无关对象
      const intersects = this.targetMesh ? this.raycaster.intersectObject(this.targetMesh) : [];

      if (intersects.length > 0) {
        const intersection = intersects[0];
        const positionAttribute = intersection.object.geometry.attributes.position;
        
        // 获取命中面的三个局部顶点坐标
        const aVertex = new THREE.Vector3(
          positionAttribute.getX(intersection.face.a),
          positionAttribute.getY(intersection.face.a),
          positionAttribute.getZ(intersection.face.a)
        );
        const bVertex = new THREE.Vector3(
          positionAttribute.getX(intersection.face.b),
          positionAttribute.getY(intersection.face.b),
          positionAttribute.getZ(intersection.face.b)
        );
        const cVertex = new THREE.Vector3(
          positionAttribute.getX(intersection.face.c),
          positionAttribute.getY(intersection.face.c),
          positionAttribute.getZ(intersection.face.c)
        );

        // 清理之前的高亮三角形,避免内存泄漏
        if (this.highlightMesh) {
          this.targetMesh.remove(this.highlightMesh);
          this.highlightMesh.geometry.dispose();
          this.highlightMesh.material.dispose();
          this.highlightMesh = null;
        }

        // 创建高亮三角形的几何体
        const highlightGeometry = new THREE.BufferGeometry();
        const vertices = new Float32Array([
          aVertex.x, aVertex.y, aVertex.z,
          bVertex.x, bVertex.y, bVertex.z,
          cVertex.x, cVertex.y, cVertex.z
        ]);
        highlightGeometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
        highlightGeometry.computeVertexNormals(); // 计算法向量,让三角形正确受光照影响

        // 创建高亮材质(半透明红色,双面可见)
        const highlightMaterial = new THREE.MeshLambertMaterial({
          color: 0xff0000,
          transparent: true,
          opacity: 0.8,
          side: THREE.DoubleSide
        });

        // 创建高亮网格并添加到原模型中(继承原模型的变换)
        this.highlightMesh = new THREE.Mesh(highlightGeometry, highlightMaterial);
        this.targetMesh.add(this.highlightMesh);
      } else {
        // 没有命中时移除高亮三角形
        if (this.highlightMesh) {
          this.targetMesh.remove(this.highlightMesh);
          this.highlightMesh.geometry.dispose();
          this.highlightMesh.material.dispose();
          this.highlightMesh = null;
        }
      }

      this.renderer.render(this.scene, this.camera);
    },
  },
  mounted() {
    this.init();
  },
};
</script>

<style scoped>
#scene-container {
  height: 99.8vh;
  width: 100%;
}
</style>

关键修改点说明

  1. 新增状态变量:highlightMesh用来追踪当前高亮的三角形,targetMesh保存加载后的STL模型,方便后续操作
  2. 优化射线检测:使用intersectObject只检测目标模型,减少不必要的性能消耗
  3. 高亮三角形实现:
    • 用BufferGeometry构建顶点数据,性能比普通Geometry更好
    • 计算法向量,让高亮三角形能正确响应场景中的光照
    • 使用半透明红色材质,既显眼又不会完全遮挡原模型
  4. 资源清理:每次移除高亮三角形时手动释放几何体和材质的内存,避免内存泄漏
  5. 细节修正:修复了原代码中不完整的颜色值,调整容器高度确保占满视口

现在你把鼠标悬停在模型的面上时,就会在对应位置显示一个红色的半透明三角形啦!如果需要改成点击高亮,只需要把mousemove事件改回mousedown即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 22:17:54