1. Three.js 模型优化与LOD技术

1.1. 模型优化基础

1.1.1. 几何体优化

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() // 自动计算法线

1.1.2. 纹理优化

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
})

1.2. LOD(细节层次)技术

1.2.1. 基础LOD实现

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)
  }
}

1.2.2. 自动LOD生成

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)
  }
}

1.2.3. 基于距离和屏幕空间的LOD

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;

1.3. 性能优化技巧

1.3.1. 实例化渲染

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)
  }
}

1.3.2. 按需加载

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()
  }
}

1.3.3. 内存管理

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
  }
}

1.4. 实用工具函数

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
  }
}

1.5. 最佳实践建议

  1. LOD级别设计

  2. 切换策略

  3. 性能监控

    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
   }
  1. 移动端优化

这些技术可以显著提升Three.js应用的性能,特别是在处理复杂场景和大量模型时。根据具体应用场景调整参数和策略。