react-three-fiber中选中3D物体时,如何获取其在2D画布的包围盒投影?
在react-three-fiber中获取3D物体包围盒的2D投影并绘制选中框
完全可以实现这个需求,核心思路是将3D物体的世界空间包围盒顶点投影到2D屏幕坐标,再计算出这些点的边界矩形。以下是具体实现步骤和代码示例:
核心步骤
- 获取物体的世界空间包围盒:使用
THREE.Box3().setFromObject(object)可以直接得到包含物体(含子物体)的世界空间包围盒,自动考虑物体的缩放、旋转和位移。 - 投影顶点到屏幕坐标:利用相机的
projectVector()方法,将包围盒的8个顶点从3D世界空间转换为2D屏幕像素坐标,注意要适配画布的尺寸和像素比。 - 计算屏幕矩形边界:遍历所有投影后的顶点,找出最小/最大的x、y值,以此确定选中框的位置和大小。
- 渲染2D选中框:在画布上层添加绝对定位的HTML元素(如div),根据计算出的矩形参数设置样式即可。
代码实现
工具函数:计算屏幕包围盒
import * as THREE from 'three'; export function getScreenBoundingBox(object, camera, canvasSize) { // 获取物体的世界空间包围盒 const worldBox = new THREE.Box3().setFromObject(object); // 提取包围盒的8个顶点 const boxVertices = [ new THREE.Vector3(worldBox.min.x, worldBox.min.y, worldBox.min.z), new THREE.Vector3(worldBox.min.x, worldBox.min.y, worldBox.max.z), new THREE.Vector3(worldBox.min.x, worldBox.max.y, worldBox.min.z), new THREE.Vector3(worldBox.min.x, worldBox.max.y, worldBox.max.z), new THREE.Vector3(worldBox.max.x, worldBox.min.y, worldBox.min.z), new THREE.Vector3(worldBox.max.x, worldBox.min.y, worldBox.max.z), new THREE.Vector3(worldBox.max.x, worldBox.max.y, worldBox.min.z), new THREE.Vector3(worldBox.max.x, worldBox.max.y, worldBox.max.z), ]; // 将顶点投影到屏幕坐标 const screenPoints = boxVertices.map(vertex => { vertex.project(camera); // 转换为画布像素坐标(适配three.js与DOM的坐标系差异) return { x: (vertex.x * 0.5 + 0.5) * canvasSize.width, y: (-vertex.y * 0.5 + 0.5) * canvasSize.height, }; }); // 计算矩形的边界值 let minX = Infinity, maxX = -Infinity; let minY = Infinity, maxY = -Infinity; screenPoints.forEach(point => { minX = Math.min(minX, point.x); maxX = Math.max(maxX, point.x); minY = Math.min(minY, point.y); maxY = Math.max(maxY, point.y); }); return { x: minX, y: minY, width: maxX - minX, height: maxY - minY, }; }
在react-three-fiber组件中使用
import { useThree, useFrame } from '@react-three/fiber'; import { useState, useRef } from 'react'; import { getScreenBoundingBox } from './utils'; function InteractiveScene() { const { camera, size } = useThree(); const [selectedObject, setSelectedObject] = useState(null); const [selectionBox, setSelectionBox] = useState(null); const cubeRef = useRef(null); // 处理物体选中事件(这里以点击立方体为例) const handleObjectClick = () => { const target = cubeRef.current; setSelectedObject(target); updateSelectionBox(target); }; // 更新选中框位置和大小 const updateSelectionBox = (object) => { if (!object) return; const box = getScreenBoundingBox(object, camera, size); setSelectionBox(box); }; // 相机或物体变换时实时更新选中框 useFrame(() => { if (selectedObject) { updateSelectionBox(selectedObject); } }); return ( <> {/* 3D物体 */} <mesh ref={cubeRef} onClick={handleObjectClick}> <boxGeometry args={[2, 2, 2]} /> <meshStandardMaterial color="#42a5f5" /> </mesh> {/* 环境光 */} <ambientLight intensity={0.5} /> <directionalLight position={[5, 5, 5]} /> {/* 2D选中框 */} {selectionBox && ( <div style={{ position: 'absolute', left: `${selectionBox.x}px`, top: `${selectionBox.y}px`, width: `${selectionBox.width}px`, height: `${selectionBox.height}px`, border: '2px solid #ffeb3b', borderRadius: '4px', pointerEvents: 'none', zIndex: 10, }} /> )} </> ); }
注意事项
- 如果物体的几何体是动态生成或修改的,确保在更新后重新计算包围盒(
setFromObject会自动处理,但极端情况下可手动调用object.geometry.computeBoundingBox())。 - 若物体包含多个层级的子物体,
setFromObject会计算整个层级的整体包围盒,符合选中整个物体的需求。 - 屏幕坐标转换时要注意three.js的y轴方向与DOM相反,所以代码中对y值做了反转处理。
内容的提问来源于stack exchange,提问作者dmraptis
相关产品推荐
相关产品推荐

