Three.js 简介

相机与三维坐标系

认识 Three.js 的三维坐标系,搞懂透视相机 fov、aspect、near、far 四个参数,避开物体凭空消失的坑。

🎯 引言

前面几篇的相机我们都是“照抄配置”:PerspectiveCamera(75, 宽高比, 0.1, 1000)。这四个数字到底什么意思?为什么有时物体摆好了却看不见?学完这篇文章,你能认识 Three.js 的三维坐标系,搞懂透视相机四个参数的含义,以后相机位置和视野范围都能自己算。


🧱 三维坐标系

2D 图表只有 x、y 两个轴,3D 空间多了一个 z 轴。Three.js 的坐标系方向是:

  • x 轴:向右为正。
  • y 轴:向上为正。
  • z 轴朝向观察者(屏幕外)为正
        +y
        │
        │    ↖ +z(指向你)
        │  ╱
        └────────→ +x

默认情况下相机站在原点 (0, 0, 0),朝 -z 方向看。这就是为什么我们总是写 camera.position.z = 5:把相机往“屏幕外”拉 5 个单位,才能看到放在原点的物体。

物体的位置、旋转、缩放,全都基于这套坐标:

cube.position.set(1, 2, -3); // 向右 1、向上 2、远离相机 3
cube.rotation.set(0.6, 0.8, 0); // 绕 x、y 轴旋转(单位是弧度)
cube.scale.set(2, 2, 2); // 三个方向都放大 2 倍
rotation 用的是弧度不是角度。一圈是 Math.PI * 2,90° 就是 Math.PI / 2。写成 rotation.x = 90 会转得面目全非。

🧱 透视相机的四个参数

const camera = new THREE.PerspectiveCamera(75, 320 / 240, 0.1, 1000);

构造函数的四个参数依次是:

参数含义类比
fov视野角度(竖直方向张多大)眼睛的余光范围
aspect画幅宽高比电影银幕的宽和高比例
near近截面:比这近的物体不渲染贴太近眼睛看不清
far远截面:比这远的物体不渲染太远看不见

这四个参数围出来的可见空间叫视锥体,像手电筒照出去的一个四棱锥:只有落在锥体范围内的物体才会被渲染。

参数的记忆口诀:角度、比例、近、远。日常开发用这套组合就够用:75, 画布宽 / 画布高, 0.1, 1000


⚡ fov:广角和长焦的区别

fov 是调整频率很高的参数,直接决定画面的透视感:

  • fov 大(如 75):视野宽,容纳的场景多,近大远小的透视感强。
  • fov 小(如 30):视野窄,像望远镜,画面里的物体显得更大、更平。

对比代码只有一行之差:

const wideCamera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000); // 广角
const narrowCamera = new THREE.PerspectiveCamera(30, aspect, 0.1, 1000); // 长焦

下面是同一个立方体在两种 fov 下的真实渲染对比(相机位置相同,左 75°、右 30°)。注意右边视野变窄后,立方体在画面里明显大了一圈:

fov = 75(广角)

fov = 30(长焦)


🪤 near 和 far:物体凭空消失的元凶

nearfar 是视锥体的前后两个“裁剪面”,范围之外的物体会被直接丢弃,不渲染也不报错,表现为物体凭空消失。

常见的踩坑场景:

// near = 0.1,far = 100
const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 100);

cube.position.z = -200; // 距离相机超过 100,消失!
camera.position.z = 0.05; // 物体在原点,距离小于 0.1,同样消失!

排查“物体不见了”时,按这个顺序检查:

  1. 物体有没有 scene.add()
  2. 物体是不是在相机背后(z 轴方向搞反)?
  3. 物体距离是不是落在 [near, far] 范围之外?
nearfar 的跨度不要设得夸张地大(比如 0.0001 ~ 1000000),跨度太大会导致深度计算精度不足,画面出现闪烁。够用即可,0.1 ~ 1000 适合绝大多数学习场景。

⚖️ 另一种相机:正交相机

除了透视相机,还有 OrthographicCamera(正交相机)。它没有近大远小,物体无论多远看起来都一样大:

const camera = new THREE.OrthographicCamera(-4, 4, 3, -3, 0.1, 1000);
对比透视相机正交相机
近大远小有,像人眼没有
适合场景3D 场景、产品展示2D 游戏、工程图纸、CAD

本课程统一使用透视相机,正交相机知道它的存在即可。


🧾 小节总结

  • 坐标系:x 向右、y 向上、z 朝向屏幕外;相机默认在原点朝 -z 看。
  • rotation 用弧度,90° = Math.PI / 2
  • 透视相机四参数:fov(角度)、aspect(宽高比)、near(近)、far(远)。
  • fov 大透视感强,fov 小像望远镜。
  • 物体在 [near, far] 范围外会被静默丢弃,这是“物体消失”的高频原因。
  • 正交相机无近大远小,适合 2D 和工程图场景。

❓ 知识问答

Q1:aspect 为什么必须等于画布的宽高比?

不相等画面会被拉伸或压扁,就像把宽银幕电影挤进方形电视。画幅尺寸变化时(比如窗口缩放),记得同步更新:camera.aspect = 新宽 / 新高,再调用 camera.updateProjectionMatrix() 让它生效。

Q2:相机需要 scene.add() 放进场景吗?

不是必须的,相机不放进场景也能正常渲染。只有当相机要挂接在别的物体上(比如跟随角色移动)时,才需要加入场景。

Q3:camera.lookAt() 之后还能改相机位置吗?

可以,但改完要再调一次 lookAt()。因为 lookAt 只是按“当时的位置”算好朝向,不会持续跟踪。

Q4:怎么实现相机绕着物体转的效果?

每一帧用三角函数更新相机位置的 x、z(让相机画圆),再配合 lookAt() 盯住物体。第 6 篇的 OrbitControls 已经把这个功能封装好了,直接用更方便。


🧪 小练习

做一个“找物体”实验:

  1. PerspectiveCamera(75, 320 / 240, 0.1, 10) 创建相机,相机放在 z = 5
  2. z = -20 处放一个立方体,运行后解释为什么看不到它。
  3. far 改成 50,再观察立方体是否出现。
views/FindCube.vue
<template>
    <div ref="containerRef" class="container"></div>
</template>

<script setup>
import { onMounted, ref } from 'vue';
import * as THREE from 'three';

const containerRef = ref(null);

onMounted(() => {
    // 请在这里编写代码
});
</script>

<style scoped>
.container {
    width: 320px;
    height: 240px;
}
</style>

🎉 恭喜你已经搞懂相机和坐标系啦!到现在场景里的东西都是静止的,下一篇我们让它们动起来。