WebGL 透视相机 正交相机

2,012 阅读1分钟

示意图

image.png image.png

两个API

const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
const camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 );

引擎里面两种相机

const cameraPer = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
const cameraOrt = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 );
camera = cameraPer;
controls.object = this.camera;

互转

  • 透视相机转正交相机
    • 相机位置到target的点,distince=this.perspectiveCameraWP.distanceTo(this.controls.target)
    • 透视矩阵的高度 distince/tan(π/2 - fov/2)*2
transPerspectPosToOrthographicZoom(screenHeight) {
    screenHeight = screenHeight || this.screenHeight;
    this.perspectiveCamera.getWorldPosition(this.perspectiveCameraWP);
    const distancePCamGroup = this.perspectiveCameraWP.distanceTo(this.controls.target);
    var vFOV = THREE.MathUtils.degToRad(this.perspectiveCamera.fov); // convert vertical fov to radians
    var viewHeight = 2 * Math.tan(vFOV / 2) * distancePCamGroup; // visible height
    return screenHeight / viewHeight;
}
  • window.resize 透视/正交都只会随着高度变化 不随着宽度变化

凡是关于相机的shader都要进行更新

  • outline
  • ssao

正交相机与其他端

  • 这个是获取实际的相机top/bottom/left/right的参数
function zoomToProjectionParam(orthoCamera){
    if(!orthoCamera.isOrthographicCamera) return;
    const dx = (orthoCamera.right - orthoCamera.left) / (2 * orthoCamera.zoom);
    const dy = (orthoCamera.top - orthoCamera.bottom) / (2 * orthoCamera.zoom);
    const cx = (orthoCamera.right + orthoCamera.left) / 2;
    const cy = (orthoCamera.top + orthoCamera.bottom) / 2;

    return {
        left: cx - dx,
        right: cx + dx,
        top: cy + dy,
        bottom: cy - dy
    }
}
  • 这里是screenHeight/2与真实相机bottom的比例
function projectionParamToZoomBottom(orthoCamera, param){
    let { bottom } = param;
    if(!orthoCamera.isOrthographicCamera) return;
    const cx = (orthoCamera.bottom + orthoCamera.top) / 2;
    const dx = cx - bottom;

    const zoom = (orthoCamera.top - orthoCamera.bottom) / (2 * dx);
    return zoom;
}

物体屏幕空间的中心

  • 计算中心
function computeBounding(obj3d){
    const boxH = new THREE.BoxHelper(obj3d, 0x000000);
    boxH.geometry.computeBoundingBox();

    const max = boxH.geometry.boundingBox.max;
    const min = boxH.geometry.boundingBox.min;
    return {
        max : max,
        min : min,
        mid : new THREE.Vector3(
            (max.x + min.x) / 2,
            (max.y + min.y) / 2,
            (max.z + min.z) / 2
        )
    }
}
  • 计算boundingbox和相机到物体中心点的距离
computeAutoCenterDistance(width, height, distanceFactor) {
        const boundingBox = computeBounding(this.componentGroup);
        let { max, min, mid } = boundingBox;

        const yLen = max.y - min.y;
        const xLen = max.x - min.x;
        const ht = (height - yLen) / height;
        const wt = (width - xLen) / width;

        // 仅依赖BoundingBox的自适应居中(Fov不能动态改变)
        distanceFactor = distanceFactor || this.cameraCenterFactor;
        let distance = max.distanceTo(min) * distanceFactor;

        let zoom = ht >= wt ? width / xLen : height / yLen;
        zoom *= this.cameraSpaceGap;

        return {
                boundingBox,
                distance,
                zoom //基本没用
        };
}
  • 设置相机(透视,透视转正交)的位置
setCameraToCenter(width, height) {
        let { boundingBox, distance } = this.computeAutoCenterDistance(width, height);
        let { max, min, mid } = boundingBox;

        // PerspectiveCamera
        let perspectiveCamera = this.view.perspectiveCamera;
        perspectiveCamera.position.set(mid.x, mid.y, mid.z + distance / this.cameraSpaceGap);
        perspectiveCamera.updateProjectionMatrix();
        this.controls.target.set(mid.x, mid.y, mid.z);
        this.controls.update();
        // OrthographicCamera
        this.view.syncOrthographicCameraTransform(height);

        return {
                boundingBox,
                distance
        };
}