相机与三维坐标系
🎯 引言
前面几篇的相机我们都是“照抄配置”: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:物体凭空消失的元凶
near 和 far 是视锥体的前后两个“裁剪面”,范围之外的物体会被直接丢弃,不渲染也不报错,表现为物体凭空消失。
常见的踩坑场景:
// 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,同样消失!
排查“物体不见了”时,按这个顺序检查:
- 物体有没有
scene.add()? - 物体是不是在相机背后(z 轴方向搞反)?
- 物体距离是不是落在
[near, far]范围之外?
near 和 far 的跨度不要设得夸张地大(比如 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 已经把这个功能封装好了,直接用更方便。
🧪 小练习
做一个“找物体”实验:
- 按
PerspectiveCamera(75, 320 / 240, 0.1, 10)创建相机,相机放在z = 5。 - 在
z = -20处放一个立方体,运行后解释为什么看不到它。 - 把
far改成 50,再观察立方体是否出现。
<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>
🎉 恭喜你已经搞懂相机和坐标系啦!到现在场景里的东西都是静止的,下一篇我们让它们动起来。
