如何使相机适合对象

IT技术 javascript three.js
2021-01-21 00:14:32

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

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

给定正在查看的对象和他们选择的相机位置,我如何计算最终相机位置以“最适合”屏幕上的对象?

如果在某些屏幕上“按原样”使用相机位置,则对象会溢出我的视口边缘,而其他对象则显得更小。我相信可以将物体安装到相机视锥体上,但一直找不到合适的东西。

6个回答

我假设您使用的是透视相机。

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

以下计算对于立方体对象是准确的,因此请考虑对象的边界框,对齐以面向相机。

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

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

三.js r.69

Three.js 中的边界框是轴对齐的——它是一个 AABB——并且与相机位置或方向无关。
2021-03-25 00:14:32
@WestLangley:我试过你的解决方案,它在某种程度上有效。就我而言,3D 对象的宽度和长度可以根据用户输入而改变。它是一个类似房子的结构,当用户输入 6m 时,对象的宽度变为 12。现在它可以正常工作,直到宽度和长度达到 25。之后,当我旋转对象时,相机进入 3D 对象内部。这是一个移动应用程序,所以我们已经阻止了手动放大和缩小的捏合和膨胀。所以用户卡住了。我试图制作一个小提琴,请参阅:jsfiddle.net/0z3z6y7w/86但是这个问题并没有发生在小提琴中。
2021-03-27 00:14:32
“如果相机居中并正面查看立方体” - 假设我们已将相机移动到边界框?如果您还不知道相机的lookAt 在哪里,如何为一组面向相机的点生成边界框?
2021-04-01 00:14:32
@WestLangley 你是怎么得到的dist“立方体的最近面”是什么意思?
2021-04-04 00:14:32

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

dist = height / 2 / Math.tan(Math.PI * fov / 360);
谢谢你的例子。我仔细检查了计算,它们应该是正确的,但由于某种原因它离立方体太近了,我不知道为什么:(
2021-03-15 00:14:32
您能否分享您的解决方案并告诉我们出了什么问题?
2021-03-20 00:14:32
这个公式确实不正确,是对上述公式的错误求解
2021-03-21 00:14:32
我不确定它在哪里错了,但它是错的。它离立方体太近了
2021-03-23 00:14:32
也许它与纵横比有某种关系?它不存在于等式中
2021-03-23 00:14:32

要计算将相机放置多远以适合屏幕的对象,您可以使用以下公式(在 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,则需要调整相机位置以包括偏移。

这是一个 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 ) ))放大和缩小,以显示相机位置考虑了对象的比例。

在Trig funcion必须tan的,而不是sin像这样:Math.abs( objectSize / Math.tan( fov / 2 ) );
2021-04-04 00:14:32
有什么想法可以反过来做吗?我有类似第一人称射击游戏的控件,我想在相机面对的当前视图中定位一个新对象......
2021-04-11 00:14:32

假设该对象适合屏幕,如果它的边界球适合,我们将任务减少到将球适合相机视图。

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

三.js 缩放范围演示

在图片上你可以看到问题的定义: 给定边界球和camera.fov,找到L,使边界球接触相机的平截头体平面。

三.js 变焦所有相机

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

三.js 变焦所有相机

完整解决方案: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();
我发现这非常有用,所以感谢小提琴和详细的答案。如果目标对象未定位在 0,0,0 处,或者您不希望轨道控件以网格几何体的中心为目标,则需要进行一些调整。我还增加了网格高度的 z 偏移,这将相机拉回恰到好处的比例,与对象的几何形状成比例,至少对我而言是这样。
2021-04-10 00:14:32

试试这个 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);