THREE.JS обновляет значение fov перспективной камеры и сохраняет то же расстояние до камеры.

Я хотел бы изменить значение 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>


person Michaël    schedule 16.09.2019    source источник


Ответы (1)


Перспективная проекция описывает отображение трехмерных точек мира, видимых с камеры-обскуры, в двухмерные точки окна просмотра. Это означает, что объект, проецируемый на окно просмотра, становится меньше по глубине.
Отношение между проецируемой областью в пространстве просмотра и координатой Z пространства просмотра является линейным. Это зависит от угла поля зрения и соотношения сторон.
См. также THREE.js PerspectiveCamera focusLength уменьшен в два раза, что не соответствует FOV.

Соотношение между Z-расстоянием и размером для поля вида fov_y в перспективной проекции:

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

Проецируемый размер объекта на видовом экране зависит от поля зрения и глубины. Это приводит к тому, что трехмерный объект никогда не может «выглядеть» одинаково при изменении поля зрения. Вы можете определить плоскость на определенном расстоянии (глубине) и найти новое расстояние, чтобы проекция объекта не менялась в размере именно для этой глубины. Конечно, проецируемый размер объекта «до» и «позади» этого определенного расстояния будет (хотя бы немного) меняться. См. также Как переключаться между камерами Perspective и Orthographic, сохраняя размер желаемого объекта соответственно Перенести 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>

person Rabbid76    schedule 17.09.2019
comment
Оно работало завораживающе. Большое спасибо за ваши объяснения и за подробности, я многому научился. - person Michaël; 22.09.2019