首页 / Three.js 入门教程 / 灯光类型:六种灯一次看懂

Three.js 入门教程

灯光类型:六种灯一次看懂

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

Three.js灯光环境光平行光点光源聚光灯面光源

本节目标:认识 three.js 的六种内置灯光,搞清每种灯管什么用、参数怎么调,学会用”主光 + 辅光 + 环境光”的思路给场景布光。

材质决定物体怎么反光,灯光决定有没有光可反。上一章讲过,MeshStandardMaterial 这类 PBR 材质必须配灯光才能看见;MeshBasicMaterial 和 MeshNormalMaterial 不受灯光影响,天生自带”发光”。所以从这一章开始,示例默认使用受光材质加灯光。

真实世界的光可以分成两类。一类是直接从光源射来的直接光;另一类是经过墙壁、地面无数次反弹才进入眼睛的间接光。实时渲染算不起无数次反弹,于是 three.js 用两类灯光分别模拟:直接光负责直接光,环境光负责”假装”间接光。

Note

灯光不生效先查材质:MeshBasicMaterial、MeshNormalMaterial 不吃光。换成 MeshStandardMaterial 再试,这是新手最常见的”灯坏了”。

灯光的公共属性

所有灯光都继承自 Light 基类,Light 又继承自 Object3D。所以每盏灯都有:

  • color:光的颜色,默认 0xffffff 白色;
  • intensity:强度,默认 1;
  • position:位置(继承自 Object3D)。

有个特点要记住:灯光的 rotation 和 scale 一般没有效果,位置才是关键。环境光连位置都不看,放哪都一样。

AmbientLight:环境光

AmbientLight 往场景里均匀地加一层光,所有物体、所有面获得的光一样多。它没有方向,不能投阴影,也没有高光。方向信息全丢了,单独用它照出来的物体是”平的”,和 MeshBasicMaterial 几乎分不清。

实际使用中环境光只用来垫底,防止背光面全黑。一盏就够,多了只是把画面整体调亮。最经典的配置是”一盏平行光 + 一盏低强度环境光”。

HemisphereLight:半球光

HemisphereLight 可以看成环境光的升级版。它从天空颜色渐变到地面颜色:朝上的面偏 skyColor,朝下的面偏 groundColor。正午户外天空亮、地面暗,室内顶灯亮、地板暗,这个渐变正好模拟了”上方亮、下方暗”的真实感觉。

构造参数比环境光多一个地面色:

const hemi = new THREE.HemisphereLight(0x87ceeb, 0x3a5f0b, 0.5);
scene.add(hemi);

半球光同样不能投阴影、没有高光,一般和直接光搭配。它的位置会影响渐变方向,但通常不需要动。

DirectionalLight:平行光

DirectionalLight 模拟太阳:光源在无穷远,光线全部平行,强度不随距离衰减。场景里所有物体被照得一样亮,跟远近无关。

它从 position 指向 target 发光:

const dirLight = new THREE.DirectionalLight(0xffffff, 3);
dirLight.position.set(6, 8, 4); // 从 (6,8,4) 照向原点
scene.add(dirLight);

target 默认在原点 (0,0,0)。想移动 target,必须先把它加进场景:

scene.add(dirLight.target); // 这行容易忘
dirLight.target.position.set(2, 0, 0);

平行光是最常用的主光,可以投阴影(第 19 章细讲)。

PointLight:点光源

PointLight 模拟灯泡:从一个点向四面八方发光。构造参数比平行光多两个:

const point = new THREE.PointLight(0xffaa00, 100, 12, 2);
  • 第二个参数 intensity:强度,r185 下单位是坎德拉(cd),下一章细讲;
  • 第三个参数 distance:最远照射距离,0 表示不限;
  • 第四个参数 decay:衰减系数,默认 2,即平方反比衰减,物理正确,一般不改。

点光源适合台灯、蜡烛、路灯这类小范围光源,可以投阴影。注意物理模式下它衰减很快,强度值通常要开到几十、几百才看得见。

SpotLight:聚光灯

SpotLight 是带方向的锥形光,像手电筒或舞台追光。参数最多:

const spot = new THREE.SpotLight(0x00ccff, 1200, 20, 0.5, 0.4, 2);

依次是:color、intensity、distance(最远距离)、angle(锥角,最大 Math.PI/2)、penumbra(边缘柔化比例 0~1)、decay(衰减)。penumbra 越大,光圈边缘越柔和,实际项目中几乎都会设一点。它同样从 position 照向 target,可以投阴影。

RectAreaLight:面光源

RectAreaLight 从一个矩形面均匀发光,模拟窗户、灯箱、灯带。它有两个特别之处:只支持 PBR 材质(MeshStandardMaterial、MeshPhysicalMaterial),而且不能投阴影。使用前要先初始化:

import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';

RectAreaLightUniformsLib.init();

const rect = new THREE.RectAreaLight(0xffffff, 2, 4, 3);
rect.position.set(4, 5, -3);
rect.lookAt(0, 1, 0);
scene.add(rect);

构造参数是 color、intensity、width、height。它和点光源一样按平方反比衰减,但没有阴影支持,通常用来做补光,或者配合环境贴图提升质感。

调试辅助器

调光时看不见光源位置很痛苦。three.js 为每种灯都准备了辅助器,把光的形状画出来:

scene.add(new THREE.DirectionalLightHelper(dirLight, 1));
scene.add(new THREE.PointLightHelper(pointLight, 0.3));
scene.add(new THREE.SpotLightHelper(spotLight));
scene.add(new THREE.HemisphereLightHelper(hemi, 1));

前四个在核心包里,RectAreaLightHelper 在扩展目录:three/addons/helpers/RectAreaLightHelper.js。辅助器只用于调试,上线前记得删掉。

一次看全六种灯

下面这个例子把六种灯全放进一个场景,配合轨道控制器转着看。你会发现灯光多了以后,画面层次立刻丰富起来。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>17 灯光类型演示</title>
  <style>
    body { margin: 0; overflow: hidden; background: #111; }
    #info {
      position: fixed; top: 12px; left: 12px; color: #fff;
      font: 14px/1.6 sans-serif; background: rgba(0,0,0,.55);
      padding: 8px 12px; border-radius: 6px;
    }
  </style>
</head>
<body>
  <div id="info">环境光 + 半球光 + 平行光 + 点光源 + 聚光灯 + 面光源</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';
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
  import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js';

  // 渲染器
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // 场景与相机
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x1a1a2e);
  const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
  camera.position.set(10, 8, 14);
  camera.lookAt(0, 1, 0);

  const controls = new OrbitControls(camera, renderer.domElement);
  controls.target.set(0, 1, 0);

  // 地面
  const floor = new THREE.Mesh(
    new THREE.PlaneGeometry(20, 20),
    new THREE.MeshStandardMaterial({ color: 0x333355 })
  );
  floor.rotation.x = -Math.PI / 2;
  scene.add(floor);

  // 三个被照亮的物体
  const material = new THREE.MeshStandardMaterial({ color: 0x88aaff, roughness: 0.4, metalness: 0.1 });
  const sphere = new THREE.Mesh(new THREE.SphereGeometry(1.2, 32, 32), material);
  sphere.position.set(-3, 1.2, 0);
  const box = new THREE.Mesh(new THREE.BoxGeometry(1.8, 1.8, 1.8), material);
  box.position.set(0, 0.9, 0);
  const torus = new THREE.Mesh(new THREE.TorusGeometry(0.9, 0.35, 16, 48), material);
  torus.position.set(3, 1.2, 0);
  scene.add(sphere, box, torus);

  // 1. 环境光:全局均匀提亮
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.25);
  scene.add(ambientLight);

  // 2. 半球光:天空蓝 + 地面暗绿
  const hemiLight = new THREE.HemisphereLight(0x87ceeb, 0x3a5f0b, 0.5);
  scene.add(hemiLight);

  // 3. 平行光:模拟太阳
  const dirLight = new THREE.DirectionalLight(0xffffff, 3);
  dirLight.position.set(6, 8, 4);
  scene.add(dirLight);
  scene.add(new THREE.DirectionalLightHelper(dirLight, 1));

  // 4. 点光源:暖色灯泡
  const pointLight = new THREE.PointLight(0xffaa00, 150, 12, 2);
  pointLight.position.set(-4, 2.5, 2);
  scene.add(pointLight);
  scene.add(new THREE.PointLightHelper(pointLight, 0.3));

  // 5. 聚光灯:蓝色手电
  const spotLight = new THREE.SpotLight(0x00ccff, 1200, 20, 0.5, 0.4, 2);
  spotLight.position.set(0, 8, 3);
  scene.add(spotLight);
  scene.add(new THREE.SpotLightHelper(spotLight));

  // 6. 面光源:模拟窗户
  RectAreaLightUniformsLib.init();
  const rectLight = new THREE.RectAreaLight(0xffffff, 2, 4, 3);
  rectLight.position.set(4, 5, -3);
  rectLight.lookAt(0, 1, 0);
  scene.add(rectLight);
  scene.add(new RectAreaLightHelper(rectLight));

  // 窗口大小变化
  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  });

  // 渲染循环
  function animate() {
    requestAnimationFrame(animate);
    box.rotation.y += 0.01;
    torus.rotation.x += 0.01;
    torus.rotation.y += 0.015;
    controls.update();
    renderer.render(scene, camera);
  }
  animate();
  </script>
</body>
</html>
Tip

布光思路:先放一盏主光确定明暗方向,再用一盏辅光补暗部,最后用环境光或半球光垫底。户外用平行光当太阳、半球光当天空;室内用点光源或聚光灯当灯具、环境光垫底。

灯光性能与数量

每盏灯都会增加片元着色的计算量,场景里灯越多,帧率越低,移动端尤其明显。经验值:性能敏感的场景里,直接光控制在 4 盏以内,加一盏环境光垫底就够。需要大量光源时,可以研究光照贴图(baked lighting)这类预处理方案。

另外有个容易混的点:intensity 的数值含义因灯而异。r185 物理模式下,点光源、聚光灯按坎德拉算,平行光、环境光只是无量纲倍数。抄别人的布光参数时先确认灯的类型,直接搬数值很容易翻车。

小结

六种灯可以按用途分三类记忆:环境光、半球光负责垫底,平行光负责主光,点光源、聚光灯、面光源负责局部点缀。下一章我们讲这些光在 r185 下到底是怎么计算的,以及为什么点光源强度要开到几百。