如何计算与视口对齐的多边形包围盒?
解决视口对齐的包围盒计算问题
核心思路是:先把多边形顶点转换到相机局部空间,在这个空间里计算轴对齐包围盒(因为相机局部空间的轴就是视口的轴),再把包围盒顶点转换回世界空间,这样得到的包围盒就会始终和视口对齐,不管相机怎么旋转。
具体步骤:
- 将多边形的每个顶点从世界空间转换到相机空间:使用相机的
matrixWorldInverse矩阵对顶点进行变换 - 在相机空间中计算x、y轴的极值,得到轴对齐包围盒的四个顶点
- 将相机空间的包围盒顶点转换回世界空间:使用相机的
matrixWorld矩阵逆变换 - 用转换后的顶点生成包围盒的几何体
下面是修改后的完整代码:
var camera, scene, renderer, geometry, material, line, axesHelper, boundingBoxGeometry, boundingBoxLine; const polygonPoints = [ { x: 10, y: 10, z: 0 }, { x: 15, y: 15, z: 0 }, { x: 20, y: 10, z: 0 }, { x: 25, y: 20, z: 0 }, { x: 15, y: 20, z: 0 }, { x: 10, y: 10, z: 0 }, ]; function getViewportAlignedBoundingBoxGeometry(geometry, camera) { const positions = geometry.attributes.position.array; const cameraSpacePoints = []; const tempVec = new THREE.Vector3(); // 1. 将所有顶点转换到相机空间 for (let i = 0; i < positions.length; i += 3) { tempVec.set(positions[i], positions[i+1], positions[i+2]); tempVec.applyMatrix4(camera.matrixWorldInverse); cameraSpacePoints.push(tempVec.clone()); } // 2. 在相机空间计算轴对齐包围盒的极值 let minX = Infinity, maxX = -Infinity; let minY = Infinity, maxY = -Infinity; cameraSpacePoints.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); }); // 3. 构造相机空间的包围盒顶点,再转换回世界空间 const cameraBoxPoints = [ new THREE.Vector3(minX, minY, 0), new THREE.Vector3(maxX, minY, 0), new THREE.Vector3(maxX, maxY, 0), new THREE.Vector3(minX, maxY, 0), new THREE.Vector3(minX, minY, 0), // 闭合线条 ]; const worldBoxPoints = cameraBoxPoints.map(point => { point.applyMatrix4(camera.matrixWorld); return point; }); return new THREE.BufferGeometry().setFromPoints(worldBoxPoints); } init(); animate(); function init() { scene = new THREE.Scene(); axesHelper = new THREE.AxesHelper(10); scene.add(axesHelper); var frustumSize = 50; var aspect = window.innerWidth / window.innerHeight; camera = new THREE.OrthographicCamera( frustumSize * aspect / -2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / -2, -1, 1 ); camera.rotation.z = 3 * Math.PI / 4; camera.position.set(15, 15); scene.add(camera); geometry = new THREE.BufferGeometry().setFromPoints(polygonPoints); // 替换成新的包围盒计算函数 boundingBoxGeometry = getViewportAlignedBoundingBoxGeometry(geometry, camera); material = new THREE.LineBasicMaterial({ color: 0xffffff }); line = new THREE.Line(geometry, material); scene.add(line); boundingBoxLine = new THREE.Line(boundingBoxGeometry, material); scene.add(boundingBoxLine); renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); } function animate() { requestAnimationFrame(animate); render(); } function render() { renderer.render(scene, camera); }
关键代码解释
tempVec.applyMatrix4(camera.matrixWorldInverse):把世界空间的顶点转换到相机局部空间,此时相机的X轴对应视口的水平方向,Y轴对应视口的垂直方向- 在相机空间计算极值后,得到的是和视口轴对齐的包围盒,再用
camera.matrixWorld把顶点转换回世界空间,这样渲染出来的包围盒就会跟着相机旋转,始终和视口保持对齐
内容的提问来源于stack exchange,提问作者voidmat
相关产品推荐
相关产品推荐

