Comment charger un grand nombre de modèles sur la carte

Gaode map réalise le chargement d'un grand nombre de modèles

introduire

Un jour, le patron est venu me voir et m'a dit : "Actuellement, nous avons déployé plus de 50 000 lampadaires dans la région NS. Vous devriez trouver un moyen d'afficher tous les modèles de lampadaires sur la carte de la région NS. Ne faites pas ça type de zoom avant L'angle de vue réduit le nombre d'affichages par agrégation d'arbres quaternaires , et c'est une opération opportuniste d'afficher des centaines de modèles uniquement après s'être focalisé sur une zone locale."

J'ai été choqué immédiatement après avoir entendu cela, je ne m'attendais pas à ce que le patron perçoive mes tours et mette en avant des exigences plus élevées. En tant qu'ingénieur (non) fier, je ne peux pas être doux, arrangez-vous immédiatement.

Après quelques jours de recherches intermittentes, j'ai finalement eu cette affaire sous le PUA du patron. Le contenu de cet article est principalement de partager le plan de mise en œuvre de "Comment charger un modèle gltf de niveau 10 000 sur la carte Gaode tout en conservant une expérience fluide".

Honeycam_2023-07-19_13-50-09.gif

Glossaire

Grille : en infographie, une grille est un objet 3D représenté par une série de triangles, ce qui correspond à la classe Geometry dans three.js. La géométrie contient des informations sur les sommets, les arêtes et les faces d'un objet 3D.

Idées de mise en œuvre

pratique conventionnelle

La manière conventionnelle de charger le modèle gltf sur Internet consiste à utiliser Mesh.clone() pour copier le modèle par lots après que le chargeur ait chargé le modèle directement. Cette approche ne peut supporter qu'un ordre de grandeur inférieur à 1000. S'il s'agit d'un modèle à nombre complexe de faces, on estime qu'il y aura un phénomène de gel s'il y a plus de 100 faces.

approche de grille instanciée

Utilisez InstancedMesh pour rendre un grand nombre d'objets avec la même géométrie et les mêmes matériaux mais avec des transformations de monde différentes, particulièrement adaptées aux scènes telles que les forêts, le gravier, un grand nombre de lampadaires urbains et un grand nombre de poubelles urbaines. L'utilisation d'InstancedMesh peut réduire efficacement le nombre d'appels de dessin, améliorant ainsi les performances de rendu globales de l'application.

Cette méthode a une limitation. geometry, material, instanceCountLe premier paramètre geometry de instanceMesh() requiert que la géométrie du modèle soit un seul Mesh sans nœuds enfants ni nœuds frères. En d'autres termes, si votre modèle gltf est composé de 3 géométries indépendantes, alors vous devez instancier 3 instanceMesh, et vous avez besoin d'un moyen de vous assurer que les positions relatives des géométries sont fixes.

À propos de l'optimisation du modèle

Quelle que soit la solution adoptée pour obtenir les hautes performances de l'effet final, la coopération de la technologie de rendu et du modèle original est nécessaire. Peu importe l'efficacité de la technologie de rendu, si vous rencontrez un modèle avec trop de triangles, vous serez toujours impuissant, il faut donc optimiser au maximum le modèle tout en veillant à l'apparence du modèle.

Pour l'optimisation du modèle, nous pouvons partir des aspects suivants, et les étapes spécifiques seront partagées dans un autre article :

  1. Fusionner les maillages du modèle
  2. Simplifiez le nombre de visages
  3. Maquette exposition carte UV
  4. Supprimer les faces internes invisibles et les arêtes inutiles dans le modèle

Après ces étapes d'optimisation, j'ai réduit le nombre de faces triangulaires du modèle original de 9044 à 1492, et la taille du fichier a été réduite de 1060 Ko à 159 Ko. Et assurez-vous qu'il n'y a pas beaucoup de changement d'apparence.

lQDPJwGTqfXrdQjNAZDNAliwDaFT34SXnsgEuBs7u4ChAQ_600_400.jpg

Code d'implémentation

Prenons "charger 10 000 modèles sur la carte" comme exemple pour expliquer les étapes d'encodage.

Honeycam_2023-07-28_08-52-19.gif

1. Précharger le modèle et créer un maillage instancié

function loadModel(){
	this.loader = new GLTFLoader()
	this.loader.load(sourceUrl, function (gltf) {
	    // 获取模型
	    const mesh = gltf.scene.children[0]
	   
	    // 缓存模型
	    _models.model = mesh
			_models.animations = gltf.animations
	    resolve()
	  },
	  function (xhr) {
	    console.log((xhr.loaded / xhr.total * 100) + '% loaded')
	  },
	  function (error) {
	    console.log('An error happened' + error)
	  }
	)
}

async function initMesh(){
	await loadModel()

	const { model, animations } = _models
	const geometry = model.geometry
	const material = model.material

  // 创建实例化网格
  const instanceCount = this._data.length
	// 3个参数分别是几何体、材质、数据量
	const instanceMesh = new THREE.InstancedMesh(geometry, material, instanceCount)
	this.scene.add(instanceMesh)
}

2. Parcourir les données et ajuster le réseau instancié

const dummy = new THREE.Object3D()
const size = 4.0
dummy.scale.set(size, size, size)

const color = new THREE.Color()

for (let i = 0; i < instanceCount; i++) {
  const { id, modelId, altitude, angle, coords, lngLat } = this._data[i]
	// 调整空间位置
  dummy.position.set(coords[0], coords[1], (altitude === undefined ? 0 : altitude) + 0)
  dummy.updateMatrix()
  // 调整朝向
  if (angle !== undefined) {
    const fn = upAxis === 'x' ? 'rotateX' : (upAxis === 'z' ? 'rotateZ' : 'rotateY')
    dummy[fn](-angle / 180 * Math.PI)
  }
  // 将对dummy的调整复制到具体的网格中
  instanceMesh.setMatrixAt(i, dummy.matrix)
  // 给实例添加一个空的颜色,用于实现后面的拾取后颜色控制
	instanceMesh.setColorAt(i, color)
}
  1. Gérer l'événement de sélection de la souris de la grille, la sélection de la souris est toujours implémentée avec les rayons Raycaster
// 整个地图容器添加鼠标事件
const t = this
this._raycaster = new THREE.Raycaster()
this.container.addEventListener('mousemove', _.debounce(function (event) {
  t.onRay(event)
}, 100, true))

onRay (event) {
  const { scene, camera } = this
  // 归一化坐标
  const pickPosition = this.setPickPosition(event)
  // 通过摄像机和鼠标位置更新射线
  this._raycaster.*setFromCamera*(pickPosition, camera)
  // 计算射线和场景的交集
  const intersects = this._raycaster.intersectObjects(scene.children, true)

  if (typeof this.onPicked === 'function') {
    this.onPicked.apply(this, [{ targets: intersects, event }])
  }
  return intersects
}

// 获取鼠标在three.js 中的归一化坐标
setPickPosition (event) {
  const pickPosition = { x: 0, y: 0 }
  const rect = this.container.getBoundingClientRect()
  // // 将鼠标位置归一化为设备坐标, x 和 y 方向的取值范围是 (-1 to +1)
  pickPosition.x = (event.clientX / rect.width) * 2 - 1
  pickPosition.y = (event.clientY / rect.height) * -2 + 1
  return pickPosition
}

// 拾取到物体时,处理拾取事件
onPicked ({ targets, event }) {
  let attrs = null
  // 选中状态颜色
  const color = new THREE.Color(0xff0000)

  const cMesh = this.getParentObject(targets[0], { name: this._impactName })

  if (cMesh?.isInstancedMesh) {
    const intersection = this._raycaster.intersectObject(cMesh, false)
    // 获取当前网格成员的编号,其实是它的生成序号
		// 通过这个编号就可以操作指定的网格成员
    const { instanceId } = intersection[0]
    // 把拾取对象变成指定颜色
    cMesh.setColorAt(instanceId, color)
    // 强制颜色实时更新,必须!
    cMesh.instanceColor.needsUpdate = true
    return
  }

Résumer

Ce programme peut-il continuer à être optimisé ? La réponse est oui, tant que les faces visibles du modèle sont fusionnées et que les faces invisibles sont supprimées, nous pouvons en outre prendre en charge une plus grande quantité de données. Ici, j'ai essayé l'effet de simplifier le lampadaire en un cube et de charger plus de données 50 000. Regardez la fréquence d'images dans le coin supérieur gauche et les performances sont tout à fait correctes.

Bien sûr, pour obtenir une expérience fluide avec la solution WebGL, non seulement une optimisation technique est requise, mais également la prise en charge de terminaux dotés de bonnes performances (carte graphique discrète). À ce moment, vous pouvez dire en toute confiance au patron : vous pouvez charger 100 000 sur le modèle, mais vous devez ajouter de l'argent (tête de chien manuelle).

Honeycam_2023-07-19_20-07-55.gif

Liens connexes

TROIS maillage instancié

threejs.org/docs/index.…

Comment Blender fusionne-t-il complètement les objets

www.bilibili.com/video/BV1Lm…

Blender Exposition Bases UV

www.bilibili.com/video/BV1vZ…

Je suppose que tu aimes

Origine juejin.im/post/7260700447168823354
conseillé
Classement