场景与相机
本教程共 40 篇 · 第 6 篇 · 更新于 2026-08-14 · 约 7 分钟阅读
本节目标:理解场景(Scene)与相机(Camera)的分工,学会配置透视相机和正交相机的参数,并能让相机移动到任意位置、看向任意目标。
场景:3D 世界的容器
场景是所有 3D 内容的容器。网格、灯光、相机,都要放进场景才会参与渲染。
创建场景只需要一行代码:
const scene = new THREE.Scene();
把物体放进去用 add:
scene.add(mesh);
scene.add(light);
场景本身看不见,但它有几个影响整个画面的设置:
background:背景色或背景贴图,默认是透明的。fog:雾,让远处的物体逐渐变淡。environment:环境贴图,影响所有 PBR 材质的反光(第 14 章会用到)。
scene.background = new THREE.Color(0x1a1a2e);
scene.fog = new THREE.Fog(0x1a1a2e, 10, 50); // 从 10 米开始起雾,50 米处完全遮住
背景色用十六进制直接写即可。r152 起色彩管理默认开启,颜色会被自动转换到正确的色彩空间,不用手动处理。
Note只有放进场景的物体才会被渲染。忘了
scene.add(mesh),画面上就什么都没有——这是新手最容易踩的坑之一。
相机:从哪个角度看世界
相机决定从哪个位置、朝哪个方向看。同一个场景,换一个相机位置,看到的画面完全不同。
相机不是网格,画面上看不到它。可以把相机理解成一只眼睛:眼睛长在哪、看向哪,决定了你看到什么。
相机也是 Object3D,所以它有 position(位置)和 rotation(旋转),用法和普通物体完全一样。默认情况下相机在原点 (0, 0, 0),看向 -Z 方向。
因为继承了 Object3D,相机还能挂子物体:把灯光或提示物体加进相机,它们就会一直跟着相机移动,做第一人称效果时很常用。
一个场景可以有多个相机,但每次渲染只用其中一个:
renderer.render(scene, camera);
three.js 最常用的相机有两种:透视相机和正交相机。
透视相机(PerspectiveCamera)
透视相机模拟人眼,近大远小。它是 3D 场景里最常用的相机。
const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 100);
构造函数的四个参数:
fov:垂直视野角度,单位是度,默认 50。越大看得越广,物体显得越小;越小越像长焦镜头。aspect:画布的宽高比,通常写成width / height。near:近裁剪面。比它离相机更近的物体不渲染。far:远裁剪面。比它更远的物体不渲染。
near 和 far 之间形成一个视锥体(frustum)。只有视锥体里的物体才会被渲染,视锥体外的直接跳过。这不是偷懒,而是性能优化:一帧里不用画所有东西。
Note透视相机的
near必须大于 0。发现物体”消失”了,先检查它是不是在 near 和 far 之间。
修改相机参数后,需要调用 updateProjectionMatrix() 让改动生效。第 8 章响应式设计里会频繁用到它。
相机还有一个 zoom 属性,默认 1。把它调大,画面就像数码变焦一样放大,适合做瞄准、特写效果,不用挪相机位置。
正交相机(OrthographicCamera)
正交相机没有近大远小。不管物体离相机多远,画出来大小都一样。
const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
六个参数定义一个长方体视区。视区里的物体按平行投影画到屏幕上,没有透视变形。
正交相机适合 2D 游戏、UI 叠加层、建筑图纸这类不需要透视效果的场景。
| 对比项 | 透视相机 | 正交相机 |
|---|---|---|
| 效果 | 近大远小,有纵深感 | 远近一样大,平行投影 |
| 参数 | fov / aspect / near / far | left / right / top / bottom / near / far |
| 典型用途 | 3D 场景、游戏主视角 | 2D 游戏、UI、俯视图 |
Tip用正交相机时,按窗口宽高比调整左右边界,否则画面会被拉伸:
new THREE.OrthographicCamera(-size * aspect, size * aspect, size, -size, 0.1, 100)
摆放相机与 lookAt
相机默认在原点,位置太贴脸。先用 position.set 把相机挪开:
camera.position.set(0, 2, 8);
但把相机挪到旁边后,它还朝着原来的方向。要让相机看向某个目标,用 lookAt:
camera.lookAt(0, 0, 0);
lookAt 的参数可以是一个坐标,也可以是一个 Vector3,两种写法等价:
camera.lookAt(0, 1, 0);
camera.lookAt(new THREE.Vector3(0, 1, 0));
它会把相机的朝向旋转到指向目标点。注意 lookAt 用的是世界坐标:相机在场景里挪到哪、目标点在哪,都由世界坐标决定。
想让相机一直跟着某个物体,就在动画循环里每帧调用一次:
function animate() {
requestAnimationFrame(animate);
camera.lookAt(target.position); // 每帧对准目标
renderer.render(scene, camera);
}
这样无论目标怎么移动,相机都会盯住它。
Note相机默认看向自己的 -Z 方向。
lookAt做的事,本质是旋转相机,让它朝向目标点。
另外,scene.add(camera)不是必须的,但加上有好处:挂在相机下的物体(比如跟随相机的提示文字)会跟着相机一起动。
可运行示例:两种相机自由切换
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>6. 场景与相机</title>
<style>
* { margin: 0; padding: 0; }
body { overflow: hidden; }
canvas { display: block; }
#hint {
position: fixed; left: 12px; top: 12px;
color: #fff; font: 14px/1.6 sans-serif;
background: rgba(0, 0, 0, 0.45); padding: 8px 12px; border-radius: 6px;
}
</style>
</head>
<body>
<div id="hint">按 1 切换透视相机 · 按 2 切换正交相机</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
// 1. 场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
// 2. 一排方块:处于不同深度,用来对比两种相机的效果
const material = new THREE.MeshBasicMaterial({ color: 0x4ecdc4 });
for (let i = 0; i < 5; i++) {
const cube = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), material);
cube.position.set((i - 2) * 1.8, 0, -i * 2);
scene.add(cube);
}
// 3. 透视相机:fov / aspect / near / far
const perspectiveCamera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100);
perspectiveCamera.position.set(0, 2.5, 8);
// 4. 正交相机:用窗口宽高比修正左右边界,避免拉伸
const aspect = innerWidth / innerHeight;
const orthographicCamera = new THREE.OrthographicCamera(-4 * aspect, 4 * aspect, 4, -4, 0.1, 100);
orthographicCamera.position.set(0, 2.5, 8);
let camera = perspectiveCamera;
// 5. 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.append(renderer.domElement);
// 6. 键盘切换相机
window.addEventListener('keydown', (event) => {
if (event.key === '1') camera = perspectiveCamera;
if (event.key === '2') camera = orthographicCamera;
});
// 7. 动画循环:相机绕场景旋转,并始终看向方块的中心
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
camera.position.x = Math.sin(t * 0.5) * 6;
camera.position.z = Math.cos(t * 0.5) * 6;
camera.lookAt(0, 0, -4);
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
把代码保存为 .html 文件,用本地服务器打开(直接双击可能因模块加载受限而失败)。运行后按 1 / 2 切换相机:
- 透视相机下,近的方块明显更大,有纵深感。
- 正交相机下,所有方块一样大,像工程图纸。
Tip实际调参时,fov 常用 45~75 之间。fov 太大会产生明显的透视畸变,太小则画面显得扁平。