灯光类型:六种灯一次看懂
本教程共 40 篇 · 第 17 篇 · 更新于 2026-08-14 · 约 8 分钟阅读
本节目标:认识 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 下到底是怎么计算的,以及为什么点光源强度要开到几百。