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

如何计算与视口对齐的多边形包围盒?

解决视口对齐的包围盒计算问题

核心思路是:先把多边形顶点转换到相机局部空间,在这个空间里计算轴对齐包围盒(因为相机局部空间的轴就是视口的轴),再把包围盒顶点转换回世界空间,这样得到的包围盒就会始终和视口对齐,不管相机怎么旋转。

具体步骤:

  • 将多边形的每个顶点从世界空间转换到相机空间:使用相机的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 20:35:26