{T}

环境光与光照贴图

环境光和光照贴图是实现真实感渲染的重要技术,用于模拟间接光照和环境反射。

概述

环境光提供场景的基础照明,光照贴图则是预计算的光照信息,两者都能显著提升场景的真实感和性能。

环境光照系统架构

plaintext
环境光照技术
├── 实时环境光
│   ├── AmbientLight        环境光 - 均匀照明
│   └── HemisphereLight     半球光 - 天空/地面环境光
├── 环境贴图
│   ├── CubeTexture         立方体贴图
│   ├── Equirectangular     等距矩形贴图(HDR)
│   └── PMREM               预滤波贴图
├── 预计算光照
│   ├── Light Map           光照贴图
│   ├── AO Map              环境光遮蔽贴图
│   └── Light Probe         光照探针
└── 后处理效果
    └── SSAO                屏幕空间环境光遮蔽

技术对比

技术性能消耗真实感动态性适用场景
AmbientLight极低完全动态基础照明
HemisphereLight完全动态户外场景
环境贴图静态反射、间接光
光照贴图静态静态场景
SSAO动态细节增强

环境光技术

AmbientLight 环境光

最简单的环境光,均匀照亮场景:

javascript
import * as THREE from 'three';
 
// 基础环境光
const ambientLight = new THREE.AmbientLight(
  0x404040,  // 颜色
  0.5        // 强度
);
 
scene.add(ambientLight);
 
// 调整强度
ambientLight.intensity = 0.3;
 
// 调整颜色
ambientLight.color.set(0x87CEEB);  // 天空蓝

特点

  • 无方向,均匀照明
  • 不产生阴影
  • 性能消耗极低
  • 强度不宜过高,否则画面发白

用途

  • 基础照明,避免完全黑暗
  • 提升暗部亮度
  • 模拟间接光照的近似效果

不同场景的环境光配置

javascript
// 夜间场景
const nightAmbient = new THREE.AmbientLight(0x1a1a2e, 0.2);
 
// 室内场景
const indoorAmbient = new THREE.AmbientLight(0xffffee, 0.4);
 
// 户外场景
const outdoorAmbient = new THREE.AmbientLight(0xffffff, 0.3);
 
// 地下场景
const undergroundAmbient = new THREE.AmbientLight(0x222233, 0.15);

HemisphereLight 半球光

模拟天空和地面的环境光,提供自然的明暗过渡:

javascript
// 半球光
const hemisphereLight = new THREE.HemisphereLight(
  0x87CEEB,  // 天空颜色
  0x8B4513,  // 地面颜色
  0.6        // 强度
);
 
scene.add(hemisphereLight);
 
// 调整参数
hemisphereLight.color.set(0x87CEEB);        // 天空
hemisphereLight.groundColor.set(0x555555);  // 地面
hemisphereLight.intensity = 0.8;

特点

  • 有方向性(上下方向)
  • 不产生阴影
  • 提供自然的明暗过渡
  • 适合户外场景

不同时间段的半球光

javascript
// 清晨 - 暖色调天空
const morningLight = new THREE.HemisphereLight(
  0xffd4a0,  // 天空:暖橙色
  0x555555,  // 地面:灰色
  0.5
);
 
// 中午 - 明亮蓝天
const noonLight = new THREE.HemisphereLight(
  0x87CEEB,  // 天空:浅蓝色
  0x8B4513,  // 地面:棕色
  0.8
);
 
// 黄昏 - 橙红色天空
const sunsetLight = new THREE.HemisphereLight(
  0xff6b35,  // 天空:橙红色
  0x4a4a4a,  // 地面:深灰色
  0.4
);
 
// 夜晚 - 深蓝天空
const nightLight = new THREE.HemisphereLight(
  0x1a1a3e,  // 天空:深蓝色
  0x0a0a1a,  // 地面:深黑色
  0.2
);

环境贴图(Environment Map)

使用环境贴图提供真实的间接光照和反射:

javascript
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
 
// 加载 HDR 环境贴图
const rgbeLoader = new RGBELoader();
 
rgbeLoader.load('environment.hdr', (texture) => {
  // 设置映射方式
  texture.mapping = THREE.EquirectangularReflectionMapping;
  
  // 设置场景环境(用于间接光照和反射)
  scene.environment = texture;
  
  // 可选:作为背景
  scene.background = texture;
});

CubeTexture 环境贴图

javascript
const cubeTextureLoader = new THREE.CubeTextureLoader();
 
const envMap = cubeTextureLoader.load([
  'px.jpg', 'nx.jpg',  // 正负 X
  'py.jpg', 'ny.jpg',  // 正负 Y
  'pz.jpg', 'nz.jpg'   // 正负 Z
]);
 
scene.environment = envMap;
scene.background = envMap;

PMREMGenerator 预处理

PMREM(Prefiltered Mipmap Radiance Environment Map)可以生成高质量的环境贴图:

javascript
import { PMREMGenerator } from 'three';
 
const pmremGenerator = new PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
 
const rgbeLoader = new RGBELoader();
rgbeLoader.load('environment.hdr', (texture) => {
  // 从等距矩形贴图生成 PMREM
  const envMap = pmremGenerator.fromEquirectangular(texture).texture;
  
  scene.environment = envMap;
  
  // 清理原始贴图
  texture.dispose();
});
 
// 完成后清理
pmremGenerator.dispose();

材质使用环境贴图

javascript
const material = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  metalness: 1.0,     // 金属度越高,反射越明显
  roughness: 0.0,     // 粗糙度越低,反射越清晰
  envMap: scene.environment,
  envMapIntensity: 1.0
});
 
// 或者单独设置
material.envMap = envMap;
material.envMapIntensity = 2.0;
 
// 不同材质的 envMapIntensity 推荐值
// 金属材质:1.0 - 3.0
// 塑料材质:0.5 - 1.0
// 木头材质:0.1 - 0.5

envMapIntensity 效果

plaintext
envMapIntensity 值越大,环境反射越明显
 
envMapIntensity = 0.5: 微弱反射
envMapIntensity = 1.0: 正常反射
envMapIntensity = 2.0: 强烈反射
envMapIntensity = 3.0: 非常强烈反射

光照贴图(Light Map)

光照贴图概述

光照贴图是预计算的光照信息,存储在纹理中,可以显著提升性能:

javascript
// 加载光照贴图
const textureLoader = new THREE.TextureLoader();
const lightMap = textureLoader.load('lightmap.jpg');
 
// 使用光照贴图
const material = new THREE.MeshStandardMaterial({
  color: 0x888888,
  lightMap: lightMap,
  lightMapIntensity: 1.0
});
 
// 设置 UV2 坐标(光照贴图使用第二套 UV)
geometry.setAttribute('uv2', geometry.attributes.uv);

光照贴图特点

优点

  • 预计算,运行时性能好
  • 可以烘焙复杂光照效果
  • 支持全局光照、间接光照
  • 适合静态场景

缺点

  • 需要额外内存
  • 不支持动态光照变化
  • 需要正确的 UV2 坐标
  • 物体移动后光照不正确

光照贴图工作流

plaintext
光照贴图工作流程:
 
1. 建模阶段
   └── 创建 3D 模型
 
2. UV 展开阶段
   └── 创建第二套 UV(UV2)用于光照贴图
   └── 要求:无重叠、合理布局
 
3. 烘焙阶段
   └── 在 DCC 软件(Blender/3ds Max)中烘焙
   └── 或使用 Three.js LightMapGenerator
 
4. 导出阶段
   └── 导出模型(GLB/FBX)
   └── 导出光照贴图纹理
 
5. 使用阶段
   └── 加载模型和光照贴图
   └── 应用到材质

使用 Blender 烘焙光照贴图

  1. 在 Blender 中设置场景和光照
  2. 选择物体,设置 UV 展开为 "Lightmap Pack"
  3. 设置烘焙类型为 "Diffuse" 或 "Combined"
  4. 配置烘焙参数(分辨率、采样等)
  5. 执行烘焙
  6. 导出模型和光照贴图

光照贴图应用示例

javascript
// 完整的光照贴图工作流
function applyLightMap(mesh, lightMapTexture) {
  // 确保几何体有 uv2 属性
  if (!mesh.geometry.attributes.uv2) {
    mesh.geometry.setAttribute('uv2', mesh.geometry.attributes.uv);
  }
  
  // 设置光照贴图
  mesh.material.lightMap = lightMapTexture;
  mesh.material.lightMapIntensity = 1.0;
  
  // 光照贴图参数
  lightMapTexture.wrapS = THREE.RepeatWrapping;
  lightMapTexture.wrapT = THREE.RepeatWrapping;
}
 
// 加载带有光照贴图的模型
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => {
  scene.add(gltf.scene);
  
  // 如果模型已包含光照贴图,无需额外处理
  // 如果需要手动设置:
  // gltf.scene.traverse((child) => {
  //   if (child.isMesh) {
  //     child.material.lightMapIntensity = 1.0;
  //   }
  // });
});

环境光遮蔽(AO)

AO 贴图

环境光遮蔽贴图模拟角落和裂缝的阴影:

javascript
const aoMap = textureLoader.load('ao.jpg');
 
const material = new THREE.MeshStandardMaterial({
  color: 0x888888,
  aoMap: aoMap,
  aoMapIntensity: 1.0
});
 
// 设置 UV2(AO 贴图也使用 UV2)
geometry.setAttribute('uv2', geometry.attributes.uv);

AO 贴图效果

plaintext
AO 贴图:存储角落、缝隙等遮挡区域的阴影信息
 
    无 AO              有 AO
  ═══════           ═══════
  ┌─────┐           ┌─────┐
  │     │           │▓▓▓▓▓│ ← 角落变暗
  │     │           │▓   ▓│
  │     │           │▓▓▓▓▓│
  └─────┘           └─────┘

实时 SSAO

使用后处理实现屏幕空间环境光遮蔽:

javascript
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';
 
// 创建后处理器
const composer = new EffectComposer(renderer);
 
// 渲染通道
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
 
// SSAO 通道
const ssaoPass = new SSAOPass(scene, camera, window.innerWidth, window.innerHeight);
ssaoPass.kernelRadius = 16;
ssaoPass.minDistance = 0.005;
ssaoPass.maxDistance = 0.1;
composer.addPass(ssaoPass);
 
// 渲染
function animate() {
  requestAnimationFrame(animate);
  composer.render();
}

SSAOPass 参数说明

参数默认值说明
kernelRadius8采样半径
minDistance0.005最小距离
maxDistance0.1最大距离

光照探针(Light Probe)

光照探针用于捕获场景中的光照信息:

javascript
import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js';
 
// 从环境贴图生成光照探针
const probe = LightProbeGenerator.fromCubeTexture(envMap);
scene.add(probe);
 
// 或者从 CubeCamera 生成
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
 
// 渲染环境
cubeCamera.update(renderer, scene);
 
// 生成探针
const probe = LightProbeGenerator.fromCubeRenderTarget(renderer, cubeRenderTarget);
scene.add(probe);

组合使用

完整的环境光照系统

javascript
import * as THREE from 'three';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
 
async function setupLighting() {
  // 1. 环境光(基础照明)
  const ambientLight = new THREE.AmbientLight(0x404040, 0.3);
  scene.add(ambientLight);
  
  // 2. 半球光(天空地面环境光)
  const hemisphereLight = new THREE.HemisphereLight(
    0x87CEEB,  // 天空
    0x8B4513,  // 地面
    0.5
  );
  scene.add(hemisphereLight);
  
  // 3. 环境贴图(间接光照和反射)
  const rgbeLoader = new RGBELoader();
  const envMap = await new Promise((resolve) => {
    rgbeLoader.load('environment.hdr', resolve);
  });
  
  envMap.mapping = THREE.EquirectangularReflectionMapping;
  scene.environment = envMap;
  
  // 4. 主光源(太阳光)
  const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
  directionalLight.position.set(10, 20, 10);
  directionalLight.castShadow = true;
  scene.add(directionalLight);
  
  return {
    ambientLight,
    hemisphereLight,
    envMap,
    directionalLight
  };
}
 
// 应用到材质
function applyToMaterial(material, lightMap, aoMap) {
  // 光照贴图
  if (lightMap) {
    material.lightMap = lightMap;
    material.lightMapIntensity = 1.0;
  }
  
  // AO 贴图
  if (aoMap) {
    material.aoMap = aoMap;
    material.aoMapIntensity = 1.0;
  }
  
  // 环境贴图强度
  material.envMapIntensity = 0.8;
}

室内场景示例

javascript
function setupIndoorLighting() {
  // 微弱环境光
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
  scene.add(ambientLight);
  
  // 室内半球光
  const hemisphereLight = new THREE.HemisphereLight(
    0xffffff,  // 天花板
    0x888888,  // 地板
    0.3
  );
  scene.add(hemisphereLight);
  
  // 窗户光
  const windowLight = new THREE.DirectionalLight(0x87CEEB, 0.5);
  windowLight.position.set(5, 3, 0);
  windowLight.castShadow = true;
  scene.add(windowLight);
  
  // 室内灯光
  const ceilingLight = new THREE.PointLight(0xffffee, 0.8, 20);
  ceilingLight.position.set(0, 4, 0);
  scene.add(ceilingLight);
  
  // 光照贴图(预烘焙)
  const lightMap = new THREE.TextureLoader().load('indoor_lightmap.jpg');
  
  return { lightMap };
}

户外场景示例

javascript
function setupOutdoorLighting() {
  // 天空半球光
  const hemisphereLight = new THREE.HemisphereLight(
    0x87CEEB,  // 天空蓝
    0x8B4513,  // 地面棕
    0.6
  );
  scene.add(hemisphereLight);
  
  // 太阳光
  const sunLight = new THREE.DirectionalLight(0xffffff, 1);
  sunLight.position.set(50, 100, 50);
  sunLight.castShadow = true;
  sunLight.shadow.mapSize.width = 2048;
  sunLight.shadow.mapSize.height = 2048;
  sunLight.shadow.camera.near = 10;
  sunLight.shadow.camera.far = 200;
  sunLight.shadow.camera.left = -50;
  sunLight.shadow.camera.right = 50;
  sunLight.shadow.camera.top = 50;
  sunLight.shadow.camera.bottom = -50;
  scene.add(sunLight);
  
  // 环境贴图
  const envMap = new THREE.CubeTextureLoader().load([
    'sky_px.jpg', 'sky_nx.jpg',
    'sky_py.jpg', 'sky_ny.jpg',
    'sky_pz.jpg', 'sky_nz.jpg'
  ]);
  scene.environment = envMap;
  scene.background = envMap;
  
  return { sunLight, envMap };
}

性能优化

环境贴图优化

javascript
import { PMREMGenerator } from 'three';
 
// 使用 PMREM 生成优化后的环境贴图
const pmremGenerator = new PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
 
// 生成多级环境贴图
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
 
// 不同材质使用不同的 LOD 级别
material.envMapIntensity = 1.0;
 
// 清理
pmremGenerator.dispose();

光照贴图优化

javascript
// 使用合适的分辨率
const lightMapSize = 1024;  // 根据场景大小选择
 
// 压缩纹理格式
lightMap.format = THREE.RGBAFormat;
lightMap.type = THREE.UnsignedByteType;
 
// 复用光照贴图
const sharedLightMap = new THREE.TextureLoader().load('lightmap.jpg');
objects.forEach(obj => {
  obj.material.lightMap = sharedLightMap;
});

资源管理

javascript
// 正确释放资源
function disposeLighting() {
  // 释放环境贴图
  if (scene.environment) {
    scene.environment.dispose();
  }
  
  // 释放光照贴图
  if (lightMap) {
    lightMap.dispose();
  }
  
  // 释放 PMREM 生成器
  pmremGenerator.dispose();
}

常见问题解答

Q: 环境光太强导致画面发白?

A: 降低环境光强度,使用多层光照:

javascript
// 不要只使用环境光
ambientLight.intensity = 0.2;  // 降低环境光
 
// 添加方向光创造立体感
directionalLight.intensity = 1;

Q: 如何获取高质量的环境贴图?

A: 可以从以下来源获取:

javascript
// 1. 使用 HDR 贴图(推荐)
// 来源:Poly Haven, HDR Labs 等
 
// 2. 自己拍摄
// 使用 360 度相机或全景拼接
 
// 3. 使用 Three.js 内置
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
 
const pmremGenerator = new PMREMGenerator(renderer);
const envMap = pmremGenerator.fromScene(new RoomEnvironment()).texture;
scene.environment = envMap;

Q: 光照贴图 UV2 坐标如何生成?

A: 在 DCC 软件中生成或使用 Three.js 工具:

javascript
// 方法一:复用 UV1(适用于简单情况)
geometry.setAttribute('uv2', geometry.attributes.uv);
 
// 方法二:使用 Blender 烘焙
// 1. 选择物体
// 2. UV 编辑器 -> UV -> Lightmap Pack
// 3. 烘焙光照贴图
 
// 方法三:程序化生成(复杂场景不推荐)

Q: SSAO 效果不理想?

A: 调整 SSAO 参数:

javascript
// 调整采样半径
ssaoPass.kernelRadius = 16;  // 增大更明显
 
// 调整距离范围
ssaoPass.minDistance = 0.005;
ssaoPass.maxDistance = 0.1;
 
// 输出调试
ssaoPass.output = SSAOPass.OUTPUT.SSAO;  // 只显示 SSAO

Q: 反射物体看起来不真实?

A: 检查材质和环境贴图设置:

javascript
// 金属材质需要正确的设置
material.metalness = 1.0;
material.roughness = 0.0;
 
// 确保设置了环境贴图
scene.environment = envMap;
 
// 调整环境贴图强度
material.envMapIntensity = 1.5;

最佳实践

  1. 组合使用:环境光 + 半球光 + 环境贴图 + 主光源
  2. 合理强度:避免环境光过强导致画面发白
  3. 环境贴图选择:选择与场景风格匹配的环境贴图
  4. 光照贴图适用:静态场景使用光照贴图提升性能
  5. AO 增强:使用 AO 贴图增加角落细节
  6. 资源管理:及时释放不再使用的贴图资源

相关链接