场景与相机:搭建你的第一个3D视角
好,我们正式开始搭建3D世界。上一章我们只是让浏览器显示了一个Three.js的空白画布,说实话挺无聊的。这一章我们要往里面放东西——准确地说,是放一个「场景」和一台「相机」。
场景和相机,是Three.js里最基础的两个概念。没有场景,你没法放物体;没有相机,你啥也看不见。我刚开始学的时候,以为场景就是个容器,相机就是个镜头,后来发现没那么简单。
场景(Scene)是什么?
场景,说白了就是一个虚拟的三维空间。你所有的物体、灯光、辅助线,都得放在这个空间里。Three.js里用 THREE.Scene 来表示。
const scene = new THREE.Scene();
就这么一行。但你别小看它。场景其实是一个树状结构,你可以往里面添加各种子对象。比如添加一个立方体:
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
嗯,这里要注意:scene.add() 可以添加任何继承自 Object3D 的对象。Mesh、Light、Group 都可以。我个人习惯把同类型的物体放在一个 Group 里,再统一加到场景,这样管理起来方便。
scene.background = new THREE.Color(0x87CEEB); 就能得到一个天蓝色的背景。
相机:透视 vs 正交
相机决定了你从哪个角度、以什么方式观察场景。Three.js 提供了两种最常用的相机:透视相机和正交相机。
透视相机(PerspectiveCamera)
透视相机模拟人眼的视觉效果——近大远小。这是最常用的相机类型,适合做游戏、室内漫游、产品展示。
const camera = new THREE.PerspectiveCamera(
75, // 视野角度(FOV),单位度
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近裁剪面
1000 // 远裁剪面
);
这四个参数,我一个个说:
- FOV(视野角度):值越大,看到的范围越广,但物体显得越小。75度是个比较舒服的值。我在做建筑可视化时,经常用45-60度,更接近人眼真实感受。
- 宽高比:一般直接用窗口的宽高比。如果不对,画面会拉伸变形。
- 近裁剪面 / 远裁剪面:相机只能看到这两个平面之间的物体。太近或太远的物体都会被裁剪掉。我曾经遇到过一个问题:模型明明在场景里,但就是看不到,后来发现是远裁剪面设得太小了。
正交相机(OrthographicCamera)
正交相机没有近大远小的效果。物体的大小不随距离改变。适合做2D游戏、UI界面、技术图纸。
const camera = new THREE.OrthographicCamera(
-10, // left
10, // right
10, // top
-10, // bottom
0.1, // near
1000 // far
);
正交相机的参数是左右上下四个边界,形成一个矩形视锥体。说白了,你看到的画面就是这个矩形范围内的物体。
| 特性 | 透视相机 | 正交相机 |
|---|---|---|
| 近大远小 | 是 | 否 |
| 适用场景 | 3D游戏、漫游 | 2D游戏、UI |
| 参数 | FOV、宽高比、近远 | 左右上下、近远 |
| 视觉感受 | 真实、有纵深感 | 平面、技术感 |
相机位置与朝向
相机创建出来后,默认在原点 (0,0,0)。但场景里的物体也在原点,所以你会和物体重叠在一起,啥也看不见。你需要移动相机。
camera.position.set(5, 5, 5); // 把相机放到 (5,5,5) 位置
camera.lookAt(0, 0, 0); // 让相机看向原点
position 是相机在三维空间中的坐标。 lookAt 是相机的朝向——它看向哪个点。这两个配合使用,你就能从任意角度观察场景了。
我个人习惯先设置位置,再调用 lookAt。如果你不调用 lookAt,相机默认朝向Z轴负方向。嗯,这个细节容易忽略。
相机控制:OrbitControls
手动设置相机位置和朝向,写死了就没法交互了。为了让用户能自由旋转、缩放、平移视角,我们需要 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; // 是否自动旋转
这里要注意几点:
- enableDamping:开启后,操作会有平滑的惯性效果,手感更好。我建议一直开着。
- dampingFactor:惯性大小,值越小惯性越明显。0.05是个不错的起点。
- autoRotate:可以让场景自动旋转,适合做展示效果。
然后,在动画循环里,你需要调用 controls.update():
function animate() {
requestAnimationFrame(animate);
controls.update(); // 更新控制器状态
renderer.render(scene, camera);
}
animate();
为什么需要 update()?因为开启阻尼后,控制器需要每帧更新才能实现平滑效果。如果不调用,阻尼就不会生效。
controls.minDistance = 2; 和 controls.maxDistance = 20; 可以防止用户把相机拉得太近或太远。
完整示例:一个可交互的3D场景
把上面所有东西拼起来,就是一个完整的可交互3D场景了:
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// 1. 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
// 2. 创建透视相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
// 3. 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 4. 添加一个立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 5. 添加轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// 6. 动画循环
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
运行这段代码,你会看到一个绿色的立方体,可以用鼠标拖拽旋转视角,滚轮缩放。嗯,这就是一个最基础的3D交互页面了。