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