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>
关键修改点说明
- 新增状态变量:
highlightMesh用来追踪当前高亮的三角形,targetMesh保存加载后的STL模型,方便后续操作 - 优化射线检测:使用
intersectObject只检测目标模型,减少不必要的性能消耗 - 高亮三角形实现:
- 用
BufferGeometry构建顶点数据,性能比普通Geometry更好 - 计算法向量,让高亮三角形能正确响应场景中的光照
- 使用半透明红色材质,既显眼又不会完全遮挡原模型
- 用
- 资源清理:每次移除高亮三角形时手动释放几何体和材质的内存,避免内存泄漏
- 细节修正:修复了原代码中不完整的颜色值,调整容器高度确保占满视口
现在你把鼠标悬停在模型的面上时,就会在对应位置显示一个红色的半透明三角形啦!如果需要改成点击高亮,只需要把mousemove事件改回mousedown即可。
内容的提问来源于stack exchange,提问作者tjallo
相关产品推荐
相关产品推荐

