3、相机系统Camera:透视相机与正交相机、相机参数详解、相机控制(OrbitControls)、多相机切换
相机,说白了就是你在3D世界里的「眼睛」。
Three.js里最常用的两双眼睛,就是透视相机和正交相机。我刚开始学的时候,总觉得它们差不多,直到有一次做建筑可视化项目,用错了相机类型,整个模型比例全崩了……嗯,从那以后我再也不敢小看这个选择了。
3.1 透视相机 vs 正交相机
先说说最直观的区别。
透视相机模拟的是人眼效果——近大远小。你站在铁轨上,远处的铁轨会汇聚成一个点。这种相机适合做游戏、漫游、产品展示,因为它看起来「真实」。
正交相机则没有透视效果,物体大小不随距离变化。你想想看,工程图纸、2D游戏、UI界面,用的就是这种。它适合做测量、俯视图、或者需要精确对齐的场景。
一句话总结:要真实感,用透视;要精确性,用正交。
3.2 相机参数详解
先看透视相机的构造。我习惯把它想象成一个「视锥体」——从相机位置向前延伸,形成一个四棱锥,近处切一刀,远处再切一刀,中间的部分就是你能看到的区域。
const camera = new THREE.PerspectiveCamera(
75, // 视场角(FOV),单位度
width/height, // 宽高比
0.1, // 近裁剪面
1000 // 远裁剪面
);
这几个参数,每个都有坑。
| 参数 | 作用 | 我踩过的坑 |
|---|---|---|
| FOV(视场角) | 视野宽度,越大看到的东西越多 | 设到120度以上,画面边缘会严重畸变,像鱼眼镜头 |
| 宽高比 | 画面宽高比例,通常用canvas尺寸计算 | 忘记在窗口resize时更新,画面会被拉伸 |
| 近裁剪面 | 离相机多近开始渲染 | 设太小(比如0.0001),深度精度会出问题,远处物体闪烁 |
| 远裁剪面 | 离相机多远停止渲染 | 设太大,同样影响深度精度,而且浪费性能 |
我的个人习惯:近裁剪面设0.1,远裁剪面设1000,够用就行。除非你做的是宇宙场景,否则别动不动设到100000。
正交相机呢?参数更简单,但更容易搞混。
const camera = new THREE.OrthographicCamera(
-10, // left
10, // right
10, // top
-10, // bottom
0.1, // near
1000 // far
);
left/right/top/bottom定义的是一个矩形视口。你想想看,这个矩形就是你能看到的「窗口」。物体在这个窗口内就显示,超出就裁剪掉。
我曾经犯过一个低级错误:把left和right设反了,结果场景上下颠倒。排查了半天才发现……嗯,这种低级错误,犯过一次就不会再犯了。
3.3 相机控制(OrbitControls)
光有相机还不够,你得能操控它。OrbitControls是Three.js官方提供的控制器,让你用鼠标拖拽旋转、滚轮缩放、右键平移。
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 开启惯性效果
controls.dampingFactor = 0.05; // 惯性系数
controls.autoRotate = false; // 自动旋转
controls.target.set(0, 0, 0); // 围绕哪个点旋转
这里有个关键点:记得在动画循环里调用controls.update()。尤其是开启了damping(惯性)之后,不调用update,旋转操作会卡顿。
避坑指南:我曾经在项目中把controls.target设错了位置,结果相机绕着空气转,模型纹丝不动。排查了半小时才发现是target没更新。建议每次移动场景后,都检查一下target是否在正确位置。
OrbitControls还有一些实用配置:
minDistance / maxDistance:限制缩放范围,防止穿模minPolarAngle / maxPolarAngle:限制上下旋转角度,避免翻转到地下enablePan:是否允许平移,做展示类项目我一般关掉
3.4 多相机切换
有时候一个相机不够用。比如我做的一个工厂监控项目,需要同时提供「全局俯视图」和「第一人称漫游」两种视角。
多相机切换的核心思路很简单:维护一个相机数组,按需切换当前激活的相机。
const cameras = {
perspective: new THREE.PerspectiveCamera(75, aspect, 0.1, 1000),
orthographic: new THREE.OrthographicCamera(-10, 10, 10, -10, 0.1, 1000)
};
let activeCamera = cameras.perspective;
// 切换相机
function switchCamera(type) {
activeCamera = cameras[type];
// 别忘了同步控制器的目标
controls.object = activeCamera;
controls.target.set(0, 0, 0);
}
// 渲染时使用当前相机
function animate() {
controls.update();
renderer.render(scene, activeCamera);
requestAnimationFrame(animate);
}
这里有个容易忽略的细节:切换相机后,OrbitControls的object属性要同步更新。否则你切到正交相机,控制器还在控制透视相机,操作就会错乱。
我的经验:多相机切换时,最好把每个相机的初始位置和target都存下来。切换时恢复,这样用户不会觉得「视角跳来跳去」。我一般用一个对象来管理:
const cameraConfigs = {
perspective: { pos: [5, 5, 5], target: [0, 0, 0] },
orthographic: { pos: [0, 10, 0], target: [0, 0, 0] }
};
还有一个进阶玩法:分屏显示。把渲染区域切成两半,左边透视,右边正交。这种在3D建模软件里很常见。实现方式是用两个renderer,或者用scissor(裁剪)功能。不过这个比较复杂,咱们课程后面会专门讲。
最后说一句:相机系统看似简单,但它是整个3D体验的入口。相机参数设不好,再好的模型也白搭。我建议你动手试试,把FOV从30调到120,看看画面变化;把正交相机的left/right调大调小,感受一下「窗口」的变化。实践出真知嘛。
公众号:蓝海资料掘金营,微信deep3321