首页 / Three.js 入门教程 / 第一个场景:Hello Cube

Three.js 入门教程

第一个场景:Hello Cube

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

Three.jsScenePerspectiveCameraWebGLRendererMeshBoxGeometry

本节目标:用场景、相机、渲染器三要素,写出第一个可运行的 three.js 程序——一个 3D 立方体。学完你能看懂任何 three.js 程序的最小结构。

完整代码

先把完整代码跑起来,再逐段讲解。保存为 index.html,用本地服务器打开:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>Hello Cube</title>
  <style>
    * { margin: 0; padding: 0; }
    html, body { height: 100%; }
    canvas { display: block; }
  </style>
  <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>
</head>
<body>
  <script type="module">
    import * as THREE from 'three';

    // 1. 场景
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x1a1a2e);

    // 2. 相机
    const camera = new THREE.PerspectiveCamera(
      75, window.innerWidth / window.innerHeight, 0.1, 100
    );
    camera.position.set(0, 0, 5);

    // 3. 立方体:几何体 + 材质 = 网格
    const geometry = new THREE.BoxGeometry(2, 2, 2);
    const material = new THREE.MeshBasicMaterial({ color: 0x00d4ff });
    const cube = new THREE.Mesh(geometry, material);
    cube.rotation.y = 0.5; // 转一点角度,立体感就出来了
    scene.add(cube);

    // 4. 渲染器
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    document.body.appendChild(renderer.domElement);

    // 5. 渲染一帧
    renderer.render(scene, camera);
  </script>
</body>
</html>

页面中央出现一个天蓝色立方体就成功了。接下来拆开讲。

三个核心对象

整个程序围绕三个对象展开,缺一不可:

对象作用
场景Scene装所有 3D 物体
相机PerspectiveCamera决定从哪看
渲染器WebGLRenderer负责画出来

把场景比作小宇宙,相机是望远镜,渲染器是照着望远镜画面作画的画家。三个对象本身都不可见,可见的是被加入场景的物体。这套结构不是 three.js 独创的,几乎所有 3D 图形系统都是这个套路,学会一次,到处通用。第 3 章的 importmap、这里的场景相机渲染器,构成了后面所有章节示例的固定开头,往后会反复复用这套骨架。

场景 Scene

Scene 是所有物体的容器,内部是一棵场景树(scene graph):物体可以挂在场景下,也可以挂在其他物体下,形成父子关系。现在只需要知道最基本的用法:

const scene = new THREE.Scene();
scene.add(cube);    // 添加物体
scene.remove(cube); // 移除物体

物体默认放在原点 (0, 0, 0)。新创建的物体如果不移动位置,全都堆在原点。

scene.background 设置背景色,接受 Color 对象:

scene.background = new THREE.Color(0x1a1a2e);

不设置的话默认黑色。想用图片当背景也可以,给 scene.background 赋一张纹理贴图就行,第 15 章会讲。

相机 PerspectiveCamera

透视相机模拟人眼效果:近大远小。构造函数有四个参数:

  • fov:视野角度(度数),越大看到的东西越多,变形也越明显
  • aspect:宽高比,画面宽度 ÷ 高度
  • near:近裁剪面,比它更靠近相机的东西不画
  • far:远裁剪面,比它更远的东西不画
const camera = new THREE.PerspectiveCamera(
  75, window.innerWidth / window.innerHeight, 0.1, 100
);

near 和 far 之间围成的区域叫视锥体(frustum),只有视锥体内的物体才被渲染。物体部分在视锥体外时,露在外面的部分会被裁掉。

fov 取多少合适?常见范围是 45~75。75 视野宽、透视感强,适合游戏;45 接近人眼专注范围、变形小,适合展示产品。同一个场景,fov 越大,物体在画面里显得越小。实际使用中,near 别设太小、far 别设太大,否则深度精度会出问题,远处物体可能出现闪烁。

相机默认在原点 (0, 0, 0),朝向 -z 方向,和立方体重叠,所以要把相机往后移:

camera.position.set(0, 0, 5);

相机摆好位置后,可以用 lookAt 让它看向某个点(第 6 章详讲):

camera.lookAt(0, 0, 0); // 看向原点

网格 Mesh:几何体 + 材质

3D 物体里最常用的是网格(Mesh),它由两部分组成:几何体定义形状,材质定义外观。

const geometry = new THREE.BoxGeometry(2, 2, 2);                    // 2×2×2 的立方体形状
const material = new THREE.MeshBasicMaterial({ color: 0x00d4ff });  // 天蓝色外观
const cube = new THREE.Mesh(geometry, material);
  • BoxGeometry 是内置几何体,三个参数是宽、高、深
  • MeshBasicMaterial 是最基础的材质,不受光照影响,所以这个例子不需要加灯光
Note

r125 之前 BoxGeometry 叫 BoxBufferGeometry,现在已经没有 BoxBufferGeometry 了。看到老教程这么写,记得去掉 Buffer。

因为正对着立方体的一面,它看起来是个正方形。加一行旋转就有立体感:

cube.rotation.y = 0.5; // 绕 y 轴转 0.5 弧度

想让立方体换个位置,设置 position:

cube.position.x = 1;          // 单独设置一个轴
cube.position.set(1, 0, 0);   // 或者三个轴一起设置

试着再加一个立方体,把下面几行插进「3. 立方体」之后:

const cube2 = new THREE.Mesh(geometry, material); // 可以复用同一个几何体和材质
cube2.position.set(3, 0, 0);
scene.add(cube2);

一个几何体、一个材质,可以创建任意多个网格,内存只占一份。这是 three.js 常用的省内存技巧。

渲染器 WebGLRenderer

渲染器负责把场景画出来,需要三步配置:

const renderer = new THREE.WebGLRenderer();               // 1. 创建
renderer.setSize(window.innerWidth, window.innerHeight);  // 2. 画布尺寸
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 3. 高清屏适配

renderer.domElement 是渲染器自动创建的 canvas 元素,需要手动放进页面:

document.body.appendChild(renderer.domElement);

不调 setSize 的话,canvas 默认是 300×150 的小画布。页面样式里写 canvas { display: block; },是为了去掉 canvas 默认的内联样式空隙,避免页面底部多出一条白边。

Tip

不调 setPixelRatio 的话,高分屏(Retina)上画面会模糊。先照抄这个写法,第 5 章详解。

渲染一帧

最后一行是真正出图的一步:

renderer.render(scene, camera);

它把场景从相机的视角「拍一张照片」画到 canvas 上。注意:这只渲染一帧静态画面。场景有任何变化(物体动了、相机动了),都要重新调用 render 才能看到新画面。

让立方体转起来(渲染循环预告)

把第 5 步的 render 换成下面的循环,立方体就会持续旋转:

function animate() {
  requestAnimationFrame(animate);   // 请求下一帧,约每秒 60 次
  cube.rotation.y += 0.01;         // 每帧转一点
  renderer.render(scene, camera);  // 重新画一帧
}
animate();

requestAnimationFrame 是浏览器提供的动画循环接口,它会在每次屏幕刷新前调用回调,通常每秒 60 次,跟屏幕刷新率同步。用 setInterval 也能实现循环,但会被标签页切后台、帧率波动影响,效果远不如 requestAnimationFrame。

循环的原理、Clock 计时、帧率控制,第 7 章专门讲,这里先跑起来感受一下。

一帧大约 16.7 毫秒(1000 ÷ 60)。如果一帧的渲染耗时超过这个值,帧率就会掉到 30fps 甚至更低,画面开始卡顿。记住这个数字,后面做性能优化时用得上。

常见问题

  • 页面空白:先开控制台看报错,最常见是 file:// 打开导致模块加载失败
  • 黑屏:场景里没加物体,或者相机在物体内部
  • 画面被拉伸:aspect 和 setSize 的宽高比不一致
  • 只看到一片颜色:相机离物体太远或太近,调整 camera.position.z
  • 立方体看不到但也没报错:检查相机朝向,默认看向 -z,把物体放 -z 方向或移动相机
  • 旋转了但看不出变化:rotation 的单位是弧度,不是角度,转 90° 要写 Math.PI / 2
  • 只有背景色没有物体:确认 scene.add(cube) 写在了 renderer.render 之前

下一节深入渲染器,看看它还有哪些重要配置。