在 Three.js 中,光照贴图(Lightmaps) 和 烘焙(Baking) 是创建高质量静态光照的关键技术。下面我将为你详细介绍这两种技术的概念、实现方法和最佳实践。
光照贴图是预先计算好的光照信息,存储为纹理贴图,用于模拟复杂光照效果(如全局光照、软阴影等),而不需要实时计算。
高性能:光照计算在预处理阶段完成
高质量:可实现逼真的全局光照效果
兼容性好:在各种硬件上都能稳定运行
javascript
// 示例模型要求:
// 1. 需要第二组UV坐标(用于光照贴图)
// 2. 合理展开UV,避免重叠和拉伸
// 3. 在Blender、Maya等软件中烘焙光照
javascript
import * as THREE from 'three';
// 加载模型和光照贴图
const loader = new THREE.GLTFLoader();
loader.load('model.glb', (gltf) => {
const model = gltf.scene;
// 遍历模型,应用光照贴图
model.traverse((child) => {
if (child.isMesh) {
const mesh = child;
// 确保材质支持光照贴图
if (mesh.material) {
// 加载光照贴图
const lightMapLoader = new THREE.TextureLoader();
const lightMap = lightMapLoader.load('lightmap.jpg');
// 配置光照贴图
lightMap.flipY = false; // 根据导出软件调整
// 应用到材质
mesh.material.lightMap = lightMap;
mesh.material.lightMapIntensity = 1.0;
// 重要:需要设置第二组UV
mesh.geometry.attributes.uv2 = mesh.geometry.attributes.uv;
// 禁用实时阴影(如果完全使用烘焙光照)
mesh.castShadow = false;
mesh.receiveShadow = false;
// 如果需要混合实时光照和烘焙光照
mesh.material.needsUpdate = true;
}
}
});
scene.add(model);
});
javascript
class LightmapSystem {
constructor() {
this.lightmaps = new Map();
this.lightmapIntensity = 1.0;
}
// 加载并应用光照贴图
async applyLightmap(model, lightmapPath, options = {}) {
const {
intensity = 1.0,
flipY = false,
blendMode = 'multiply'
} = options;
// 加载光照贴图
const texture = await this.loadTexture(lightmapPath, flipY);
model.traverse((child) => {
if (child.isMesh && child.material) {
this.setupMaterial(child, texture, intensity, blendMode);
}
});
return model;
}
// 设置材质
setupMaterial(mesh, lightmap, intensity, blendMode) {
const material = mesh.material;
// 如果材质是数组(多个材质)
if (Array.isArray(material)) {
material.forEach(mat => this.configureMaterial(mat, lightmap, intensity));
} else {
this.configureMaterial(material, lightmap, intensity);
}
// 确保有第二组UV
if (!mesh.geometry.attributes.uv2 && mesh.geometry.attributes.uv) {
mesh.geometry.setAttribute('uv2', mesh.geometry.attributes.uv.clone());
}
// 根据混合模式调整
if (blendMode === 'multiply') {
material.lightMap = lightmap;
}
material.needsUpdate = true;
}
// 配置单个材质
configureMaterial(material, lightmap, intensity) {
material.lightMap = lightmap;
material.lightMapIntensity = intensity;
// 调整材质属性以适应光照贴图
material.emissiveIntensity = 0.1; // 微小的自发光使阴影区域不纯黑
// 如果是标准或物理材质
if (material.isMeshStandardMaterial || material.isMeshPhysicalMaterial) {
material.envMapIntensity = 0.5; // 降低环境贴图强度
}
}
// 异步加载纹理
loadTexture(path, flipY = false) {
return new Promise((resolve, reject) => {
new THREE.TextureLoader().load(
path,
(texture) => {
texture.flipY = flipY;
texture.encoding = THREE.sRGBEncoding;
resolve(texture);
},
undefined,
reject
);
});
}
// 混合实时光照和烘焙光照
setupHybridLighting(model, options = {}) {
const {
ambientIntensity = 0.3,
directionalIntensity = 0.7,
shadowEnabled = false
} = options;
model.traverse((child) => {
if (child.isMesh) {
// 允许接收实时阴影(可选)
child.receiveShadow = shadowEnabled;
// 调整材质以更好地混合
if (child.material) {
// 提高环境光贡献
child.material.envMapIntensity = ambientIntensity;
// 如果是物理材质,调整光泽度
if (child.material.isMeshPhysicalMaterial) {
child.material.roughness = Math.min(
child.material.roughness * 1.2, 1.0
);
}
}
}
});
}
}
javascript
// 1. 光照贴图压缩
function optimizeLightmap(texture, maxSize = 2048) {
texture.generateMipmaps = true;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
// 自动调整尺寸
if (texture.image) {
const scale = Math.min(1, maxSize / Math.max(
texture.image.width,
texture.image.height
));
texture.image.width *= scale;
texture.image.height *= scale;
}
}
// 2. 批处理静态物体
function batchStaticMeshes(scene) {
const staticMeshes = [];
scene.traverse((child) => {
if (child.isMesh && child.material.lightMap) {
// 标记为静态
child.frustumCulled = true;
staticMeshes.push(child);
}
});
// 可以进一步合并几何体(如果材质相同)
return staticMeshes;
}
// 3. 光照贴图缓存
class LightmapCache {
constructor() {
this.cache = new Map();
this.maxSize = 10; // 最大缓存数量
}
get(key) {
const entry = this.cache.get(key);
if (entry) {
// 更新使用时间
entry.lastUsed = Date.now();
return entry.texture;
}
return null;
}
set(key, texture) {
if (this.cache.size >= this.maxSize) {
this.removeOldest();
}
this.cache.set(key, {
texture,
lastUsed: Date.now()
});
}
}
javascript
// 检查清单:
// 1. 确认第二组UV存在
if (!mesh.geometry.attributes.uv2) {
console.error('Missing UV2 coordinates');
mesh.geometry.setAttribute('uv2', mesh.geometry.attributes.uv.clone());
}
// 2. 检查纹理加载
textureLoader.load('lightmap.jpg', (texture) => {
console.log('Texture loaded:', texture.image.width, 'x', texture.image.height);
}, undefined, (error) => {
console.error('Failed to load texture:', error);
});
// 3. 检查材质类型
if (!mesh.material.lightMap) {
console.warn('Material does not support lightmaps:', mesh.material.type);
}
javascript
// 调整光照贴图强度
material.lightMapIntensity = 0.8; // 调整这个值
// 调整材质颜色
material.color.multiplyScalar(1.2); // 提亮基础颜色
// 使用色调映射
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
javascript
// 在建模软件中:
// 1. 确保UV岛之间有足够的填充(padding)
// 2. 避免UV拉伸
// 在Three.js中:
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
javascript
// 对于有动画的物体,可以使用多张光照贴图
class AnimatedLightmap {
constructor(mesh, lightmapTextures) {
this.mesh = mesh;
this.lightmaps = lightmapTextures;
this.currentFrame = 0;
// 创建材质副本以支持动画
this.originalMaterial = mesh.material.clone();
this.animatedMaterial = mesh.material.clone();
}
update(animationProgress) {
// 根据动画进度选择光照贴图
const frameIndex = Math.floor(animationProgress * this.lightmaps.length);
if (frameIndex !== this.currentFrame && this.lightmaps[frameIndex]) {
this.currentFrame = frameIndex;
this.animatedMaterial.lightMap = this.lightmaps[frameIndex];
this.mesh.material = this.animatedMaterial;
}
}
}
烘焙软件:
Blender(免费,功能强大)
Marmoset Toolbag(实时预览优秀)
Substance Painter(纹理烘焙一流)
Three.js相关工具:
THREE.LightMapExporter(自定义导出器)
glTF-Pipeline(优化glTF资源)
光照贴图和烘焙技术在Three.js中实现相对直接,关键在于:
前期准备:正确的UV布局和烘焙设置
资源优化:合理的光照贴图分辨率和压缩
混合照明:结合烘焙光照和实时光照达到最佳效果
性能监控:注意内存使用和渲染性能
对于完全静态的场景,推荐全部使用烘焙光照;对于半动态场景,可以结合使用烘焙和实时阴影;对于完全动态的场景,则需要依赖实时光照系统。