首页 / Three.js 入门教程 / 场景与相机

Three.js 入门教程

场景与相机

本教程共 40 篇 · 第 6 篇 · 更新于 2026-08-14 · 约 7 分钟阅读

Three.js场景相机PerspectiveCameraOrthographicCameralookAtfov

本节目标:理解场景(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 / farleft / 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 太大会产生明显的透视畸变,太小则画面显得扁平。