ThreeJS学习笔记 - 3.相机

279 阅读16分钟

相机概念

Three.js 中的相机是从 Camera​ 类派生出来的,Camera​ 决定了场景中哪个部分被渲染器“看到”,从而绘制到屏幕上。它定义了视角、视野、投影方式等参数,可以理解为场景的观察者。Three.js 提供了多种类型的相机,例如透视相机(PerspectiveCamera​)和正交相机(OrthographicCamera​)。

功能和特性:

  • 视角与投影:PerspectiveCamera​ 模拟人眼的视觉效果,有远近透视关系;OrthographicCamera​ 则没有透视效果,适合技术图纸等用途。
  • 位置和方向:相机的位置和朝向决定了它从何处观察场景以及观察方向。
  • 可视范围:相机的视锥(view frustum)定义了可以看到的场景范围,例如近裁剪面和远裁剪面之间的距离。

相机有两种常用类型:

  • 透视相机(PerspectiveCamera) :模拟人眼或摄影机的视觉效果,远近物体有不同的大小表现。

    常用于游戏和模拟现实世界的场景

  • 正交相机(OrthographicCamera) :没有透视效果,物体无论距离远近,大小都是一样的。

    常用于需要精确几何形状的情况,比如建筑图纸、2D 游戏等

还有三种不常用的类型:

  • 立方相机(CubeCamera) :它会从场景的六个方向(前、后、左、右、上、下)拍摄六张图片,然后将这些图片组合成一个立方体贴图

    常用于创建动态反射和折射效果,例如水面反射、金属表面反射等。

  • 数组相机(ArrayCamera) :数组相机包含多个子相机,可以同时渲染多个视图。每个子相机都有自己的视角和视图矩阵。

    常用于多视图渲染,例如虚拟现实(VR)和增强现实(AR)应用中的双眼视图渲染。

  • 立体相机(StereoCamera) :立体相机用于立体渲染,通过两个相机模拟人眼的视差效果,生成左右眼的图像

    常用于立体显示设备,例如3D眼镜和VR头显,提供立体视觉效果。


使用相机

既然知道有上面几种相机,那么自然会怎么使用

透视相机

透视相机 PerspectiveCamera​ 模拟人眼的透视效果,它的构造函数如下:

const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  • fov(Field of View, 视角):摄像机的垂直视角,以度数为单位。视角越大,相机所能看到的场景越多,物体显得越小;视角越小,相机看到的范围越小,物体显得越大。
  • aspect​(纵横比):相机视口的宽高比,通常设置为 canvas​ 的宽度除以高度。确保图像不变形。
  • near​(近裁剪面):相机前方最近可以看到的距离,任何比这个距离更近的物体将不会被渲染。
  • far​(远裁剪面):相机可以看到的最远距离,任何比这个距离更远的物体将不会被渲染。

透视相机的可视范围与投影

透视相机是基于投影矩阵的,它将 3D 世界的物体转换为 2D 图像。

视野的范围是一个锥形区域,该区域被称为视锥体,锥顶在相机位置处,随着距离的增加锥体扩展。近裁剪面和远裁剪面定义了这个锥体的起点和终点,视野角度(FOV)定义了锥体的张角。

只有在视锥体之内的物体才会渲染出来,视锥体范围之外的物体不会显示在Canvas画布上。

image-20240902113313-ngv74al.png

典型的透视相机设置

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  • 75 度的视野角
  • 纵横比为窗口宽高比
  • 最近可见距离为 0.1,最远为 1000,这意味着相机能看到 0.1 到 1000 单位之间的物体,超出这个范围的物体会被裁剪掉。

近裁剪面和远裁剪面

上面的代码中如果某个物体的一半比0.1还近那么只能看到0.1以外的半部分,如果比1000还远那么只能看到1000以内的半部分。

我们可以模拟一下如果超过了近裁剪面会发送什么

如下:

// 新建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 60, 1000);
camera.position.set(51, 51, 51);

// 添加一个cube到场景中
const geometry = new THREE.BoxGeometry(50, 50, 50);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

cube大小是50 * 50 * 50,默认在原点,

如果一个相机它的近裁剪面是60,而它的位置又是51 * 51 * 51,那么会看到以下效果

如果近裁剪面改成0.1,位置不变

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(51, 51, 51);

那么又变成了

透视相机参数随着Canvas画布尺寸的变化而变化

// 新建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(51, 51, 51);

// 重绘
const onWindowResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight; // 长宽比
  camera.updateProjectionMatrix(); // 更新相机投影矩阵
  renderer.value.setSize(window.innerWidth, window.innerHeight); // 重置渲染器尺寸
  renderer.value.render(scene, camera); // 重新渲染
};
window.addEventListener('resize', onWindowResize, false);

正交相机

正交相机 OrthographicCamera​ 与透视相机的不同之处在于它没有透视效果,物体距离相机的远近不会影响物体的大小。它的构造函数如下:

const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
  • left:视口的左边界。
  • right:视口的右边界。
  • top​:视口的上边界。
  • bottom​:视口的下边界。
  • near​:近裁剪面。
  • far​:远裁剪面。

正交相机的可视范围与投影

正投影相机的可视范围和透视相机的视锥体相似,只是形状不同。

image-20241010160219-55qth5r.png

典型的正交相机设置

const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.OrthographicCamera(-10 * aspect, 10 * aspect, 10, -10, 0.1, 1000);

在这里,左右和上下的边界是根据窗口的宽高比进行调整的。

正交相机参数随着Canvas画布尺寸的变化而变化

// 新建正交相机
const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.OrthographicCamera(
  -aspect * 50, aspect * 50, 50, -50, 0.1, 1000
);
camera.position.set(51, 51, 51);

// 新建渲染器
const renderer = ref<THREE.WebGLRenderer | null>(null); // 渲染器

const onWindowResize = () => {
  const aspect = window.innerWidth / window.innerHeight; // 计算窗口宽高比
  camera.left = -aspect * 50; // 设置相机视锥体的左侧面
  camera.right = aspect * 50; // 设置相机视锥体的右侧面
  camera.top = 50; // 设置相机视锥体的上侧面
  camera.bottom = -50; // 设置相机视锥体的下侧面
  camera.updateProjectionMatrix(); // 更新相机投影矩阵
  renderer.value.setSize(window.innerWidth, window.innerHeight); // 重置渲染器尺寸
  renderer.value.render(scene, camera); // 重新渲染
};
window.addEventListener('resize', onWindowResize, false);

相机常用属性与方法

常用属性

  • position​:THREE.Vector3

    相机在三维空间中的位置,默认值为 (0, 0, 0)

    常用方法

    • set(x, y, z)​:设置位置
    • copy(vector)​:复制另一个向量的值
    • add(vector)​:向当前位置添加一个向量
    • sub(vector)​:从当前位置减去一个向量
  • rotation​:THREE.Euler

    相机的旋转角度,使用欧拉角表示,默认值为 (0, 0, 0)

    常用方法

    • set(x, y, z, order)​:设置旋转角度和顺序
    • copy(euler)​:复制另一个欧拉角的值
  • fov​:Number

    视场角,表示相机视野的垂直角度(仅适用于透视相机),默认值为 50

  • aspect​:Number

    相机视图的宽高比,默认值为 1

  • near​ 和 far​:Number

    相机的近剪裁面和远剪裁面,默认值各自为 0.1​ 和 2000

  • zoom​:Number

    相机的缩放级别(仅适用于正交相机),默认值为 1

  • up​:THREE.Vector3

    相机的“上”方向,可以通过改变 up​ 向量来调整相机的顶部朝向,默认值为 (0, 1, 0)​,即沿着 y 轴向上

    常用方法

    • set(x, y, z)​:设置上方向

常用方法

  1. lookAt(vector)

    • 描述:使相机朝向某个目标点
    • 参数:vector​(类型:THREE.Vector3​)目标点的坐标
  2. updateProjectionMatrix()

    • 描述:更新相机的投影矩阵。当相机的属性(如 fov​、aspect​、near​、far​ 等)发生变化时,需要调用此方法
  3. getWorldDirection(target)

    • 描述:获取相机在世界坐标系中的方向
    • 参数:target​(类型:THREE.Vector3​)存储方向的向量
    • 返回值:target​(类型:THREE.Vector3​)相机的方向向量
  4. clone()

    • 描述:克隆相机对象
    • 返回值:新的相机对象
  5. copy(source)

    • 描述:将另一个相机对象的属性复制到当前相机对象
    • 参数:source​(类型:THREE.Camera​)源相机对象
    • 返回值:当前相机对象。
  6. toJSON()

    • 描述:将相机对象转换为 JSON 格式
    • 返回值:相机对象的 JSON 表示
  7. setViewOffset(fullWidth, fullHeight, x, y, width, height)

    • 描述:设置相机的视图偏移,用于多视图渲染

    • 参数:

      • fullWidth​(类型:number​)完整宽度
      • fullHeight​(类型:number​)完整高度
      • x​(类型:number​)视图偏移的 x 坐标
      • y​(类型:number​)视图偏移的 y 坐标
      • width​(类型:number​)视图宽度
      • height​(类型:number​)视图高度
  8. clearViewOffset()

    • 描述:清除相机的视图偏移
  9. getFilmHeight()

    • 描述:获取相机的胶片高度(仅适用于透视相机)
    • 返回值:胶片高度(类型:number​)
  10. getFilmWidth()

    • 描述:获取相机的胶片宽度(仅适用于透视相机)
    • 返回值:胶片宽度(类型:number​)

相机控制器

通过相机控制,我们可以使用鼠标、键盘、或触控设备对相机进行旋转、缩放、平移等操作。

常用的相机控制有:

  1. OrbitControls(轨道控制器):允许用户围绕一个目标点(通常是场景的中心或某个物体)进行旋转、缩放和平移

    常用于 3D 场景浏览和简单的交互项目中。

  2. TrackballControls(球形控制器):提供了更加自由的相机控制,

    用户可以围绕场景的任意方向旋转、平移和缩放。

    相较于 OrbitControls​,TrackballControls​ 更加灵活且没有固定的目标点,常用于需要自由旋转的场景。

  3. FlyControls(飞行控制器):专为第一人称视角设计的控制器,类似于飞行游戏中的导航系统。

    用户可以使用键盘或鼠标实现相机在 3D 空间中的自由移动。

  4. FirstPersonControls(第一人称控制器):专为第一人称视角设计

    常用于需要通过键盘和鼠标控制相机移动的应用,类似于第一人称射击游戏。

  5. 自定义相机控制:如果现有的控制器不能满足需求,你可以基于用户输入(如鼠标、键盘事件)自定义相机的移动、旋转和缩放

轨道控制器

OrbitControls​ 是最常用的相机控制器之一,它允许用户围绕一个目标点(通常是场景的中心或某个物体)进行旋转、缩放和平移。

// 首先得导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

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

常用属性

阻尼效果
  • enableDamping​:Boolean

    启用或禁用相机移动时的阻尼效果,类似惯性运动,默认为 false

  • dampingFactor​:Number

    阻尼系数,控制阻尼效果的强弱,默认值为 0.25​,通常设为 0.1​ 到 0.25​ 之间

缩放控制
  • enableZoom​:Boolean

    允许用户通过鼠标滚轮或触摸缩放场景,默认值为 true

  • minDistance​ 和 maxDistance​:Number

    控制相机距离目标的最小和最大距离,用于限制缩放,默认值分别为 0​ 和 Infinity

  • zoomSpeed​:Boolean

    缩放速度,默认值为 1.0

旋转控制
  • enableRotate​:Boolean

    是否启用旋转,默认值为 true

  • rotateSpeed​:Number

    旋转速度,默认值为 1.0

  • minPolarAngle​ 和 maxPolarAngle​:Number

    限制垂直旋转的角度范围,以防止相机翻转。角度单位是弧度,默认值分别为 0​ 和 Math.PI

    例:限制垂直旋转在 45 度到 90 度之间:

    controls.minPolarAngle = Math.PI / 4; controls.maxPolarAngle = Math.PI / 2;

  • minAzimuthAngle​ 和 maxAzimuthAngle​:Number

    相机水平旋转的最小和最大角度,默认值分别为 -Infinity​ 和 Infinity

自动旋转
  • autoRotate​:Boolean

    启用自动旋转功能,可以让相机自动围绕目标旋转。通常用于展示场景,需使用 update​ 方法才生效,默认值为 false

  • autoRotateSpeed​:Number

    设置自动旋转速度,默认值为 2.0

平移控制
  • enablePan​:Boolean

    启用或禁用相机的平移操作,平移操作常用于在二维平面上移动视角,默认值为 true

  • panSpeed​:Number

    平移速度,默认值为 1.0

  • screenSpacePanning​:Boolean

    是否在屏幕空间中平移,默认值为 false

  • keyPanSpeed​:Number

    通过键盘平移的速度,默认值为 7.0

球形控制器

TrackballControls​ 提供了更加自由的相机控制,用户可以围绕场景的任意方向旋转、平移和缩放。相较于 OrbitControls​,TrackballControls​ 更加灵活且没有固定的目标点,适合需要自由旋转的场景。

import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js';

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

常用属性

旋转控制
  • rotateSpeed​:Number

    控制时旋转速度,默认值为 1.0

  • noRotate​:Boolean

    是否禁用旋转,默认值为 false

缩放控制
  • zoomSpeed​:Number

    缩放速度,默认值为 1.2

  • noZoom​:Boolean

    是否禁用缩放,默认值为 false

  • minDistance​ 和 maxDistance​:Number

    控制相机距离目标的最小和最大距离,用于限制缩放,默认值为 Infinity

平移控制
  • panSpeed​:Number

    平移速度,默认值为 0.3

  • noPan​:Boolean

    是否禁用平移,默认值为 false

动态阻尼
  • dynamicDampingFactor​:Number

    动态阻尼系数,控制阻尼效果的强弱,默认值为 0.2

静态移动
  • staticMoving​:Boolean

    是否启用静态移动,默认值为 false​。如果为 true​,则相机移动时不会有惯性效果

其他参数
  • keys​:Array

    定义用于控制的键盘按键

    默认为 ['KeyA', 'KeyS', 'KeyD']​,分别表示A, S, D。

    该数组包含用于控制交互的按键代码。

    • 当定义的第一个按键按下后,所有的鼠标交互(左/中/右键)表现为环绕。
    • 当定义的第二个按键按下后,所有的鼠标交互(左/中/右键)表现为缩放。
    • 当定义的第三个按键按下后,所有的鼠标交互(左/中/右键)表现为平移。
  • mouseButtons​:Object

    定义用于控制的鼠标按键,默认值为 { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.ZOOM, RIGHT: THREE.MOUSE.PAN }

功能特点

  • 自由旋转:用户可以以任意方向旋转相机,而不局限于围绕特定目标。
  • 平滑过渡:提供了平滑的过渡效果,特别适合需要旋转相机进行观察的 3D 场景。
  • 平移与缩放:允许使用鼠标右键进行平移,使用鼠标滚轮进行缩放。

飞行控制器

FlyControls​ 是专为第一人称视角设计的控制器,类似于飞行游戏中的导航系统。用户可以使用键盘或鼠标实现相机在 3D 空间中的自由移动。

import { FlyControls } from 'three/examples/jsm/controls/FlyControls.js';

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

常用属性

基本控制
  • autoForward​:初始变换后,摄像机将自动向前移动(且不会停止)

    默认值为 false

  • dragToLook ​:若该值设为 true,你将只能通过执行拖拽交互来环视四周。

    默认值为 false

  • movementSpeed​:Number

    移动速度,默认值为 1

  • rollSpeed​:Number

    旋转速度。默认值为 0.005

第一人称控制器

FirstPersonControls​ 与 FlyControls​ 类似,专为第一人称视角设计,适用于需要通过键盘和鼠标控制相机移动的应用,类似于第一人称射击游戏。

FirstPersonControls​ 比 FlyControls​ 更适合需要限制相机移动在地面上的场景,比如虚拟漫游或建筑展示项目。

import { FirstPersonControls } from 'three/examples/jsm/controls/FirstPersonControls.js';

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

常用属性

移动控制
  • movementSpeed​:Number

    移动速度,默认值为 1

  • lookSpeed​:Number

    环视速度(鼠标速度), 默认为 0.005

  • lookVertical​:Boolean

    是否允许垂直方向的视角移动,默认值为 true

  • autoForward​:Boolean

    是否自动向前移动,默认值为 false

  • activeLook​:Boolean

    是否启用环视(鼠标移动视角),默认值为 true

限制控制
  • constrainVertical​:Boolean

    是否限制垂直方向的视角移动,默认值为 true

  • verticalMin​ 和 verticalMax​:Number

    垂直方向视角移动的最小和最大角度(弧度),默认值分别为 0​ 和 Math.PI

    范围在 0​ 到 Math.PI​ 弧度之间

其他控制
  • heightSpeed​:Boolean

    是否根据高度调整移动速度,默认值为 false

  • heightCoef​:Number

    当Y坐标接近.heightMax时摄像机的移动速度,默认值为 1

  • heightMin​ 和 heightMax​:Number

    用于调节移动速度的摄像机的最小高度限制和最大值高度限制,默认值分别为 0​ 和 1

自定义相机控制

自定义相机控制可以通过扩展现有的控制器或从头编写新的控制器来实现。

实现自定义控制器类

import * as THREE from 'three';

export class CustomCameraControls {
    camera: THREE.Camera; // 相机对象
    domElement: HTMLElement; // 绑定的DOM元素
    movementSpeed: number; // 移动速度
    rotationSpeed: number; // 旋转速度

    constructor(camera: THREE.Camera, domElement: HTMLElement) {
        this.camera = camera; // 初始化相机对象
        this.domElement = domElement; // 初始化DOM元素

        // 控制参数
        this.movementSpeed = 10.0; // 移动速度
        this.rotationSpeed = 0.005; // 旋转速度

        // 绑定事件处理函数
        this.onMouseMove = this.onMouseMove.bind(this);

        // 添加事件监听器
        this.domElement.addEventListener('mousemove', this.onMouseMove);
    }

    // 鼠标移动事件处理函数
    onMouseMove(event: MouseEvent) {
        const movementX = event.movementX || 0; // 获取鼠标X轴移动距离
        const movementY = event.movementY || 0; // 获取鼠标Y轴移动距离

        this.camera.rotation.y -= movementX * this.rotationSpeed; // 根据鼠标移动调整相机的y轴旋转
        this.camera.rotation.x -= movementY * this.rotationSpeed; // 根据鼠标移动调整相机的x轴旋转
    }

    // 更新相机位置和旋转
    update(delta: number) {
        const moveDistance = this.movementSpeed * delta; // 计算移动距离

        // 移动和旋转逻辑可以根据需要添加
    }

    // 移除事件监听器
    dispose() {
        this.domElement.removeEventListener('mousemove', this.onMouseMove);
    }
}

使用自定义控制器

// 使用自定义控制器
const controls = new CustomCameraControls(camera, canvas.value);

// 创建时钟
const clock = new THREE.Clock();

// 添加渲染循环
const animate = () => {
  requestAnimationFrame(animate);
  const delta = clock.getDelta(); // 获取时间增量
  controls.update(delta); // 更新相机控制
  renderer.value.render(scene, camera);
};
animate();

控制器共有属性和方法

所有控制器的抽象基类 Controls​ 的常用属性和方法

共有属性

基本控制
  • enabled​:Boolean

    是否启用控制,默认为 true

  • target​:THREE.Vector3

    控制相机围绕哪个目标旋转,默认为 (0, 0, 0)

  • domElement​:HTMLDOMElement

    用于监听鼠标和键盘事件的 DOM 元素, 如果没有在构造函数中提供,.connect() 必须在 domElement 设置后才能调用。

共有方法

  • update

    用于更新相机的位置和方向。

    每次在渲染循环中调用 update()​ 方法,可以确保相机的控制状态(如旋转、缩放、平移等)被正确应用

    update()​ 方法通常在以下情况下使用:

    1. 动画循环中

      在每一帧渲染之前调用 update()​ 方法,以确保相机的控制状态(如旋转、缩放、平移等)被正确应用。这是最常见的用法。

    2. 启用阻尼效果时

      如果启用了阻尼效果(enableDamping​),需要在动画循环中调用 update()​ 方法,以应用阻尼效果。

    3. 自动旋转时

      如果启用了自动旋转(autoRotate​),需要在动画循环中调用 update()​ 方法,以实现自动旋转效果。

    4. 手动更新相机位置

      在某些情况下,你可能需要手动更新相机的位置和方向,例如在响应用户输入或其他事件时。