Guide de développement de l'obtention des données Mesh de la scène
Lors de la reconstruction des données LCC, le LCC Unity SDK génère également des données Mesh utilisées pour les collisions. En plus de la détection de collision, le SDK offre la capacité d'extraire ces données Mesh, de les convertir en objets Mesh Unity et de les visualiser, ce qui est pratique pour des scénarios tels que l'exportation et le baking de NavMesh.
Ce qui suit utilise un exemple complet pour décrire les étapes d'obtention synchrone de toutes les données Mesh et d'obtention à la demande des données Mesh d'une plage spécifiée.
Configuration de l'environnement
Créez une toute nouvelle scène dans Unity. Unity crée Caméra principale et Lumière directionnelle par défaut.
Configurer la Caméra principale
Sélectionnez Caméra principale dans la fenêtre Hiérarchie, et définissez ses propriétés dans la fenêtre Inspecteur comme illustré ci-dessous.

Définir les propriétés de la Caméra principale
Créer LCCManager
Faites un clic droit dans une zone vide de la fenêtre Hiérarchie, sélectionnez Créer vide dans le menu contextuel pour créer un GameObject vide, nommez-le LCCManager, normalisez son Transform, et ajoutez-y le script LCCManager via Ajouter un composant.

Créer le GameObject LCCManager et ajouter le script LCCManager
Créer LCCRenderer
Faites un clic droit dans une zone vide de la fenêtre Hiérarchie, sélectionnez Créer vide pour créer un GameObject vide, nommez-le LCCRenderer, et définissez son Transform sur Rotation.x = -90 et Scale.x = -1.

Créer LCCRenderer et définir son Transform (Rotation.x = -90, Scale.x = -1)
Remarque :
Les réglages de Transform du GameObject LCCRenderer (Rotation.x = -90, Scale.x = -1) l'alignent avec le système de coordonnées des données LCC. Pour plus de détails sur l'alignement du système de coordonnées, voir « Système de coordonnées des données LCC » dans Introduction au SDK Unity.
Prérequis de données
Le prérequis pour que l'obtention des données Mesh prenne effet est que la scène porte les données Mesh de collision correspondantes (générées lorsque LCC Studio la reconstruit). De plus, cette capacité a les contraintes suivantes :
- PC / Mac uniquement, et seules les sources de données locales sont prises en charge (les sources de données HTTP ne le sont pas).
- Ne peut être appelé que sur le thread principal.
- Les données au format natif (PLY / SOG / SPZ / splat / ksplat) ne portent pas de données Mesh de collision ; l'appel des méthodes associées retourne
null.
Les données reconstruites dans des versions antérieures peuvent ne pas contenir de données de collision, auquel cas l'appel de GetMeshes() retourne null.
Obtenir synchroniquement toutes les données Mesh
Obtenir synchroniquement toutes les données Mesh signifie lire en une seule fois les données géométriques de tous les blocs Mesh de collision de la scène, implémenté via la méthode Renderer.GetMeshes().
Créez un nouveau script C# nommé Meshes.cs avec le code suivant :
using System.Collections.Generic;
using LCCCore;
using UnityEngine;
using UnityEngine.Rendering;
public class Meshes : MonoBehaviour
{
public LCCManager m_manager;
public string m_FilePath;
private LCCCore.Renderer m_renderer;
private bool m_isLoaded = false;
private Camera m_camera;
private const string kContainerName = "LCCVisualizedMeshes";
void Start()
{
m_camera = Camera.main;
m_isLoaded = false;
m_manager.SetPlatformType(PlatformType.PC);
m_renderer = m_manager.GetRender(this.transform);
m_renderer.Load(m_FilePath,
p => Debug.Log($"[Meshes] progression du chargement : {p:P1}"),
() =>
{
Debug.Log($"[Meshes] fichier chargé : {m_FilePath}");
m_isLoaded = true;
m_manager.SetAutoFov(false);
},
e => Debug.LogError($"[Meshes] erreur de chargement du fichier : {e}")
);
}
public void onGetAllMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] Les données n'ont pas fini de charger ; impossible d'obtenir les Meshes.");
return;
}
var meshes = m_renderer.GetMeshes();
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Aucun Mesh obtenu (les données peuvent ne pas contenir de colliders).");
return;
}
VisualizeMeshes(meshes);
}
private void VisualizeMeshes(List<LCCMeshBlock> meshes)
{
// Nettoyer le résultat de visualisation précédent pour éviter l'accumulation
Transform root = m_renderer.Transform;
Transform old = root.Find(kContainerName);
if (old != null) Destroy(old.gameObject);
var container = new GameObject(kContainerName).transform;
container.SetParent(root, false);
Material material = CreateVisualMaterial();
for (int i = 0; i < meshes.Count; i++)
{
LCCMeshBlock block = meshes[i];
if (block.vertices == null || block.vertices.Length == 0) continue;
if (block.triangles == null || block.triangles.Length < 3) continue;
var go = new GameObject(string.IsNullOrEmpty(block.id) ? $"MeshBlock_{i}" : block.id);
go.transform.SetParent(container, false);
var mesh = new Mesh { name = go.name };
// Le nombre de sommets d'un seul bloc peut dépasser 65535, des indices 32 bits doivent donc être utilisés, sinon la géométrie sera corrompue
mesh.indexFormat = IndexFormat.UInt32;
mesh.vertices = block.vertices;
mesh.triangles = block.triangles;
mesh.RecalculateNormals();
mesh.RecalculateBounds();
var mf = go.AddComponent<MeshFilter>();
mf.sharedMesh = mesh;
var mr = go.AddComponent<MeshRenderer>();
mr.sharedMaterial = material;
}
Debug.Log($"[Meshes] GameObjects créés et {meshes.Count} blocs Mesh visualisés.");
}
private Material CreateVisualMaterial()
{
Shader shader = Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard")
?? Shader.Find("Diffuse");
if (shader == null) return null;
var mat = new Material(shader) { color = new Color(0.7f, 0.7f, 0.7f, 1f) };
// L'ordre d'enroulement des triangles du mesh de collision n'est pas garanti cohérent ; désactiver le culling des faces arrière pour un affichage complet
mat.SetFloat("_Cull", (float)CullMode.Off);
return mat;
}
}
Remarque :
GetMeshes()est une opération lourde synchrone : elle lit de manière synchrone depuis le disque, analyse et alloue de la mémoire. Dans les grandes scènes, par ex. un jeu de données de 262 blocs, elle peut avoir besoin de lire une grande quantité de données disque et d'allouer de plusieurs Go à des dizaines de Go de mémoire managée. Appelez-la une fois et mettez vous-même le résultat en cache ; ne l'appelez jamais à chaque image.vertices/boundsLocalsont dans le système de coordonnées local de l'objet, donc attacher le GameObject de visualisation sousRenderer.Transforml'aligne automatiquement.- Le nombre de sommets d'un seul bloc peut dépasser 65535 (maximum mesuré 390 656) ; avant de construire votre propre
Mesh, vous devez définirmesh.indexFormat = IndexFormat.UInt32, sinon la géométrie sera corrompue.- L'ordre d'enroulement des triangles du mesh de collision n'est pas garanti cohérent ; l'exemple désactive le culling des faces arrière (
_Cull = Off) pour un affichage complet.
Attachez le script au GameObject LCCRenderer, et dans l'Inspecteur, faites glisser le GameObject LCCManager sur la propriété Manager et renseignez le File Path.
Créez ensuite un Bouton et associez la méthode onGetAllMeshes() à son événement onClick.
Après l'exécution, cliquez sur le Bouton pour voir tous les Meshes de collision de la scène.

Visualiser toutes les données Mesh de collision après avoir cliqué sur le bouton
Obtenir les données Mesh à la demande
La récupération à la demande signifie lire uniquement la partie spécifiée des données Mesh plutôt que de lire toutes les données Mesh en une fois, afin d'éviter un surcoût important d'E/S et de mémoire. Ce qui suit utilise la lecture des blocs Mesh dans une certaine plage autour de la caméra comme exemple.
Utilisez d'abord GetMeshBlockInfos() pour obtenir les métadonnées de tous les blocs (y compris les boîtes englobantes en espace mondial), calculez la distance du centre de la caméra de rendu à la surface BBOX du bloc en coordonnées mondiales, filtrez les ids de blocs dont la distance est inférieure à 100 m, puis utilisez la version paramétrée GetMeshes(ids) pour lire uniquement ces blocs.
Ajoutez les méthodes suivantes à Meshes.cs :
public void onRequireMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] Les données n'ont pas fini de charger ; impossible d'obtenir les Meshes.");
return;
}
// 1. Lecture sans E/S de toutes les métadonnées de blocs, filtrage des blocs à moins de 100 m par la distance caméra-surface BBOX
var infos = m_renderer.GetMeshBlockInfos();
if (infos == null || infos.Count == 0)
{
Debug.LogWarning("[Meshes] Aucune info de Mesh obtenue (les données peuvent ne pas contenir de colliders).");
return;
}
Vector3 camPos = m_camera.transform.position;
var ids = new List<string>();
foreach (var info in infos)
{
// Lorsque la caméra est à l'intérieur de la BBOX, ClosestPoint retourne la caméra elle-même et la distance est 0, elle est donc également sélectionnée
if (calculateMinDistanceToAABB(info.boundsWorld, camPos) < 100f)
ids.Add(info.id);
}
if (ids.Count == 0)
{
Debug.LogWarning("[Meshes] Aucun bloc Mesh à moins de 100 m de la caméra.");
return;
}
// 2. Lire uniquement les blocs filtrés par id et les visualiser
var meshes = m_renderer.GetMeshes(ids);
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Échec de l'obtention des Meshes par id.");
return;
}
VisualizeMeshes(meshes);
Debug.Log($"[Meshes] {ids.Count} blocs sélectionnés à moins de 100 m de la caméra, {meshes.Count} visualisés.");
}
// Calculer la distance la plus proche d'un point à la surface AABB (retourne 0 lorsque le point est à l'intérieur de l'AABB)
private static float calculateMinDistanceToAABB(Bounds _aabb, Vector3 _point)
{
Vector3 p = _aabb.ClosestPoint(_point);
return Vector3.Distance(p, _point);
}
Remarque :
GetMeshBlockInfos()est une opération sans E/S et sans analyse qui ne lit que les métadonnées déjà en mémoire ; vous pouvez l'appeler librement.- Le filtre de distance utilise
boundsWorld(boîte englobante en espace mondial), et le centre de la caméra prendCamera.transform.position.- Utilisez
Bounds.ClosestPointpour calculer la distance la plus proche d'un point à la surface AABB : lorsque la caméra est à l'intérieur de la BBOX,ClosestPointretourne la caméra elle-même et la distance est 0, donc ce bloc est également sélectionné.- Le rayon de filtrage
100f(unité : mètres) de l'exemple peut être ajusté selon les besoins.
Créez un Bouton et associez onRequireMeshes() à son événement onClick.
Après l'exécution, cliquez sur le bouton pour voir les blocs Mesh à moins de 100 m de la caméra.

Obtenir à la demande les données Mesh à moins de 100 m de la caméra
Étapes clés
Les étapes clés pour obtenir et visualiser les données Mesh de la scène :
- Terminez la pré-configuration telle que
SetPlatformType, et chargez la scène LCC viaGetRender+Load. - Confirmez que les données portent des données Mesh de collision (les formats natifs ou les données reconstruites dans des versions antérieures peuvent ne pas en contenir).
- Récupération complète : appelez
GetMeshes()pour lire tous les blocs en une fois et mettez vous-même le résultat en cache pour éviter les appels répétés. - Récupération à la demande : utilisez d'abord
GetMeshBlockInfos()pour obtenir les boîtes englobantes mondiales des blocs, filtrez les ids par distance de caméra, puis utilisezGetMeshes(ids)pour lire uniquement les blocs cibles. - Lors de la visualisation, attachez le GameObject sous
Renderer.Transformpour l'aligner avec les coordonnées locales, et construisez le Mesh avecIndexFormat.UInt32.
API de récupération de Mesh
Les méthodes suivantes se trouvent toutes sur Renderer ; elles doivent être appelées après la fin du chargement de la scène, et les données doivent porter des données Mesh de collision.
GetMeshBlockInfos
// Valeur de retour : List<LCCMeshBlockInfo> → métadonnées des blocs Mesh de collision ; null lorsqu'il n'y a pas de Mesh ou indisponible
public List<LCCMeshBlockInfo> GetMeshBlockInfos()
Retourne les métadonnées de tous les blocs Mesh de collision (id, boîte englobante mondiale/locale, nombre de sommets, nombre de faces). Sans E/S et sans analyse ; ne lit que les métadonnées déjà en mémoire.
Exemple d'utilisation :
var infos = renderer.GetMeshBlockInfos();
foreach (var info in infos)
{
Debug.Log($"{info.id}: verts={info.vertexCount}, faces={info.faceCount}, bbox={info.boundsWorld.size}");
}
Remarques :
- PC / Mac uniquement + source de données locale ; thread principal uniquement.
- Utilisation typique : utilisez-la d'abord pour obtenir la BBox mondiale de chaque bloc, choisissez les ids nécessaires, puis appelez
GetMeshes(ids)pour lire uniquement ces blocs. - Les formats natifs (PLY / SOG / SPZ / splat / ksplat) n'ont pas de Mesh et retournent
null(ce qui est normal et non journalisé).
GetMeshes
// Valeur de retour : List<LCCMeshBlock> → toutes les données géométriques des Mesh de collision ; null lorsqu'indisponible
public List<LCCMeshBlock> GetMeshes()
Retourne de manière synchrone les données géométriques de tous les blocs Mesh de collision (sommets + indices de triangles, dans le système de coordonnées local). C'est une opération lourde (lecture disque synchrone + analyse + allocation) ; appelez-la une fois et mettez-la vous-même en cache, jamais à chaque image.
Exemple d'utilisation :
var meshes = renderer.GetMeshes();
foreach (var block in meshes)
{
// block.vertices / block.triangles sont dans le système de coordonnées local de l'objet
}
Remarques :
- PC / Mac uniquement + source de données locale ; thread principal uniquement.
- Pour les grandes scènes, utilisez
GetMeshBlockInfos()pour choisir les ids +GetMeshes(ids)à la place, afin d'éviter d'allouer une énorme quantité de mémoire managée en une fois. - Le nombre de sommets d'un seul bloc peut dépasser 65535 ; avant de construire votre propre Mesh, vous devez définir
mesh.indexFormat = IndexFormat.UInt32.
GetMeshes (par id)
// Valeur de retour : List<LCCMeshBlock> → données géométriques des Mesh de collision pour les ids spécifiés ; null lorsqu'il n'y a pas d'impact
public List<LCCMeshBlock> GetMeshes(IEnumerable<string> _ids)
Retourne de manière synchrone les données géométriques des blocs Mesh de collision avec les ids spécifiés (système de coordonnées local). Les ids sont fournis par GetMeshBlockInfos().
| Paramètre | Type | Description |
|---|---|---|
_ids | IEnumerable<string> | La collection d'ids de blocs cibles (fournie par GetMeshBlockInfos()) |
Exemple d'utilisation :
var ids = new List<string> { "block-0", "block-3" };
var meshes = renderer.GetMeshes(ids);
Remarques :
- Les ids inconnus sont ignorés avec un avertissement ; les ids dupliqués sont dédupliqués (pas de lecture disque répétée) ; l'ordre de retour est l'ordre de parcours des ids (après déduplication).
- Passer
null, une entrée vide ou aucun impact retournenullavec un avertissement. - Les autres contraintes sont identiques à
GetMeshes().
Structure de données LCCMeshBlockInfo
public struct LCCMeshBlockInfo {
public string id; // id de nœud, correspondant à LCCMeshBlock.id
public Bounds boundsWorld; // AABB en espace mondial, utilisée pour sélectionner les ids
public Bounds boundsLocal; // AABB en espace local
public int vertexCount; // nombre de sommets
public int faceCount; // nombre de faces
}
| Champ | Type | Description |
|---|---|---|
id | string | Id de nœud, correspondant à LCCMeshBlock.id |
boundsWorld | Bounds | AABB en espace mondial, utilisée pour sélectionner les ids |
boundsLocal | Bounds | AABB en espace local |
vertexCount | int | Nombre de sommets |
faceCount | int | Nombre de faces |
Structure de données LCCMeshBlock
public struct LCCMeshBlock {
public string id; // id de nœud
public Vector3[] vertices; // sommets, système de coordonnées local
public int[] triangles; // indices de triangles, 3 par triangle
public Bounds boundsLocal; // bornes dans le même espace que les sommets
}
| Champ | Type | Description |
|---|---|---|
id | string | Id de nœud |
vertices | Vector3[] | Tableau de sommets, système de coordonnées local de l'objet |
triangles | int[] | Indices de triangles, 3 par triangle |
boundsLocal | Bounds | Bornes dans le même espace que les sommets |
Remarque :
vertices/boundsLocalsont tous deux dans le système de coordonnées local de l'objet. Pour les visualiser dans Unity, attachez le GameObject qui porte le Mesh sousRenderer.Transformpour qu'il s'aligne automatiquement. Le nombre de sommets d'un seul bloc peut dépasser 65535 ; avant de construire votre propre Mesh, vous devez définirmesh.indexFormat = IndexFormat.UInt32.