Three.js对象适配相机
本文介绍在 three.js 中将对象适配相机视野的解决方案。 换句话说,将相机在 z 轴上拉回多远,才能在不裁剪的情况下尽可能大地显示对象。
1、问题陈述
最初我一直在使用 viewstl 在这个博客上显示 3D (STL) 模型。
但我对 auto_resize
功能不满意。 以至于我为 viewstl 提出了一个问题。 但是提供的解决方案(使用手动缩放)也并不完全适合我 。
因为在我研究这个问题的过程中,我偶然发现了 Anthony Biondo 的Simple STL Viewer 帖子,所以我决定也推出我自己的——更简单的——基于Anthony的 STL 查看器。 毕竟,three.js 拥有所有需要的组件。 所以我只需要一点胶水。
在进一步研究“fit camera to object”问题时,我发现了一些关于 three.js 的讨论,提供了一些解决方案。 但我发现的解决方案都没有真正适合我。
啊,我想我终于攻克了了难题。 :)
2、解决方案
我发现的大多数算法基本上都存在两个问题:
- 他们忽略纵横比不同于 1.0 的相机的垂直 FOV 和水平 FOV 之间的差异
- 他们取对象的三个维度的最大值并基于该维度进行计算
由于我的简化 STL 查看器有一个始终以 (0, 0, 0) 为中心的单一模型,我基本上可以采用一种快捷方式来计算最佳拟合:
相机的 z 距离是边界框 z 大小的一半,然后是以下较大者:
- 基于水平 FOV 和边界框 x 大小的距离
- 基于垂直 FOV 和边界框 y 大小的距离
为什么这样行得通? 因为我需要让边界框 (BB) 的整个正面尺寸可见。 所以 z 尺寸的一半让我从中心到 BB 的前面,然后利用一点三角函数让我在给定的纵横比和侧面尺寸的情况下得到正确的距离。
这有点难以解释,但更容易画出来:
我还尝试在代码中记录它:
const fitCameraToCenteredObject = function (camera, object, offset, orbitControls ) {
const boundingBox = new THREE.Box3();
boundingBox.setFromObject( object );
var middle = new THREE.Vector3();
var size = new THREE.Vector3();
boundingBox.getSize(size);
// figure out how to fit the box in the view:
// 1. figure out horizontal FOV (on non-1.0 aspects)
// 2. figure out distance from the object in X and Y planes
// 3. select the max distance (to fit both sides in)
//
// The reason is as follows:
//
// Imagine a bounding box (BB) is centered at (0,0,0).
// Camera has vertical FOV (camera.fov) and horizontal FOV
// (camera.fov scaled by aspect, see fovh below)
//
// Therefore if you want to put the entire object into the field of view,
// you have to compute the distance as: z/2 (half of Z size of the BB
// protruding towards us) plus for both X and Y size of BB you have to
// figure out the distance created by the appropriate FOV.
//
// The FOV is always a triangle:
//
// (size/2)
// +--------+
// | /
// | /
// | /
// | F° /
// | /
// | /
// | /
// |/
//
// F° is half of respective FOV, so to compute the distance (the length
// of the straight line) one has to: `size/2 / Math.tan(F)`.
//
// FTR, from https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
// the camera.fov is the vertical FOV.
const fov = camera.fov * ( Math.PI / 180 );
const fovh = 2*Math.atan(Math.tan(fov/2) * camera.aspect);
let dx = size.z / 2 + Math.abs( size.x / 2 / Math.tan( fovh / 2 ) );
let dy = size.z / 2 + Math.abs( size.y / 2 / Math.tan( fov / 2 ) );
let cameraZ = Math.max(dx, dy);
// offset the camera, if desired (to avoid filling the whole canvas)
if( offset !== undefined && offset !== 0 ) cameraZ *= offset;
camera.position.set( 0, 0, cameraZ );
// set the far plane of the camera so that it easily encompasses the whole object
const minZ = boundingBox.min.z;
const cameraToFarEdge = ( minZ < 0 ) ? -minZ + cameraZ : cameraZ - minZ;
camera.far = cameraToFarEdge * 3;
camera.updateProjectionMatrix();
if ( orbitControls !== undefined ) {
// set camera to rotate around the center
orbitControls.target = new THREE.Vector3(0, 0, 0);
// prevent camera from zooming out far enough to create far plane cutoff
orbitControls.maxDistance = cameraToFarEdge * 2;
}
};
3、演示
检查同一模型在 4 种不同旋转中的自动定位(在对象周围绘制边界框)3:
第一个:
第二个:
第三个:
第四个:
原文连接:Cracking the three.js object fitting (to camera) nut
BimAnt翻译整理,转载请标明出处