Visual LabINTERACTIVE LEARNING
OpenGL 与 3D第 1 / 4 章

Worker 端:几何合并与序列化

按材质合并几何体,并整理可转移的 Buffer 与图片资源。

Web WorkerglTF性能
专题预计 44 分钟查看源文 ↗

worker

重写了加载过程

import CustGLTFLoader from '@/workers/gltf-loader.js'
import { LoadingManager, FrontSide, DoubleSide, Mesh } from 'three'
import { mergeGeometries } from './BufferGeometryUtils.js'

self.onmessage = async function (e) {
  const { isMainModel, files, model_id } = e.data
  postMessage({ type: 1 })
  const modelGroup = await loadAndMergeGLTF(model_id, files, isMainModel)
  if (modelGroup) {
    genMainModelMsg(modelGroup)
  }
}

async function genMainModelMsg(modelGroup) {
  const materialMaps = {}
  modelGroup.updateMatrixWorld()
  modelGroup.traverse(mesh => {
    if (mesh instanceof Mesh) {
      if (mesh.material.uuid in materialMaps) {
        materialMaps[mesh.material.uuid].meshes.push(mesh)
      } else {
        materialMaps[mesh.material.uuid] = {
          material: mesh.material,
          meshes: [mesh],
        }
      }
    }
  })
  const geometryMaps = {}
  for (const key in materialMaps) {
    const { material, meshes } = materialMaps[key]

    const geometryList = meshes.map((mesh, index) => {
      const geometry = mesh.geometry.clone() //没必要克隆
      geometry.applyMatrix4(mesh.matrixWorld)
      mesh.geometry.dispose()
      return geometry
    })
    const mergedGeometry = mergeGeometries(geometryList, true)
    if(mergedGeometry){
      mergedGeometry.computeVertexNormals()
    } else {
      // 合并失败,则不处理
      continue
    }
    geometryMaps[key] = {
      material,
      geometry: mergedGeometry,

      groups: mergedGeometry.groups,
      originalMeshes: meshes.map(mesh => {
        const originalGeometry = mesh.geometry
        return {
          boundingBox: originalGeometry.boundingBox.clone().applyMatrix4(mesh.matrixWorld),
          boundingSphere: originalGeometry.boundingSphere.clone().applyMatrix4(mesh.matrixWorld),
          assimpMeshName: mesh.userData.assimpMeshName,
        }
      }),
    }
  }
  const transferableObjects= []

  const geometryData = {}

  for (const key in geometryMaps) {
    const { material, geometry, originalMeshes } = geometryMaps[key]
    const buffers = {}
    for (const attributeName in geometry.attributes) {
      const attribute = geometry.attributes[attributeName]
      buffers[attributeName] = attribute.array.buffer
      transferableObjects.push(attribute.array.buffer)
    }

    if (geometry.index) {
      buffers['index'] = geometry.index.array.buffer
      transferableObjects.push(geometry.index.array.buffer)
    }

    const jsons = material.toJSON()
    //删除无用的属性
    for (const key of ['images', 'metadata', 'textures', 'envMapRotation']) {
      if (Reflect.has(jsons, key)) delete jsons[key]
    }

    geometryData[key] = {
      attributes: Object.keys(geometry.attributes).reduce((acc, name) => {
        const attr = geometry.attributes[name]
        acc[name] = {
          itemSize: attr.itemSize,
          normalized: attr.normalized,
          array: attr.array,
        }
        return acc
      }, {}),
      index: geometry.index
        ? {
            array: geometry.index.array,
          }
        : null,
      materialProps: {
        jsons,
        type: material.type,
        map: material.map?.source?.data instanceof ImageBitmap ? material.map.source.data : null,
        metalnessMap: material.metalnessMap?.source?.data instanceof ImageBitmap ? material.metalnessMap.source.data : null,
        roughnessMap: material.roughnessMap?.source?.data instanceof ImageBitmap ? material.roughnessMap.source.data : null,
      },
      originalMeshes: originalMeshes.map(mesh => ({
        boundingBox: {
          min: { x: mesh.boundingBox.min.x, y: mesh.boundingBox.min.y, z: mesh.boundingBox.min.z },
          max: { x: mesh.boundingBox.max.x, y: mesh.boundingBox.max.y, z: mesh.boundingBox.max.z },
        },
        boundingSphere: {
          center: { x: mesh.boundingSphere.center.x, y: mesh.boundingSphere.center.y, z: mesh.boundingSphere.center.z },
          radius: mesh.boundingSphere.radius,
        },
        assimpMeshName: mesh.assimpMeshName,
      })),
      groups: geometry.groups,
    }
    if (geometryData[key].materialProps.map) {
      transferableObjects.push(geometryData[key].materialProps.map)
    }
    material.dispose()
  }
  self.postMessage(
    {
      type: 4,
      geometryData,
    },
    transferableObjects
  )
}