Skip to content

6. 深入理解透视相机和视椎体

Published:

对于刚接触 Three.js 的同学来说,透视相机的视椎体确实是个比较难理解的概念。

这节我们把它可视化的在 3D 场景中展示出来,这样就容易理解了。

创建项目:

mkdir perspective-camera
cd perspective-camera
npm init -y

image.png

安装用到的 ts 类型:

npm install --save-dev @types/three

和之前一样,创建 index.html,引入 three.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <script type="importmap">
    {
        "imports": {
            "three": "https://esm.sh/three@0.174.0/build/three.module.js",
            "three/addons/": "https://esm.sh/three@0.174.0/examples/jsm/"
        }
    }
    </script>
    <script type="module" src="./index.js"></script>
</body>
</html>

创建 index.js

import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';

const scene = new THREE.Scene();

const axesHelper = new THREE.AxesHelper(200);
scene.add(axesHelper);

const width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
camera.position.set(200, 200, 200);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height)

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

render();

document.body.append(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);

创建 Scene、Camera、Renderer,还有 OrbitContols 轨道控制器。

跑一下:

npx live-server

image.png

image.png

要看视椎体的话,我们需要创建另一个相机,用这个相机来观察:

Three.js 提供了一个 CameraHelper 来画视椎体,传入 camera 就行:

image.png

我们单独创建一个 PerspectiveCamera,传入 CameraHelper:

image.png

把 AxesHelper 注释掉,不然比较乱。

const camera2 = new THREE.PerspectiveCamera(20, 16 / 9, 100, 300);
const cameraHelper = new THREE.CameraHelper(camera2);
scene.add(cameraHelper);

现在就能看到视椎体了:

image.png

这里先顺便说下 OrbitContols 的操作:

鼠标左键拖动可以上下左右旋转:

2025-03-29 09.31.47.gif

鼠标右键拖动可以平移:

2025-03-29 09.34.56.gif

鼠标滚轮可以缩放:

2025-03-29 09.35.54.gif

当然,你用触摸板会有和鼠标对应的操作。

然后来说回这个视椎体:

image.png

第一个参数是角度,第二个参数是宽高比,第三个是近裁截面的距离,第四个参数是远裁截面的距离。

image.png

这些参数是怎么影响这个视椎体的呢?

我们用 dat.gui 来可视化调试下:

image.png

image.png

import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
const camera2 = new THREE.PerspectiveCamera(20, 16 / 9, 100, 300);
let cameraHelper = new THREE.CameraHelper(camera2);
scene.add(cameraHelper);

const gui = new GUI();
function onChange() {
    camera2.updateProjectionMatrix();
    cameraHelper.update();
}
gui.add(camera2, 'fov', [30, 60, 10]).onChange(onChange);
gui.add(camera2, 'aspect', {
    '16/9': 16/9,
    '4/3': 4/3
}).onChange(onChange);
gui.add(camera2, 'near', 0, 300).onChange(onChange);
gui.add(camera2, 'far', 300, 800).onChange(onChange);

改了参数之后要调用 camera2 的 updateProjectionMatrix 方法来触发 camera 的更新,然后调用 cameraHelper.update 来更新 helper。

2025-03-29 11.16.31.gif

2025-03-29 11.17.46.gif

调节 near,far,可以看到可视范围也就是视椎体的变化。

然后调节下 fov 和 aspect:

2025-03-29 11.21.32.gif

2025-03-29 11.23.11.gif

分别是影响视椎体的角度和宽高比,可视化可以直观的看出来。

我们看到的 3D 世界就是这个视椎体的 near 和 far 之间的部分。

在网页上显示的话,宽高比一般都是设置网页的宽高比也就是 window.innerWidth / window.innerHeight

而 near 一般设置 0.1,默认值也是 0.1

image.png

image.png

near 默认值 0.1、far 默认值 2000,fov 默认值 50,aspect 默认值是 1

一般这个 aspect 是要改的。

near 设置成 0.1,也就是可视范围这么大:

image.png

那啥时候要把 near 设置一个比较大的值呢?

image.png

near 设置了比较大,那离摄像机很近的一些物体就被截掉看不到了。

比如下雪的时候,离摄像机特别近的雪花会很大,这时候我们不希望看到特别大的雪花,就把近裁截面距离设置的大一点,把特别近的一些物体裁截掉。

far 就比较容易理解了,如果 far 不够大,那物体会被裁截看不到了。

像这样:

2025-03-19 10.13.43.gif

超出视椎体的会被裁截。

所以我们经常会修改改 far 的范围,让它包含所有物体。

而 fov 角度影响的是能看的范围的多少,同时也会影响离物体的远近:

image.png

image.png

当你想物体离的远一点,除了调节相机的 position,不妨调大一些 fov 试试。

案例代码上传了小册仓库

总结

这节我们深入理解了透视相机的视椎体。

我们通过 CameraHelper 来把视椎体可视化的画出来,然后用 dat.gui 调节了 fov、aspect、near、far 4 个参数,看了视椎体的变化。

这就是透视相机的视椎体的 4 个参数,后面会经常用到,需要对它们有透彻的理解。

评论