如何使相机适合对象

时间:2013-01-30 21:35:44

标签: javascript three.js

使用three.js我有以下内容。

  • 包含多个Object3D实例的场景
  • 几个预定义的摄像头Vector3位置
  • 如果屏幕调整大小,画布的动态宽度/高度
  • 用户可以选择一个对象(从上面)
  • 用户可以选择摄像机位置(从上方)

考虑到正在查看的对象和摄像机位置,他们选择如何计算最终摄像机位置以“最适合”屏幕上的对象?

如果相机位置在某些屏幕上“按原样”使用,则物体会在我的视口边缘流血,而其他物体则会显得较小。我相信可以将物体安装到相机平截头体上,但却找不到合适的物体。

7 个答案:

答案 0 :(得分:58)

我假设您正在使用透视相机。

您可以设置相机的位置,视野或两者。

以下计算对于作为多维数据集的对象是完全一样的,因此请根据对象的边界框进行思考,对齐以面向相机。

如果相机居中并正面观看立方体,请定义

dist = distance from the camera to the _closest face_ of the cube

height = height of the cube.

如果您按如下方式设置摄像机视野

fov = 2 * Math.atan( height / ( 2 * dist ) ) * ( 180 / Math.PI ); // in degrees

然后立方体高度将与可见高度匹配。

此时,您可以稍微抬起相机,或稍微增加视野。

如果视野是固定的,那么使用上面的等式来求解距离。


编辑:如果您希望多维数据集width与可见的宽度匹配,请让aspect为画布的宽高比(画布宽度除以画布高度),并像这样设置摄像机视野

fov = 2 * Math.atan( ( width / aspect ) / ( 2 * dist ) ) * ( 180 / Math.PI ); // in degrees

three.js r.69

答案 1 :(得分:16)

根据WestLangleys的回答,这里是如何用固定的相机视野计算距离:

dist = height / 2 / Math.tan(Math.PI * fov / 360);

答案 2 :(得分:13)

要计算放置相机以使对象适合屏幕的距离,您可以使用此公式(在Javascript中):

// Convert camera fov degrees to radians
var fov = camera.fov * ( Math.PI / 180 ); 

// Calculate the camera distance
var distance = Math.abs( objectSize / Math.sin( fov / 2 ) );

其中objectSize是对象的高度或宽度。对于立方体/球体对象,您可以使用高度或宽度。对于长度或宽度较大的非立方体/非球体对象,请使用var objectSize = Math.max( width, height )获取更大的值。

请注意,如果您的对象位置不在0, 0, 0,则需要调整相机位置以包含偏移量。

Here's a CodePen显示了这一点。相关部分:

var fov = cameraFov * ( Math.PI / 180 );
var objectSize = 0.6 + ( 0.5 * Math.sin( Date.now() * 0.001 ) );

var cameraPosition = new THREE.Vector3(
    0,
    sphereMesh.position.y + Math.abs( objectSize / Math.sin( fov / 2 ) ),
    0
);

您可以看到,如果您抓住窗口手柄并调整其大小,则球体仍占据屏幕高度的100%。此外,对象以正弦波方式(0.6 + ( 0.5 * Math.sin( Date.now() * 0.001 ) ))向上和向下缩放,以显示摄像机位置考虑对象的比例。

答案 3 :(得分:1)

来自user151496关于使用宽高比的建议,这似乎有效,尽管我只测试过几个不同的参数集。

var maxDim = Math.max(w, h);
var aspectRatio = w / h;        
var distance = maxDim/ 2 /  aspectRatio / Math.tan(Math.PI * fov / 360);

答案 4 :(得分:1)

我有一个相同的问题,但是我希望如果整个对象都比我的屏幕宽,则这些对象(总体上由Box3表示)可以在我的手机上旋转,因此我可以通过放大查看它尽可能靠近。

const objectSizes = bboxMap.getSize();
console.log('centerPoint', centerPoint, bboxMap, objectSizes, tileMap);

//setupIsometricOrthographicCamera(bboxMap);
//https://gamedev.stackexchange.com/questions/43588/how-to-rotate-camera-centered-around-the-cameras-position
//https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
//https://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
//          Top
//      +--------+
// Left | Camera | Right
//      +--------+
//         Bottom
// canvas.height/2 / disance = tan(fov); canvas.width/2 / disance = tan(fovLR);
// => canvas.width / canvas.height = tan(fovLR)/tan(fov);
// => tan(fovLR) = tan(fov) * aspectRatio;
//If rotating the camera around z-axis in local space by 90 degrees.
//        Left
//        +---+
// Bottom |   | Top
//        |   |
//        +---+
//        Right
// => tan(fovLR) = tan(fov) / aspectRatio;
const padding = 0, fov = 50;
let aspectRatio = canvas.width / canvas.height;
let tanFOV = Math.tan(Math.PI * fov / 360);
let viewWidth = padding + objectSizes.x, viewHeight = padding + objectSizes.y;
//The distances are proportional to the view's with or height
let distanceH = viewWidth / 2 / (tanFOV * aspectRatio);
let distanceV = viewHeight / 2 / tanFOV;
const camera = this.camera = new THREE.PerspectiveCamera(fov, aspectRatio, 0.1, 10000); //VIEW_ANGLE, ASPECT, NEAR, FAR
if (aspectRatio > 1 != viewWidth > viewHeight) {
    console.log('screen is more narrow than the objects to be viewed');
    // viewWidth / canvas.width => viewHeight / canvas.width
    // viewHeight / canvas.height => viewWidth / canvas.height;
    distanceH *= viewHeight / viewWidth;
    distanceV *= viewWidth / viewHeight;
    camera.rotateZ(Math.PI / 2);
}
camera.position.z = Math.max(distanceH, distanceV) + bboxMap.max.z;
//camera.lookAt(tileMap.position);

我使用手机在两个不同方向(横向和纵向)上测试了Box3的两个不同方面,效果很好。

参考

答案 5 :(得分:0)

尝试使用OrbitControls

    let padding = 48;
    let w = Math.max(objectLength, objectWidth) + padding;
    let h = objectHeight + padding;

    let fovX = camera.fov * (aspectX / aspectY);
    let fovY = camera.fov;

    let distanceX = (w / 2) / Math.tan(Math.PI * fovX / 360) + (w / 2);
    let distanceY = (h / 2) / Math.tan(Math.PI * fovY / 360) + (w / 2);

    let distance = Math.max(distanceX, distanceY);

答案 6 :(得分:0)

如果对象的边界球体适合,则假定它适合屏幕,我们减少了将球体适合相机视图的任务。

在给定的示例中,我们在更改相机旋转以使对象获得最佳视点的同时保持PerspectiveCamera.fov不变。沿.lookAt方向向量移动相机可实现缩放效果。

three.js zoom extents demo

在图片上您可以看到问题的定义: 给定边界球和camera.fov,找到L,使边界球接触摄像机的视锥平面。

three.js zoom all camera

以下是您计算球体到相机的所需距离的方法:

three.js zoom all camera

完整的解决方案:https://jsfiddle.net/mmalex/h7wzvbkt/

var renderer;
var camera;
var scene;
var orbit;
var object1;

function zoomExtents() {
  let vFoV = camera.getEffectiveFOV();
  let hFoV = camera.fov * camera.aspect;

  let FoV = Math.min(vFoV, hFoV);
  let FoV2 = FoV / 2;

  let dir = new THREE.Vector3();
  camera.getWorldDirection(dir);

  let bb = object1.geometry.boundingBox;
  let bs = object1.geometry.boundingSphere;
  let bsWorld = bs.center.clone();
  object1.localToWorld(bsWorld);

  let th = FoV2 * Math.PI / 180.0;
  let sina = Math.sin(th);
  let R = bs.radius;
  let FL = R / sina;

  let cameraDir = new THREE.Vector3();
  camera.getWorldDirection(cameraDir);

  let cameraOffs = cameraDir.clone();
  cameraOffs.multiplyScalar(-FL);
  let newCameraPos = bsWorld.clone().add(cameraOffs);

  camera.position.copy(newCameraPos);
  camera.lookAt(bsWorld);
  orbit.target.copy(bsWorld);

  orbit.update();
}

scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(54, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 15;
camera.position.y = 15;
camera.position.z = 15;
camera.lookAt(0, 0, 0);

renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0xfefefe));
document.body.appendChild(renderer.domElement);

orbit = new THREE.OrbitControls(camera, renderer.domElement);

// create light
{
  var spotLight = new THREE.SpotLight(0xffffff);
  spotLight.position.set(0, 100, 50);
  spotLight.castShadow = true;
  spotLight.shadow.mapSize.width = 1024;
  spotLight.shadow.mapSize.height = 1024;
  spotLight.shadow.camera.near = 500;
  spotLight.shadow.camera.far = 4000;
  spotLight.shadow.camera.fov = 30;
  scene.add(spotLight);
}

var root = new THREE.Object3D();
scene.add(root);

function CustomSinCurve(scale) {
  THREE.Curve.call(this);
  this.scale = (scale === undefined) ? 1 : scale;
}

CustomSinCurve.prototype = Object.create(THREE.Curve.prototype);
CustomSinCurve.prototype.constructor = CustomSinCurve;

CustomSinCurve.prototype.getPoint = function(t) {
  var tx = t * 3 - 1.5;
  var ty = Math.sin(2 * Math.PI * t);
  var tz = 0;

  return new THREE.Vector3(tx, ty, tz).multiplyScalar(this.scale);
};

var path = new CustomSinCurve(10);
var geometry = new THREE.TubeGeometry(path, 20, 2, 8, false);

var material = new THREE.MeshPhongMaterial({
  color: 0x20f910,
  transparent: true,
  opacity: 0.75
});

object1 = new THREE.Mesh(geometry, material);
object1.geometry.computeBoundingBox();
object1.position.x = 22.3;
object1.position.y = 0.2;
object1.position.z = -1.1;
object1.rotation.x = Math.PI / 3;
object1.rotation.z = Math.PI / 4;

root.add(object1);

object1.geometry.computeBoundingSphere();

var geometry = new THREE.SphereGeometry(object1.geometry.boundingSphere.radius, 32, 32);
var material = new THREE.MeshBasicMaterial({
  color: 0xffff00
});
material.transparent = true;
material.opacity = 0.35;
var sphere = new THREE.Mesh(geometry, material);
object1.add(sphere);

var size = 10;
var divisions = 10;
var gridHelper = new THREE.GridHelper(size, divisions);
scene.add(gridHelper);

var animate = function() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
};

animate();