javascript
// 1. 简化几何体面数
import { SimplifyModifier } from 'three/examples/jsm/modifiers/SimplifyModifier'
const modifier = new SimplifyModifier()
const simplifiedGeometry = modifier.modify(originalGeometry, count) // 减少面数
// 2. 合并几何体
function mergeGeometries(meshes) {
const mergedGeometry = new THREE.BufferGeometry()
const geometries = meshes.map(mesh => mesh.geometry)
// 使用 BufferGeometryUtils 合并
return THREE.BufferGeometryUtils.mergeBufferGeometries(geometries)
}
// 3. 使用索引几何体
const geometry = new THREE.BoxGeometry()
geometry.computeVertexNormals() // 自动计算法线
javascript
// 1. 纹理压缩
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load('texture.jpg')
texture.minFilter = THREE.LinearMipmapLinearFilter // 使用mipmap
texture.generateMipmaps = true
// 2. 使用纹理图集
const atlasTexture = new THREE.TextureLoader().load('atlas.png')
// 为不同模型分配UV坐标
// 3. 压缩纹理格式
const loader = new THREE.CompressedTextureLoader()
loader.load('texture.dds', texture => {
material.map = texture
})
javascript
import { LOD } from 'three'
class ModelLOD {
constructor() {
this.lod = new THREE.LOD()
// 加载不同细节级别的模型
this.loadLODModels()
}
async loadLODModels() {
// 高细节(近距离)
const highDetail = await this.loadModel('model-high.glb')
highDetail.scale.set(1, 1, 1)
this.lod.addLevel(highDetail, 0) // 0-50单位距离使用
// 中细节
const mediumDetail = await this.loadModel('model-medium.glb')
mediumDetail.scale.set(1, 1, 1)
this.lod.addLevel(mediumDetail, 50) // 50-100单位距离
// 低细节
const lowDetail = await this.loadModel('model-low.glb')
lowDetail.scale.set(1, 1, 1)
this.lod.addLevel(lowDetail, 100) // 100+单位距离
// 简单替代(非常远)
const billboard = this.createBillboard()
this.lod.addLevel(billboard, 300)
}
createBillboard() {
// 创建公告牌或简单几何体
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
return new THREE.Mesh(geometry, material)
}
}
javascript
import { SimplifyModifier } from 'three/examples/jsm/modifiers/SimplifyModifier'
class AutoLODGenerator {
constructor(model, levels = 3) {
this.model = model
this.levels = levels
this.lod = new THREE.LOD()
}
generateLOD() {
const originalGeometry = this.model.geometry.clone()
const faceCount = originalGeometry.attributes.position.count / 3
for (let i = 0; i < this.levels; i++) {
const reductionFactor = 1 - (i + 1) / this.levels
const targetFaces = Math.max(faceCount * reductionFactor, 100)
const simplified = this.simplifyGeometry(originalGeometry, targetFaces)
const mesh = new THREE.Mesh(simplified, this.model.material.clone())
// 设置切换距离(可根据模型大小动态计算)
const distance = i * 50
this.lod.addLevel(mesh, distance)
}
return this.lod
}
simplifyGeometry(geometry, targetCount) {
const modifier = new SimplifyModifier()
const simplified = geometry.clone()
const iterations = Math.max(geometry.attributes.position.count - targetCount, 0)
return modifier.modify(simplified, iterations)
}
}
javascript
class AdvancedLOD {
constructor() {
this.lodManager = new LODManager()
this.camera = null
this.screenSizeThreshold = 0.1 // 屏幕空间阈值
}
update() {
if (!this.camera) return
this.lodManager.children.forEach(lod => {
// 计算对象在屏幕上的大小
const screenSize = this.calculateScreenSize(lod)
// 基于屏幕大小选择LOD级别
this.selectLODLevel(lod, screenSize)
})
}
calculateScreenSize(object3D) {
const boundingSphere = new THREE.Sphere()
object3D.geometry.computeBoundingSphere()
boundingSphere.copy(object3D.geometry.boundingSphere)
// 转换到世界空间
object3D.updateWorldMatrix(true, false)
boundingSphere.applyMatrix4(object3D.matrixWorld)
// 计算屏幕空间大小
const distance = this.camera.position.distanceTo(boundingSphere.center)
const radius = boundingSphere.radius
const screenHeight = 2 * Math.atan(radius / distance) * 180 / Math.PI
return screenHeight
}
}
示例中 LODManager 是一个自定义类,具体实现根据自己的项目需求定义,此处给出一个参考实现:
javascript
// LODManager.js
import * as THREE from 'three';
class LODManager {
constructor(camera, options = {}) {
this.camera = camera;
this.scene = null;
this.lodObjects = new Map(); // 存储所有LOD对象
this.enabled = true;
// 配置参数
this.options = {
updateInterval: 100, // 更新间隔(ms)
screenSizeThreshold: 0.05, // 屏幕空间阈值
distanceThreshold: 0.5, // 距离变化阈值(避免频繁切换)
debug: false,
...options
};
this.lastUpdateTime = 0;
this.lastCameraPosition = new THREE.Vector3();
this.debugObjects = [];
if (this.options.debug) {
this.setupDebug();
}
}
// 设置要管理的场景
setScene(scene) {
this.scene = scene;
this.scene.traverse((object) => {
if (object.isLOD) {
this.registerLOD(object);
}
});
}
// 注册LOD对象
registerLOD(lodObject) {
if (!lodObject.isLOD) {
console.warn('Object is not a THREE.LOD instance');
return;
}
const id = lodObject.uuid;
this.lodObjects.set(id, {
object: lodObject,
currentLevel: 0,
lastScreenSize: 0,
lastDistance: 0
});
}
// 移除LOD对象
removeLOD(lodObject) {
const id = lodObject.uuid;
this.lodObjects.delete(id);
}
// 主更新函数
update(timestamp) {
if (!this.enabled || !this.camera || this.lodObjects.size === 0) {
return;
}
// 控制更新频率
if (timestamp - this.lastUpdateTime < this.options.updateInterval) {
return;
}
this.lastUpdateTime = timestamp;
// 计算相机移动距离
const cameraMoved = this.lastCameraPosition.distanceTo(this.camera.position);
// 遍历所有LOD对象
this.lodObjects.forEach((lodData, id) => {
const lod = lodData.object;
// 使用Three.js内置的基于距离的更新
lod.update(this.camera);
// 同时基于屏幕空间优化
this.updateBasedOnScreenSize(lod, lodData, cameraMoved);
// 调试显示
if (this.options.debug) {
this.updateDebugInfo(lod, lodData);
}
});
// 保存相机位置
this.lastCameraPosition.copy(this.camera.position);
}
// 基于屏幕空间更新LOD级别
updateBasedOnScreenSize(lod, lodData, cameraMoved) {
// 计算对象在屏幕上的大小
const screenSize = this.calculateScreenSize(lod);
const distance = this.camera.position.distanceTo(lod.position);
// 检查是否需要更新(避免频繁切换)
const sizeChanged = Math.abs(screenSize - lodData.lastScreenSize) > this.options.screenSizeThreshold;
const distanceChanged = Math.abs(distance - lodData.lastDistance) > this.options.distanceThreshold;
if (sizeChanged || distanceChanged || cameraMoved > 5) {
// 根据屏幕大小选择最佳LOD级别
const targetLevel = this.selectLODLevelByScreenSize(lod, screenSize);
if (targetLevel !== lodData.currentLevel) {
this.switchLODLevel(lod, targetLevel, lodData.currentLevel);
lodData.currentLevel = targetLevel;
}
lodData.lastScreenSize = screenSize;
lodData.lastDistance = distance;
}
}
// 计算对象在屏幕上的大小(标准化)
calculateScreenSize(object3D) {
const boundingSphere = new THREE.Sphere();
// 获取对象的边界球
if (object3D.geometry) {
if (!object3D.geometry.boundingSphere) {
object3D.geometry.computeBoundingSphere();
}
boundingSphere.copy(object3D.geometry.boundingSphere);
} else {
// 对于LOD对象,使用所有级别的平均大小
let totalRadius = 0;
let count = 0;
object3D.children.forEach(child => {
if (child.geometry && child.geometry.boundingSphere) {
totalRadius += child.geometry.boundingSphere.radius;
count++;
}
});
boundingSphere.radius = count > 0 ? totalRadius / count : 1;
boundingSphere.center.set(0, 0, 0);
}
// 转换到世界空间
object3D.updateWorldMatrix(true, false);
boundingSphere.applyMatrix4(object3D.matrixWorld);
// 计算屏幕空间大小(像素高度)
const distance = this.camera.position.distanceTo(boundingSphere.center);
if (distance < boundingSphere.radius) {
return 1.0; // 相机在物体内部
}
// 计算物体在屏幕上的高度(弧度)
const angle = 2 * Math.atan(boundingSphere.radius / distance);
// 转换为屏幕标准化大小 (0-1)
const screenHeight = angle * 180 / Math.PI; // 角度
// 基于视场角标准化
const fov = this.camera.fov * (Math.PI / 180);
const normalizedSize = screenHeight / fov;
return Math.min(normalizedSize, 1.0);
}
// 根据屏幕大小选择LOD级别
selectLODLevelByScreenSize(lod, screenSize) {
const levels = lod.levels;
// 如果没有设置屏幕大小阈值,使用默认的距离阈值
if (levels.length === 0) return 0;
// 根据屏幕大小决定级别(屏幕越小,使用越低细节)
if (screenSize < 0.05) return Math.min(2, levels.length - 1); // 很小:低细节
if (screenSize < 0.2) return Math.min(1, levels.length - 1); // 中等:中细节
return 0; // 大:高细节
}
// 切换LOD级别(可添加过渡效果)
switchLODLevel(lod, newLevel, oldLevel) {
// 这里可以添加平滑过渡效果
// 例如:淡入淡出、几何体变形等
// 简单实现:直接切换可见性
lod.levels.forEach((level, index) => {
if (level.object) {
level.object.visible = (index === newLevel);
}
});
if (this.options.debug) {
console.log(`LOD switched: ${oldLevel} -> ${newLevel}, Screen size: ${this.calculateScreenSize(lod).toFixed(3)}`);
}
}
// 设置自定义LOD切换策略
setCustomLODStrategy(strategyFunction) {
this.customStrategy = strategyFunction;
}
// 获取LOD统计数据
getStats() {
let totalObjects = 0;
let visibleHigh = 0;
let visibleMedium = 0;
let visibleLow = 0;
this.lodObjects.forEach(lodData => {
totalObjects++;
if (lodData.currentLevel === 0) visibleHigh++;
else if (lodData.currentLevel === 1) visibleMedium++;
else visibleLow++;
});
return {
totalObjects,
visibleHigh,
visibleMedium,
visibleLow,
memorySavings: ((visibleMedium + visibleLow * 2) / totalObjects).toFixed(2)
};
}
// 调试设置
setupDebug() {
console.log('LODManager debug mode enabled');
}
updateDebugInfo(lod, lodData) {
// 可以添加调试信息显示
}
// 销毁
dispose() {
this.lodObjects.clear();
this.debugObjects.forEach(obj => {
if (obj.parent) obj.parent.remove(obj);
});
this.debugObjects = [];
}
}
export default LODManager;
javascript
class InstancedLOD {
constructor(modelPath, count = 100) {
this.instances = []
this.lodGroups = new Map() // 按LOD级别分组实例
this.init(modelPath, count)
}
async init(modelPath, count) {
// 加载模型
const model = await this.loadModel(modelPath)
// 创建实例化网格
const geometry = model.geometry
const material = model.material
const instancedMesh = new THREE.InstancedMesh(
geometry,
material,
count
)
// 为每个实例设置矩阵
const matrix = new THREE.Matrix4()
for (let i = 0; i < count; i++) {
matrix.setPosition(
Math.random() * 100 - 50,
0,
Math.random() * 100 - 50
)
instancedMesh.setMatrixAt(i, matrix)
}
// 按距离分组管理LOD
this.setupLODGroups(instancedMesh)
}
}
javascript
class ProgressiveLOD {
constructor() {
this.loadingManager = new THREE.LoadingManager()
this.priorityQueue = [] // 加载优先级队列
}
addToQueue(object, priority) {
this.priorityQueue.push({ object, priority })
this.priorityQueue.sort((a, b) => b.priority - a.priority)
}
update(camera) {
// 根据与相机的距离计算优先级
this.priorityQueue.forEach(item => {
const distance = camera.position.distanceTo(item.object.position)
item.priority = 1 / distance // 越近优先级越高
})
// 加载高优先级模型
this.loadNextInQueue()
}
}
javascript
class MemoryAwareLOD {
constructor(maxMemoryMB = 100) {
this.maxMemory = maxMemoryMB * 1024 * 1024 // 转换为字节
this.usedMemory = 0
this.cache = new Map()
}
loadModelWithMemoryCheck(url, lodLevel) {
const key = `${url}_${lodLevel}`
if (this.cache.has(key)) {
return Promise.resolve(this.cache.get(key))
}
return this.estimateMemoryUsage(url).then(estimatedSize => {
if (this.usedMemory + estimatedSize > this.maxMemory) {
this.freeMemory(estimatedSize)
}
return this.loadModel(url).then(model => {
this.cache.set(key, model)
this.usedMemory += estimatedSize
return model
})
})
}
freeMemory(requiredSize) {
// LRU(最近最少使用)策略释放内存
const entries = Array.from(this.cache.entries())
entries.sort((a, b) => a[1].lastUsed - b[1].lastUsed)
let freed = 0
while (freed < requiredSize && entries.length > 0) {
const [key, model] = entries.shift()
this.disposeModel(model)
this.cache.delete(key)
freed += model.estimatedSize
}
this.usedMemory -= freed
}
}
javascript
// LOD工具类
class LODUtils {
// 计算模型的合适LOD距离
static calculateLODDistances(model, baseDistance = 10) {
const bbox = new THREE.Box3().setFromObject(model)
const size = bbox.getSize(new THREE.Vector3())
const maxDim = Math.max(size.x, size.y, size.z)
// 基于模型大小调整距离
return {
high: 0,
medium: baseDistance * maxDim,
low: baseDistance * maxDim * 2,
billboard: baseDistance * maxDim * 4
}
}
// 渐进式细节增强
static async progressiveEnhancement(model, camera, renderer) {
// 初始加载低模
const lowPoly = await this.loadLowPolyVersion(model)
// 当相机靠近时加载高模
const checkDistance = () => {
const distance = camera.position.distanceTo(model.position)
if (distance < 50 && !model.highPolyLoaded) {
this.loadHighPolyVersion(model).then(highPoly => {
model.geometry = highPoly.geometry
model.highPolyLoaded = true
})
}
}
// 每帧检查
renderer.setAnimationLoop(() => {
checkDistance()
})
}
// 动态LOD切换
static createDynamicLOD(model, options = {}) {
const lod = new THREE.LOD()
const distances = options.distances || [0, 50, 100, 200]
distances.forEach((distance, index) => {
const simplified = this.createSimplifiedModel(model, index / distances.length)
lod.addLevel(simplified, distance)
})
// 添加平滑过渡
if (options.smoothTransition) {
lod.update = function(camera) {
THREE.LOD.prototype.update.call(this, camera)
// 当前级别与目标级别的混合
const currentLevel = this.getCurrentLevel()
if (this.previousLevel !== undefined && currentLevel !== this.previousLevel) {
this.transition(currentLevel, this.previousLevel)
}
this.previousLevel = currentLevel
}
}
return lod
}
}
LOD级别设计:
通常3-4个级别足够
最高级别面数控制在5000-10000
最低级别可简化为公告牌或简单几何体
切换策略:
使用屏幕空间而非绝对距离
添加滞后阈值防止频繁切换
考虑物体重要性(主角周围 vs 背景)
性能监控:
javascript
// 使用Stats.js监控
const stats = new Stats()
document.body.appendChild(stats.dom)
// 监控Draw Calls
renderer.info.render.calls
// 监控面数
function countTriangles(scene) {
let triangles = 0
scene.traverse(obj => {
if (obj.isMesh) {
triangles += obj.geometry.index ?
obj.geometry.index.count / 3 :
obj.geometry.attributes.position.count / 3
}
})
return triangles
}
移动端优化:
更激进的LOD距离
使用压缩纹理格式(ASTC, PVRTC)
减少实时光照和阴影
这些技术可以显著提升Three.js应用的性能,特别是在处理复杂场景和大量模型时。根据具体应用场景调整参数和策略。