Chapter 04

光照系统

五种光源特性详解、shadowMap 实时阴影完整配置、光照性能优化,最终打造专业级 3D 产品展示台

1. 五种光源特性

Three.js 的光源系统模仿真实世界的光照行为。不同光源有不同的物理模型和性能开销:

光源光照形状支持阴影性能典型用途
AmbientLight无方向(全局均匀)极高基础环境光,防止暗面全黑
DirectionalLight平行光(模拟太阳)室外场景主光源
PointLight从点向四周发散是(昂贵)灯泡、火焰、游戏道具
SpotLight圆锥形聚光中低台灯、舞台追光、产品展示
HemisphereLight天空/地面双色室外天光模拟(配合方向光)
// AmbientLight — 基础环境光,无方向无阴影
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

// DirectionalLight — 平行光(模拟太阳)
const dirLight = new THREE.DirectionalLight(0xfff5e0, 2); // 暖白光
dirLight.position.set(5, 10, 5);
scene.add(dirLight);
scene.add(dirLight.target); // 目标点(默认 origin)

// PointLight — 点光源
const pointLight = new THREE.PointLight(
  0xff8833,  // 颜色
  5,         // 强度
  10,        // 衰减距离(0=不衰减)
  2          // 衰减指数(2=物理正确)
);
pointLight.position.set(2, 3, 0);
scene.add(pointLight);

// SpotLight — 聚光灯
const spotLight = new THREE.SpotLight(
  0xffffff,  // 颜色
  10,        // 强度
  20,        // 最大距离
  Math.PI / 6, // 半锥角(30°)
  0.1,       // 边缘柔化(0=硬边,1=很软)
  1          // 衰减
);
spotLight.position.set(0, 5, 0);
scene.add(spotLight);
scene.add(spotLight.target);

// HemisphereLight — 半球光(天空色+地面色)
const hemi = new THREE.HemisphereLight(0x87ceeb, 0x4a3728, 0.6);
scene.add(hemi);

2. 阴影:castShadow / receiveShadow / shadowMap

实时阴影需要在三个地方分别开启:渲染器、光源、需要投射/接收阴影的物体。

// ── 步骤 1:开启渲染器阴影 ──
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 软阴影(推荐)
// 其他选项:
// THREE.BasicShadowMap    — 硬阴影,最快
// THREE.PCFShadowMap      — 多采样软化
// THREE.PCFSoftShadowMap  — 更软,性能适中
// THREE.VSMShadowMap      — 方差阴影,支持模糊

// ── 步骤 2:光源开启投影 ──
// DirectionalLight 和 SpotLight 支持阴影
dirLight.castShadow = true;

// 配置阴影贴图分辨率(越大越清晰,性能越低)
dirLight.shadow.mapSize.width  = 2048;
dirLight.shadow.mapSize.height = 2048;

// 调整阴影相机的视锥体(DirectionalLight)
// 尽量缩小 near/far 和 left/right 范围以提高阴影精度
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far  = 50;
dirLight.shadow.camera.left   = -10;
dirLight.shadow.camera.right  =  10;
dirLight.shadow.camera.top    =  10;
dirLight.shadow.camera.bottom = -10;

// SpotLight 阴影相机配置
spotLight.shadow.camera.near = 0.5;
spotLight.shadow.camera.far  = 30;
spotLight.shadow.camera.fov  = 40;

// ── 步骤 3:物体设置投影/接收阴影 ──
cube.castShadow    = true;  // 投射阴影(产生影子)
cube.receiveShadow = true;  // 接收阴影(显示他人的影子)

floor.castShadow    = false;
floor.receiveShadow = true; // 地面只接收,不投射

// 调试:可视化阴影相机范围
const shadowHelper = new THREE.CameraHelper(dirLight.shadow.camera);
scene.add(shadowHelper);
⚠️

Shadow Acne(阴影痤疮):物体在自身投影的阴影中出现条纹。解决方案:调整 shadow.bias(通常设为 -0.001-0.0001)。

3. 光照性能优化

// 光照辅助器——可视化光源位置和范围(开发时使用)
const dirHelper = new THREE.DirectionalLightHelper(dirLight, 2);
scene.add(dirHelper);

const pointHelper = new THREE.PointLightHelper(pointLight, 0.3);
scene.add(pointHelper);

const spotHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotHelper);

// 动态更新辅助器(光源移动时必须调用)
function animate() {
  requestAnimationFrame(animate);
  spotHelper.update(); // SpotLight 需要每帧更新辅助器
  renderer.render(scene, camera);
}

4. 实战:3D 产品展示台(PBR + 多光源 + 阴影)

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111827);

const camera = new THREE.PerspectiveCamera(50, w/h, 0.1, 100);
camera.position.set(0, 3, 6);

renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping; // 电影色调映射
renderer.toneMappingExposure = 1.2;

// ── 展台 ──
const pedestalGeo = new THREE.CylinderGeometry(1.5, 2, 0.3, 32);
const pedestalMat = new THREE.MeshStandardMaterial({
  color: 0x1a1a2e, roughness: 0.2, metalness: 0.8
});
const pedestal = new THREE.Mesh(pedestalGeo, pedestalMat);
pedestal.receiveShadow = true;
scene.add(pedestal);

// ── 产品(蓝色金属球)──
const product = new THREE.Mesh(
  new THREE.SphereGeometry(0.8, 64, 32),
  new THREE.MeshStandardMaterial({
    color: 0x049EF4, roughness: 0.1, metalness: 0.9
  })
);
product.position.y = 1.1;
product.castShadow = true;
product.receiveShadow = true;
scene.add(product);

// ── 地面 ──
const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(20, 20),
  new THREE.MeshStandardMaterial({ color: 0x0a0f1e, roughness: 0.8 })
);
floor.rotation.x = -Math.PI / 2;
floor.position.y = -0.15;
floor.receiveShadow = true;
scene.add(floor);

// ── 主光源(方向光)──
const keyLight = new THREE.DirectionalLight(0xfff8e7, 3);
keyLight.position.set(4, 8, 4);
keyLight.castShadow = true;
keyLight.shadow.mapSize.set(2048, 2048);
keyLight.shadow.camera.near = 1;
keyLight.shadow.camera.far  = 20;
keyLight.shadow.camera.left = keyLight.shadow.camera.bottom = -5;
keyLight.shadow.camera.right = keyLight.shadow.camera.top =   5;
keyLight.shadow.bias = -0.001;
scene.add(keyLight);

// ── 补光(蓝色点光源,左侧)──
const fillLight = new THREE.PointLight(0x0066ff, 2, 10);
fillLight.position.set(-4, 2, 2);
scene.add(fillLight);

// ── 轮廓光(背面)──
const rimLight = new THREE.SpotLight(0x00aaff, 5, 15, Math.PI/5, 0.3);
rimLight.position.set(0, 4, -5);
rimLight.target = product;
scene.add(rimLight, rimLight.target);

// ── 自动旋转 + OrbitControls ──
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.minDistance = 3;
controls.maxDistance = 15;

const clock = new THREE.Clock();
function animate() {
  requestAnimationFrame(animate);
  const t = clock.getElapsedTime();
  product.rotation.y = t * 0.4;
  controls.update();
  renderer.render(scene, camera);
}
animate();
ℹ️

三点打光法:专业摄影/3D 渲染中常用的基础布光方案,包含:
主光(Key Light):主要光源,决定基本光影结构,通常较强
补光(Fill Light):填补暗部,防止阴影太黑,通常颜色与主光互补
轮廓光(Rim/Back Light):从物体背面打光,分离物体与背景,增加立体感

本章小结:光照是决定 3D 场景品质的关键因素之一。实践中建议:环境光 0.3-0.5 亮度 + 方向光作为主光源 + 1-2 个补光点;阴影只给主光源开启;开发阶段添加光照辅助器,上线前移除。