Guida allo sviluppo dell'ottenimento dei dati Mesh della scena
Quando ricostruisce i dati LCC, l'LCC Unity SDK genera anche dati Mesh usati per le collisioni. Oltre al rilevamento delle collisioni, l'SDK offre la capacità di estrarre questi dati Mesh, convertirli in oggetti Mesh di Unity e visualizzarli, il che è comodo per scenari come l'esportazione e il baking della NavMesh.
Di seguito viene usato un esempio completo per descrivere i passaggi per ottenere in modo sincrono tutti i dati Mesh e per ottenere su richiesta i dati Mesh di un intervallo specificato.
Configurazione dell'ambiente
Crea una scena completamente nuova in Unity. Unity crea Camera principale e Luce direzionale per impostazione predefinita.
Configura la Camera principale
Seleziona Camera principale nella finestra Gerarchia e imposta le sue proprietà nella finestra Inspector come mostrato di seguito.

Imposta le proprietà della Camera principale
Crea LCCManager
Fai clic con il pulsante destro su un'area vuota nella finestra Gerarchia, seleziona Crea vuoto dal menu a comparsa per creare un GameObject vuoto, assegna il nome LCCManager, normalizza il suo Transform e aggiungi lo script LCCManager tramite Aggiungi componente.

Crea il GameObject LCCManager e aggiungi lo script LCCManager
Crea LCCRenderer
Fai clic con il pulsante destro su un'area vuota nella finestra Gerarchia, seleziona Crea vuoto per creare un GameObject vuoto, assegna il nome LCCRenderer e imposta il suo Transform su Rotation.x = -90 e Scale.x = -1.

Crea LCCRenderer e imposta il suo Transform (Rotation.x = -90, Scale.x = -1)
Nota:
Le impostazioni Transform del GameObject LCCRenderer (Rotation.x = -90, Scale.x = -1) lo allineano al sistema di coordinate dei dati LCC. Per i dettagli sull'allineamento del sistema di coordinate, vedi "Sistema di coordinate dei dati LCC" in Introduzione all'Unity SDK.
Prerequisiti dei dati
Il prerequisito affinché l'ottenimento dei dati Mesh abbia effetto è che la scena contenga i corrispondenti dati Mesh di collisione (generati quando LCC Studio la ricostruisce). Inoltre, questa capacità ha i seguenti vincoli:
- Solo PC / Mac, e sono supportate solo le origini dati locali (le origini dati HTTP non sono supportate).
- Può essere chiamata solo sul thread principale.
- I dati in formato nativo (PLY / SOG / SPZ / splat / ksplat) non contengono dati Mesh di collisione; la chiamata dei metodi correlati restituisce
null.
I dati ricostruiti in versioni precedenti potrebbero non contenere dati di collisione, nel qual caso la chiamata di GetMeshes() restituisce null.
Ottieni in modo sincrono tutti i dati Mesh
Ottenere in modo sincrono tutti i dati Mesh significa leggere i dati geometrici di tutti i blocchi Mesh di collisione nella scena in una volta sola, implementato tramite il metodo Renderer.GetMeshes().
Crea un nuovo script C# denominato Meshes.cs con il seguente codice:
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] avanzamento caricamento: {p:P1}"),
() =>
{
Debug.Log($"[Meshes] file caricato: {m_FilePath}");
m_isLoaded = true;
m_manager.SetAutoFov(false);
},
e => Debug.LogError($"[Meshes] errore di caricamento del file: {e}")
);
}
public void onGetAllMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] I dati non hanno terminato il caricamento; impossibile ottenere i Meshes.");
return;
}
var meshes = m_renderer.GetMeshes();
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Nessun Mesh ottenuto (i dati potrebbero non contenere collider).");
return;
}
VisualizeMeshes(meshes);
}
private void VisualizeMeshes(List<LCCMeshBlock> meshes)
{
// Pulisci il risultato di visualizzazione precedente per evitare accumuli
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 };
// Il numero di vertici di un singolo blocco può superare 65535, quindi devono essere usati indici a 32 bit, altrimenti la geometria verrà corrotta
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] Creati GameObject e visualizzati {meshes.Count} blocchi Mesh.");
}
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'ordine di avvolgimento dei triangoli della mesh di collisione non è garantito coerente; disabilita il back-face culling per una visualizzazione completa
mat.SetFloat("_Cull", (float)CullMode.Off);
return mat;
}
}
Nota:
GetMeshes()è un'operazione sincrona pesante: legge in modo sincrono dal disco, analizza e alloca memoria. Nelle scene grandi, ad es. un dataset di 262 blocchi, potrebbe dover leggere una grande quantità di dati dal disco e allocare da diversi GB a decine di GB di memoria gestita. Chiamalo una volta e memorizza tu stesso il risultato in cache; non chiamarlo mai a ogni fotogramma.vertices/boundsLocalsono nel sistema di coordinate locale dell'oggetto, quindi associare il GameObject di visualizzazione sottoRenderer.Transformlo allinea automaticamente.- Il numero di vertici di un singolo blocco può superare 65535 (massimo misurato 390.656); prima di costruire il tuo
Mesh, devi impostaremesh.indexFormat = IndexFormat.UInt32, altrimenti la geometria verrà corrotta.- L'ordine di avvolgimento dei triangoli della mesh di collisione non è garantito coerente; l'esempio disabilita il back-face culling (
_Cull = Off) per una visualizzazione completa.
Associa lo script al GameObject LCCRenderer e, nell'Inspector, trascina il GameObject LCCManager sulla proprietà Manager e compila il File Path.
Poi crea un Button e associa il metodo onGetAllMeshes() al suo evento onClick.
Dopo l'esecuzione, fai clic sul Button per vedere tutti i Mesh di collisione nella scena.

Visualizza tutti i dati Mesh di collisione dopo aver fatto clic sul pulsante
Ottieni i dati Mesh su richiesta
Il recupero su richiesta significa leggere solo la parte specificata dei dati Mesh invece di leggere tutti i dati Mesh in una volta sola, per evitare un grande overhead di I/O e memoria. Di seguito viene usato come esempio la lettura dei blocchi Mesh entro un certo intervallo attorno alla camera.
Prima usa GetMeshBlockInfos() per ottenere i metadati di tutti i blocchi (inclusi i box di delimitazione in spazio mondo), calcola la distanza dal centro della camera di rendering alla superficie del BBOX del blocco in coordinate mondo, filtra gli id dei blocchi con una distanza inferiore a 100 m, e poi usa il parametrizzato GetMeshes(ids) per leggere solo quei blocchi.
Aggiungi i seguenti metodi a Meshes.cs:
public void onRequireMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] I dati non hanno terminato il caricamento; impossibile ottenere i Meshes.");
return;
}
// 1. Lettura a zero I/O di tutti i metadati dei blocchi, filtrando i blocchi entro 100 m tramite la distanza camera-superficie BBOX
var infos = m_renderer.GetMeshBlockInfos();
if (infos == null || infos.Count == 0)
{
Debug.LogWarning("[Meshes] Nessuna informazione Mesh ottenuta (i dati potrebbero non contenere collider).");
return;
}
Vector3 camPos = m_camera.transform.position;
var ids = new List<string>();
foreach (var info in infos)
{
// Quando la camera è dentro il BBOX, ClosestPoint restituisce la camera stessa e la distanza è 0, quindi viene selezionato anche quel blocco
if (calculateMinDistanceToAABB(info.boundsWorld, camPos) < 100f)
ids.Add(info.id);
}
if (ids.Count == 0)
{
Debug.LogWarning("[Meshes] Nessun blocco Mesh entro 100 m dalla camera.");
return;
}
// 2. Leggi solo i blocchi filtrati per id e visualizzali
var meshes = m_renderer.GetMeshes(ids);
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Impossibile ottenere i Meshes per id.");
return;
}
VisualizeMeshes(meshes);
Debug.Log($"[Meshes] Selezionati {ids.Count} blocchi entro 100 m dalla camera, visualizzati {meshes.Count}.");
}
// Calcola la distanza più vicina da un punto alla superficie dell'AABB (restituisce 0 quando il punto è dentro l'AABB)
private static float calculateMinDistanceToAABB(Bounds _aabb, Vector3 _point)
{
Vector3 p = _aabb.ClosestPoint(_point);
return Vector3.Distance(p, _point);
}
Nota:
GetMeshBlockInfos()è un'operazione a zero I/O, zero parsing che legge solo i metadati già in memoria; puoi chiamarla liberamente.- Il filtro della distanza usa
boundsWorld(box di delimitazione in spazio mondo), e il centro della camera prendeCamera.transform.position.- Usa
Bounds.ClosestPointper calcolare la distanza più vicina da un punto alla superficie dell'AABB: quando la camera è dentro il BBOX,ClosestPointrestituisce la camera stessa e la distanza è 0, quindi viene selezionato anche quel blocco.- Il raggio di filtro
100f(unità: metri) nell'esempio può essere regolato secondo necessità.
Crea un Button e associa onRequireMeshes() al suo evento onClick.
Dopo l'esecuzione, fai clic sul pulsante per vedere i blocchi Mesh entro 100 m dalla camera.

Ottieni su richiesta i dati Mesh entro 100 m dalla camera
Punti chiave
I passaggi chiave per ottenere e visualizzare i dati Mesh della scena:
- Completa la preconfigurazione come
SetPlatformTypee carica la scena LCC tramiteGetRender+Load. - Conferma che i dati contengano dati Mesh di collisione (i formati nativi o i dati ricostruiti in versioni precedenti potrebbero non contenerli).
- Recupero completo: chiama
GetMeshes()per leggere tutti i blocchi in una volta sola e memorizza tu stesso il risultato in cache per evitare chiamate ripetute. - Recupero su richiesta: usa prima
GetMeshBlockInfos()per ottenere i box di delimitazione mondo dei blocchi, filtra gli id per distanza dalla camera, poi usaGetMeshes(ids)per leggere solo i blocchi di destinazione. - Durante la visualizzazione, associa il GameObject sotto
Renderer.Transformper allinearlo alle coordinate locali e costruisci il Mesh conIndexFormat.UInt32.
API per il recupero dei Mesh
I seguenti metodi sono tutti su Renderer; devono essere chiamati dopo che la scena ha terminato il caricamento, e i dati devono contenere dati Mesh di collisione.
GetMeshBlockInfos
// Valore restituito: List<LCCMeshBlockInfo> → metadati dei blocchi Mesh di collisione; null quando non c'è alcun Mesh o non è disponibile
public List<LCCMeshBlockInfo> GetMeshBlockInfos()
Restituisce i metadati di tutti i blocchi Mesh di collisione (id, box di delimitazione mondo/locale, numero di vertici, numero di facce). Zero-I/O e zero-parsing; legge solo i metadati già in memoria.
Esempio di utilizzo:
var infos = renderer.GetMeshBlockInfos();
foreach (var info in infos)
{
Debug.Log($"{info.id}: verts={info.vertexCount}, faces={info.faceCount}, bbox={info.boundsWorld.size}");
}
Note:
- Solo PC / Mac + origine dati locale; solo thread principale.
- Uso tipico: usalo prima per ottenere il BBox mondo di ogni blocco, seleziona gli id necessari, poi chiama
GetMeshes(ids)per leggere solo quei blocchi. - I formati nativi (PLY / SOG / SPZ / splat / ksplat) non hanno Mesh e restituiscono
null(il che è normale e non viene registrato).
GetMeshes
// Valore restituito: List<LCCMeshBlock> → tutti i dati geometrici dei Mesh di collisione; null quando non disponibili
public List<LCCMeshBlock> GetMeshes()
Restituisce in modo sincrono i dati geometrici di tutti i blocchi Mesh di collisione (vertici + indici dei triangoli, nel sistema di coordinate locale). È un'operazione pesante (lettura sincrona dal disco + parsing + allocazione); chiamala una volta e memorizza tu stesso il risultato in cache, mai a ogni fotogramma.
Esempio di utilizzo:
var meshes = renderer.GetMeshes();
foreach (var block in meshes)
{
// block.vertices / block.triangles sono nel sistema di coordinate locale dell'oggetto
}
Note:
- Solo PC / Mac + origine dati locale; solo thread principale.
- Per scene grandi, usa invece
GetMeshBlockInfos()per selezionare gli id +GetMeshes(ids), per evitare di allocare una grande quantità di memoria gestita in una volta sola. - Il numero di vertici di un singolo blocco può superare 65535; prima di costruire il tuo Mesh, devi impostare
mesh.indexFormat = IndexFormat.UInt32.
GetMeshes (per id)
// Valore restituito: List<LCCMeshBlock> → dati geometrici dei Mesh di collisione per gli id specificati; null quando non ci sono corrispondenze
public List<LCCMeshBlock> GetMeshes(IEnumerable<string> _ids)
Restituisce in modo sincrono i dati geometrici dei blocchi Mesh di collisione con gli id specificati (sistema di coordinate locale). Gli id sono forniti da GetMeshBlockInfos().
| Parametro | Tipo | Descrizione |
|---|---|---|
_ids | IEnumerable<string> | La raccolta degli id dei blocchi di destinazione (fornita da GetMeshBlockInfos()) |
Esempio di utilizzo:
var ids = new List<string> { "block-0", "block-3" };
var meshes = renderer.GetMeshes(ids);
Note:
- Gli id sconosciuti vengono saltati con un avviso; gli id duplicati vengono deduplicati (non letti dal disco ripetutamente); l'ordine di restituzione è l'ordine di attraversamento degli id (dopo la deduplicazione).
- Passando
null, un input vuoto o nessuna corrispondenza si restituiscenullcon un avviso. - Gli altri vincoli sono gli stessi di
GetMeshes().
Struttura dati LCCMeshBlockInfo
public struct LCCMeshBlockInfo {
public string id; // id del nodo, corrispondente a LCCMeshBlock.id
public Bounds boundsWorld; // AABB in spazio mondo, usata per selezionare gli id
public Bounds boundsLocal; // AABB in spazio locale
public int vertexCount; // numero di vertici
public int faceCount; // numero di facce
}
| Campo | Tipo | Descrizione |
|---|---|---|
id | string | Id del nodo, corrispondente a LCCMeshBlock.id |
boundsWorld | Bounds | AABB in spazio mondo, usata per selezionare gli id |
boundsLocal | Bounds | AABB in spazio locale |
vertexCount | int | Numero di vertici |
faceCount | int | Numero di facce |
Struttura dati LCCMeshBlock
public struct LCCMeshBlock {
public string id; // id del nodo
public Vector3[] vertices; // vertici, sistema di coordinate locale
public int[] triangles; // indici dei triangoli, 3 per triangolo
public Bounds boundsLocal; // limiti nello stesso spazio dei vertici
}
| Campo | Tipo | Descrizione |
|---|---|---|
id | string | Id del nodo |
vertices | Vector3[] | Array di vertici, sistema di coordinate locale dell'oggetto |
triangles | int[] | Indici dei triangoli, 3 per triangolo |
boundsLocal | Bounds | Limiti nello stesso spazio dei vertici |
Nota:
vertices/boundsLocalsono entrambi nel sistema di coordinate locale dell'oggetto. Per visualizzarli in Unity, associa il GameObject che contiene il Mesh sottoRenderer.Transformper allinearli automaticamente. Il numero di vertici di un singolo blocco può superare 65535; prima di costruire il tuo Mesh, devi impostaremesh.indexFormat = IndexFormat.UInt32.