THREE.JS如何更新透视相机的fov值并保持相机距离不变

4
我想在动画中修改相机的FOV值。不幸的是,一旦我实现了FOV值,我就会发现场景变小了。
所以我一直在想,FOV值和透视相机的距离位置之间的数学关系是什么?
这个想法是通过修改相机位置来保持相同的场景大小(而FOV值不断变化)。
非常感谢。
编辑1:
以下是一个示例,说明我的问题:当我实现相机的FOV值(从4到45)时,我的正方形与相机之间的距离会改变。如何防止这种情况?

        let W = window.innerWidth, H = window.innerHeight;
        
        let renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( W, H );

        document.body.appendChild( renderer.domElement );

        let camera = new THREE.PerspectiveCamera( 4, W/H, 1, 100 );
        let scene = new THREE.Scene();

        camera.position.set(0,0,14);
        camera.lookAt(0,0,0);

        let geo = new THREE.BoxGeometry(0.5, 0.5, 0.5);
        let mat = new THREE.MeshNormalMaterial();
        let mesh = new THREE.Mesh(geo, mat);
        mesh.rotation.set(0.2,0.4,-0.1);
        scene.add(mesh);

        renderer.render(scene, camera);

        let progress = {};
        progress.fov = 4;

        
            TweenMax.to(progress, 2,{
                fov:45,
                onUpdate:function(){
                    camera.lookAt(0,0,0);
                    camera.updateProjectionMatrix();
                    camera.fov = progress.fov;
                    
                    renderer.render(scene, camera);
                    
                },
                repeat:-1,
                ease:Power3.easeInOut
            });
            
        
body{margin:0;padding:0;overflow:hidden;background: #666;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.js"></script>

1个回答

10
透视投影描述了从针孔相机看到的世界中的3D点到视口中的2D点的映射。这意味着在视口上投影的对象会因其深度而变小。
视图空间中投影区域与Z坐标之间的关系是线性的。它取决于视场角和纵横比。
也请参阅THREE.js PerspectiveCamera focalLength off by a factor of two, inconsistent with FOV

在透视投影中,视野 fov_y 的 Z 距离和尺寸的关系为:

depht_s = Math.tan(fov_y/2.0 * Math.PI/180.0) * 2.0;

在视口上投影的对象的预期大小取决于视野和深度。这导致当视野改变时,三维对象永远不可能“看起来”相同。您可以定义一个特定距离(深度)的平面,并找到一个新的距离,使得该对象的投影在该深度下大小不会改变。当然,在这个特定距离之前和之后,对象的投影大小至少会略微改变。请参见如何在透视和正交相机之间切换,保持所需对象的大小如何从透视相机转置z位置到three.js中的正交相机
请查看插图。当物体靠近摄像机时,无法看到立方体的顶点,但当其远离时可以看到。

初始视野为4.0。因此,Z距离和大小之间的比率为:
let init_depht_s    = Math.tan(4.0/2.0 * Math.PI/180.0) * 2.0;

当视野动画时,Z距离和尺寸之间的当前比例为:

let current_depht_s = Math.tan(progress.fov/2.0 * Math.PI/180.0) * 2.0;

现在,您需要定义一个距离,该距离必须“保持大小”。到立方体中心的初始距离为14.0,因此我将选择此距离作为参考距离。该距离必须按比例init_depht_s / current_depht_s进行缩放,然后立方体的投影(恰好在此距离处)保持其大小:
camera.position.set(0, 0, 14 * init_depht_s / current_depht_s);

看看这个例子,它基于你的原始代码(我已经将近平面更改为0.1,否则立方体将被剪裁,因为最终距离低于1.0):

let W = window.innerWidth, H = window.innerHeight;
        
let renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( W, H );

document.body.appendChild( renderer.domElement );

let camera = new THREE.PerspectiveCamera( 4, W/H, 0.1, 100 );
let scene = new THREE.Scene();

camera.position.set(0,0,14);
camera.lookAt(0,0,0);

let geo = new THREE.BoxGeometry(0.5, 0.5, 0.5);
let mat = new THREE.MeshNormalMaterial();
let mesh = new THREE.Mesh(geo, mat);
mesh.rotation.set(0.2,0.4,-0.1);
scene.add(mesh);

renderer.render(scene, camera);

let progress = {};
progress.fov = 4;

TweenMax.to(progress, 2,{
    fov:45,
    onUpdate:function(){
        let init_depht_s    = Math.tan(4.0/2.0 * Math.PI/180.0) * 2.0;
        let current_depht_s = Math.tan(progress.fov/2.0 * Math.PI/180.0) * 2.0;

        camera.position.set(0, 0, 14 * init_depht_s / current_depht_s);
        camera.lookAt(0,0,0);
        camera.updateProjectionMatrix();
        camera.fov = progress.fov;
        
        renderer.render(scene, camera);
        
    },
    repeat:-1,
    ease:Power3.easeInOut
});
body{margin:0;padding:0;overflow:hidden;background: #666;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.js"></script>


它像魔法一样运行得很顺利。非常感谢你的解释和细节,我学到了很多。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接