Three.js 相机 #
相机决定了用户如何观察 3D 场景。理解相机的原理和配置是创建优秀 3D 体验的关键。
相机概述 #
text
┌─────────────────────────────────────────────────────────────┐
│ Three.js 相机类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 透视相机 │ │ 正交相机 │ │ 立体相机 │ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │Perspective │ │Orthographic │ │ Stereo │ │
│ │ Camera │ │ Camera │ │ Camera │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ 特点:近大远小 特点:无透视效果 特点:VR/3D视觉 │
│ │
└─────────────────────────────────────────────────────────────┘
透视相机(PerspectiveCamera) #
透视相机模拟人眼视觉,是最常用的相机类型。
基本用法 #
javascript
const camera = new THREE.PerspectiveCamera(
75, // fov:视角
window.innerWidth / window.innerHeight, // aspect:宽高比
0.1, // near:近裁剪面
1000 // far:远裁剪面
);
camera.position.set(0, 0, 5);
参数详解 #
FOV(视角) #
text
FOV = 45°(窄视角) FOV = 75°(标准) FOV = 120°(广角)
▲ ▲ ▲
/|\ /|\ /|\
/ | \ / | \ / | \
/ | \ / | \ / | \
/ | \ / | \ / | \
/ | \ / | \ / | \
/ | \ / | \ / | \
/ | \ / | \ / | \
──────────────── ──────────────── ────────────────
窄视野 标准视野 广阔视野
| FOV 值 | 效果 | 适用场景 |
|---|---|---|
| 30-45 | 窄视角 | 人像、特写 |
| 50-75 | 标准 | 通用场景 |
| 90-120 | 广角 | 风景、建筑 |
| 120+ | 鱼眼 | 特殊效果 |
Aspect(宽高比) #
javascript
const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
Near 和 Far(裁剪面) #
text
相机位置
│
▼
┌───────┴───────┐
│ Near Plane │ ← near(近裁剪面)
│ (0.1) │ 比这更近的不渲染
│ │
│ │
│ 可见区域 │
│ │
│ │
│ Far Plane │ ← far(远裁剪面)
│ (1000) │ 比这更远的不渲染
└───────────────┘
相机属性 #
位置(Position) #
javascript
camera.position.set(0, 5, 10);
camera.position.x = 0;
camera.position.y = 5;
camera.position.z = 10;
camera.position.x += 1;
旋转(Rotation) #
javascript
camera.rotation.set(0, Math.PI / 4, 0);
camera.rotation.x = -Math.PI / 6;
camera.rotation.y = Math.PI / 4;
观察目标(lookAt) #
javascript
camera.lookAt(0, 0, 0);
camera.lookAt(new THREE.Vector3(0, 0, 0));
const target = new THREE.Vector3(5, 0, 0);
camera.lookAt(target);
投影矩阵 #
javascript
camera.updateProjectionMatrix();
console.log(camera.projectionMatrix);
console.log(camera.projectionMatrixInverse);
正交相机(OrthographicCamera) #
正交相机没有透视效果,物体大小不随距离变化。
基本用法 #
javascript
const frustumSize = 10;
const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.OrthographicCamera(
frustumSize * aspect / -2, // left
frustumSize * aspect / 2, // right
frustumSize / 2, // top
frustumSize / -2, // bottom
0.1, // near
1000 // far
);
camera.position.set(0, 0, 10);
参数详解 #
text
Top (上边界)
▲
│
Left ◄────┼────► Right
(左边界) │ (右边界)
│
▼
Bottom (下边界)
正交投影视图:所有光线平行,无透视效果
| 参数 | 描述 |
|---|---|
| left | 左边界 |
| right | 右边界 |
| top | 上边界 |
| bottom | 下边界 |
| near | 近裁剪面 |
| far | 远裁剪面 |
等轴测视图 #
javascript
const aspect = window.innerWidth / window.innerHeight;
const d = 5;
const camera = new THREE.OrthographicCamera(
-d * aspect, d * aspect,
d, -d,
0.1, 1000
);
camera.position.set(10, 10, 10);
camera.lookAt(0, 0, 0);
2D 视图 #
javascript
const camera = new THREE.OrthographicCamera(
-window.innerWidth / 2,
window.innerWidth / 2,
window.innerHeight / 2,
-window.innerHeight / 2,
0.1, 1000
);
camera.position.z = 100;
相机控制器 #
OrbitControls #
轨道控制器允许用户通过鼠标/触摸旋转、缩放、平移场景。
javascript
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 1;
controls.maxDistance = 100;
controls.maxPolarAngle = Math.PI / 2;
配置选项 #
| 选项 | 默认值 | 描述 |
|---|---|---|
| enableDamping | false | 启用阻尼 |
| dampingFactor | 0.05 | 阻尼系数 |
| screenSpacePanning | true | 屏幕空间平移 |
| minDistance | 0 | 最小距离 |
| maxDistance | Infinity | 最大距离 |
| minPolarAngle | 0 | 最小极角 |
| maxPolarAngle | Math.PI | 最大极角 |
| minAzimuthAngle | -Infinity | 最小方位角 |
| maxAzimuthAngle | Infinity | 最大方位角 |
| enableZoom | true | 启用缩放 |
| zoomSpeed | 1.0 | 缩放速度 |
| enableRotate | true | 启用旋转 |
| rotateSpeed | 1.0 | 旋转速度 |
| enablePan | true | 启用平移 |
| panSpeed | 1.0 | 平移速度 |
使用示例 #
javascript
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1, 0);
controls.update();
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 2;
controls.maxDistance = 20;
controls.maxPolarAngle = Math.PI / 2;
controls.minAzimuthAngle = -Math.PI / 4;
controls.maxAzimuthAngle = Math.PI / 4;
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
FlyControls #
飞行控制器模拟飞行器控制。
javascript
import { FlyControls } from 'three/addons/controls/FlyControls.js';
const controls = new FlyControls(camera, renderer.domElement);
controls.movementSpeed = 1;
controls.rollSpeed = Math.PI / 24;
controls.autoForward = false;
controls.dragToLook = true;
FirstPersonControls #
第一人称控制器。
javascript
import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
const controls = new FirstPersonControls(camera, renderer.domElement);
controls.movementSpeed = 1;
controls.lookSpeed = 0.1;
controls.autoForward = false;
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
controls.update(clock.getDelta());
renderer.render(scene, camera);
}
animate();
PointerLockControls #
指针锁定控制器,适合第一人称游戏。
javascript
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
const controls = new PointerLockControls(camera, document.body);
document.addEventListener('click', () => {
controls.lock();
});
controls.addEventListener('lock', () => {
console.log('Pointer locked');
});
controls.addEventListener('unlock', () => {
console.log('Pointer unlocked');
});
const velocity = new THREE.Vector3();
const direction = new THREE.Vector3();
document.addEventListener('keydown', (event) => {
switch (event.code) {
case 'KeyW': direction.z = -1; break;
case 'KeyS': direction.z = 1; break;
case 'KeyA': direction.x = -1; break;
case 'KeyD': direction.x = 1; break;
}
});
document.addEventListener('keyup', (event) => {
switch (event.code) {
case 'KeyW': case 'KeyS': direction.z = 0; break;
case 'KeyA': case 'KeyD': direction.x = 0; break;
}
});
function animate() {
requestAnimationFrame(animate);
if (controls.isLocked) {
velocity.x = direction.x * 0.1;
velocity.z = direction.z * 0.1;
controls.moveRight(velocity.x);
controls.moveForward(-velocity.z);
}
renderer.render(scene, camera);
}
animate();
TrackballControls #
轨迹球控制器,提供更自由的旋转。
javascript
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
const controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
相机动画 #
相机移动 #
javascript
const startPos = new THREE.Vector3(0, 0, 5);
const endPos = new THREE.Vector3(5, 5, 5);
const duration = 2000;
let startTime = null;
function animateCamera(timestamp) {
if (!startTime) startTime = timestamp;
const progress = (timestamp - startTime) / duration;
if (progress < 1) {
camera.position.lerpVectors(startPos, endPos, progress);
camera.lookAt(0, 0, 0);
requestAnimationFrame(animateCamera);
}
}
requestAnimationFrame(animateCamera);
相机环绕 #
javascript
const radius = 5;
let angle = 0;
function animate() {
requestAnimationFrame(animate);
angle += 0.01;
camera.position.x = Math.sin(angle) * radius;
camera.position.z = Math.cos(angle) * radius;
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);
}
animate();
相机跟随 #
javascript
const target = new THREE.Vector3();
const offset = new THREE.Vector3(0, 2, -5);
const smoothness = 0.1;
function animate() {
requestAnimationFrame(animate);
const desiredPosition = target.clone().add(offset);
camera.position.lerp(desiredPosition, smoothness);
camera.lookAt(target);
renderer.render(scene, camera);
}
animate();
使用 GSAP 动画 #
javascript
import gsap from 'gsap';
gsap.to(camera.position, {
x: 5,
y: 5,
z: 5,
duration: 2,
ease: 'power2.inOut',
onUpdate: () => {
camera.lookAt(0, 0, 0);
}
});
相机辅助器 #
CameraHelper #
javascript
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
const cameraHelper = new THREE.CameraHelper(camera);
scene.add(cameraHelper);
function animate() {
requestAnimationFrame(animate);
cameraHelper.update();
renderer.render(scene, mainCamera);
}
animate();
多相机切换 #
javascript
const cameras = {
perspective: new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000),
orthographic: new THREE.OrthographicCamera(-5, 5, 5, -5, 0.1, 1000),
top: new THREE.OrthographicCamera(-5, 5, 5, -5, 0.1, 1000),
front: new THREE.OrthographicCamera(-5, 5, 5, -5, 0.1, 1000)
};
cameras.perspective.position.set(5, 5, 5);
cameras.perspective.lookAt(0, 0, 0);
cameras.orthographic.position.set(5, 5, 5);
cameras.orthographic.lookAt(0, 0, 0);
cameras.top.position.set(0, 10, 0);
cameras.top.lookAt(0, 0, 0);
cameras.front.position.set(0, 0, 10);
cameras.front.lookAt(0, 0, 0);
let activeCamera = cameras.perspective;
function switchCamera(name) {
activeCamera = cameras[name];
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, activeCamera);
}
animate();
分屏渲染 #
javascript
const camera1 = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
camera1.position.set(0, 0, 5);
const camera2 = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
camera2.position.set(5, 0, 0);
camera2.lookAt(0, 0, 0);
function animate() {
requestAnimationFrame(animate);
renderer.setScissorTest(true);
renderer.setViewport(0, 0, window.innerWidth / 2, window.innerHeight);
renderer.setScissor(0, 0, window.innerWidth / 2, window.innerHeight);
renderer.render(scene, camera1);
renderer.setViewport(window.innerWidth / 2, 0, window.innerWidth / 2, window.innerHeight);
renderer.setScissor(window.innerWidth / 2, 0, window.innerWidth / 2, window.innerHeight);
renderer.render(scene, camera2);
renderer.setScissorTest(false);
}
animate();
相机对比表 #
| 特性 | 透视相机 | 正交相机 |
|---|---|---|
| 透视效果 | 有 | 无 |
| 近大远小 | 是 | 否 |
| 真实感 | 高 | 低 |
| 适用场景 | 3D 游戏、产品展示 | 2D 游戏、CAD、UI |
| 性能 | 相同 | 相同 |
| 配置复杂度 | 中等 | 中等 |
最佳实践 #
选择合适的 FOV #
javascript
const fovPresets = {
portrait: 45,
standard: 60,
wide: 90,
fisheye: 120
};
const camera = new THREE.PerspectiveCamera(
fovPresets.standard,
window.innerWidth / window.innerHeight,
0.1,
1000
);
设置合理的裁剪面 #
javascript
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(75, aspect, near, far);
// 避免过大的 far/near 比值,会导致深度冲突
// 推荐:far/near < 10000
响应式处理 #
javascript
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
});
完整示例 #
javascript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(5, 5, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 2;
controls.maxDistance = 20;
controls.maxPolarAngle = Math.PI / 2;
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0xff6b6b });
const cube = new THREE.Mesh(geometry, material);
cube.position.y = 0.5;
scene.add(cube);
const cameraHelper = new THREE.CameraHelper(camera);
scene.add(cameraHelper);
function animate() {
requestAnimationFrame(animate);
cube.rotation.y += 0.01;
controls.update();
cameraHelper.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
下一步 #
现在你已经深入了解了相机系统,接下来学习 动画,为场景添加动态效果!
最后更新:2026-03-28