环境光与光照贴图
环境光和光照贴图是实现真实感渲染的重要技术,用于模拟间接光照和环境反射。
概述
环境光提供场景的基础照明,光照贴图则是预计算的光照信息,两者都能显著提升场景的真实感和性能。
环境光照系统架构
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.5envMapIntensity 效果:
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 烘焙光照贴图:
- 在 Blender 中设置场景和光照
- 选择物体,设置 UV 展开为 "Lightmap Pack"
- 设置烘焙类型为 "Diffuse" 或 "Combined"
- 配置烘焙参数(分辨率、采样等)
- 执行烘焙
- 导出模型和光照贴图
光照贴图应用示例:
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 参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
| kernelRadius | 8 | 采样半径 |
| minDistance | 0.005 | 最小距离 |
| maxDistance | 0.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; // 只显示 SSAOQ: 反射物体看起来不真实?
A: 检查材质和环境贴图设置:
javascript
// 金属材质需要正确的设置
material.metalness = 1.0;
material.roughness = 0.0;
// 确保设置了环境贴图
scene.environment = envMap;
// 调整环境贴图强度
material.envMapIntensity = 1.5;最佳实践
- 组合使用:环境光 + 半球光 + 环境贴图 + 主光源
- 合理强度:避免环境光过强导致画面发白
- 环境贴图选择:选择与场景风格匹配的环境贴图
- 光照贴图适用:静态场景使用光照贴图提升性能
- AO 增强:使用 AO 贴图增加角落细节
- 资源管理:及时释放不再使用的贴图资源
相关链接
- 光源类型
- 阴影系统
- Three.js 官方文档 - Environment Maps
- Poly Haven - 免费 HDR 环境贴图资源