Przewodnik pobierania danych siatki sceny
Podczas rekonstrukcji danych LCC LCC Unity SDK generuje również dane siatki używane do kolizji. Oprócz wykrywania kolizji SDK udostępnia możliwość wyodrębnienia tych danych siatki, konwersji ich na obiekty Mesh Unity i wizualizacji, co jest wygodne w scenariuszach takich jak eksportowanie i wypiekanie NavMesh.
Poniżej na kompletnym przykładzie opisano kroki synchronicznego pobierania wszystkich danych siatki oraz pobierania danych siatki z określonego zakresu na żądanie.
Konfiguracja środowiska
Utwórz zupełnie nową scenę w Unity. Unity domyślnie tworzy Kamerę główną i Światło kierunkowe.
Skonfiguruj Kamerę główną
Wybierz Kamerę główną w oknie Hierarchia i ustaw jej właściwości w oknie Inspektor, jak pokazano poniżej.

Ustaw właściwości Kamery głównej
Utwórz LCCManager
Kliknij prawym przyciskiem myszy pusty obszar w oknie Hierarchia, wybierz Utwórz pusty z menu podręcznego, aby utworzyć pusty GameObject, nazwij go LCCManager, znormalizuj jego Transform i dodaj do niego skrypt LCCManager przez Dodaj komponent.

Utwórz GameObject LCCManager i dodaj skrypt LCCManager
Utwórz LCCRenderer
Kliknij prawym przyciskiem myszy pusty obszar w oknie Hierarchia, wybierz Utwórz pusty, aby utworzyć pusty GameObject, nazwij go LCCRenderer i ustaw jego Transform na Rotation.x = -90 i Scale.x = -1.

Utwórz LCCRenderer i ustaw jego Transform (Rotation.x = -90, Scale.x = -1)
Uwaga:
Ustawienia Transform GameObject LCCRenderer (Rotation.x = -90, Scale.x = -1) wyrównują go z układem współrzędnych danych LCC. Szczegóły wyrównania układu współrzędnych znajdziesz w sekcji „Układ współrzędnych danych LCC" w Wprowadzenie do Unity SDK.
Wymagania wstępne danych
Warunkiem zadziałania pobierania danych siatki jest to, że scena przenosi odpowiednie dane siatki kolizji (generowane, gdy LCC Studio ją rekonstruuje). Ponadto ta możliwość ma następujące ograniczenia:
- Tylko PC / Mac i obsługiwane są tylko lokalne źródła danych (źródła danych HTTP nie są obsługiwane).
- Można wywoływać tylko na wątku głównym.
- Dane w formacie natywnym (PLY / SOG / SPZ / splat / ksplat) nie przenoszą danych siatki kolizji; wywołanie powiązanych metod zwraca
null.
Dane rekonstruowane we wcześniejszych wersjach mogą nie zawierać danych kolizji, w takim przypadku wywołanie GetMeshes() zwraca null.
Synchroniczne pobieranie wszystkich danych siatki
Synchroniczne pobieranie wszystkich danych siatki oznacza odczytanie naraz danych geometrii wszystkich bloków siatki kolizji w scenie, zaimplementowane przez metodę Renderer.GetMeshes().
Utwórz nowy skrypt C# o nazwie Meshes.cs z następującym kodem:
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] postęp ładowania: {p:P1}"),
() =>
{
Debug.Log($"[Meshes] plik załadowany: {m_FilePath}");
m_isLoaded = true;
m_manager.SetAutoFov(false);
},
e => Debug.LogError($"[Meshes] błąd ładowania pliku: {e}")
);
}
public void onGetAllMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] Dane nie zostały jeszcze załadowane; nie można pobrać siatek.");
return;
}
var meshes = m_renderer.GetMeshes();
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Nie pobrano żadnych siatek (dane mogą nie zawierać koliderów).");
return;
}
VisualizeMeshes(meshes);
}
private void VisualizeMeshes(List<LCCMeshBlock> meshes)
{
// Wyczyść poprzedni wynik wizualizacji, aby uniknąć gromadzenia
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 };
// Liczba wierzchołków pojedynczego bloku może przekroczyć 65535, więc muszą być użyte indeksy 32-bitowe, w przeciwnym razie geometria zostanie uszkodzona
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] Utworzono GameObjects i zwizualizowano {meshes.Count} bloków siatki.");
}
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) };
// Kolejność nawijania trójkątów siatki kolizji nie jest gwarantowana jako spójna; wyłącz odcinanie ścian tylnych dla pełnego wyświetlenia
mat.SetFloat("_Cull", (float)CullMode.Off);
return mat;
}
}
Uwaga:
GetMeshes()to synchroniczna ciężka operacja: synchronicznie odczytuje z dysku, analizuje i alokuje pamięć. W dużych scenach, np. zestawie danych o 262 blokach, może wymagać odczytu dużej ilości danych z dysku i alokacji od kilku GB do kilkudziesięciu GB pamięci zarządzanej. Wywołaj raz i sam buforuj wynik; nigdy nie wywołuj co klatkę.vertices/boundsLocalsą w lokalnym układzie współrzędnych obiektu, więc dołączenie GameObject wizualizacji podRenderer.Transformwyrównuje go automatycznie.- Liczba wierzchołków pojedynczego bloku może przekroczyć 65535 (zmierzone maksimum 390 656); przed zbudowaniem własnego
Meshmusisz ustawićmesh.indexFormat = IndexFormat.UInt32, w przeciwnym razie geometria zostanie uszkodzona.- Kolejność nawijania trójkątów siatki kolizji nie jest gwarantowana jako spójna; przykład wyłącza odcinanie ścian tylnych (
_Cull = Off) dla pełnego wyświetlenia.
Dołącz skrypt do GameObject LCCRenderer, a w Inspektorze przeciągnij GameObject LCCManager na właściwość Manager i wypełnij File Path.
Następnie utwórz Przycisk i powiąż metodę onGetAllMeshes() z jego zdarzeniem onClick.
Po uruchomieniu kliknij Przycisk, aby zobaczyć wszystkie siatki kolizji w scenie.

Wizualizuj wszystkie dane siatki kolizji po kliknięciu przycisku
Pobieranie danych siatki na żądanie
Pobieranie na żądanie oznacza odczytywanie tylko określonej części danych siatki zamiast odczytywania wszystkich danych siatki naraz, aby uniknąć dużego narzutu I/O i pamięci. Poniżej jako przykład użyto odczytywania bloków siatki w pewnym zakresie wokół kamery.
Najpierw użyj GetMeshBlockInfos(), aby uzyskać metadane wszystkich bloków (w tym prostokąty ograniczające w przestrzeni świata), oblicz odległość od środka kamery renderującej do powierzchni BBOX bloku we współrzędnych świata, odfiltruj id bloków o odległości mniejszej niż 100 m, a następnie użyj sparametryzowanego GetMeshes(ids), aby odczytać tylko te bloki.
Dodaj następujące metody do Meshes.cs:
public void onRequireMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] Dane nie zostały jeszcze załadowane; nie można pobrać siatek.");
return;
}
// 1. Odczyt zerowy I/O wszystkich metadanych bloków, filtrowanie bloków w promieniu 100 m według odległości kamera-powierzchnia BBOX
var infos = m_renderer.GetMeshBlockInfos();
if (infos == null || infos.Count == 0)
{
Debug.LogWarning("[Meshes] Nie pobrano informacji o siatkach (dane mogą nie zawierać koliderów).");
return;
}
Vector3 camPos = m_camera.transform.position;
var ids = new List<string>();
foreach (var info in infos)
{
// Gdy kamera jest wewnątrz BBOX, ClosestPoint zwraca samą kamerę, a odległość wynosi 0, więc blok również jest wybierany
if (calculateMinDistanceToAABB(info.boundsWorld, camPos) < 100f)
ids.Add(info.id);
}
if (ids.Count == 0)
{
Debug.LogWarning("[Meshes] Brak bloków siatki w promieniu 100 m od kamery.");
return;
}
// 2. Odczytaj tylko przefiltrowane bloki według id i zwizualizuj je
var meshes = m_renderer.GetMeshes(ids);
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Nie udało się pobrać siatek według id.");
return;
}
VisualizeMeshes(meshes);
Debug.Log($"[Meshes] Wybrano {ids.Count} bloków w promieniu 100 m od kamery, zwizualizowano {meshes.Count}.");
}
// Oblicz najbliższą odległość od punktu do powierzchni AABB (zwraca 0, gdy punkt jest wewnątrz AABB)
private static float calculateMinDistanceToAABB(Bounds _aabb, Vector3 _point)
{
Vector3 p = _aabb.ClosestPoint(_point);
return Vector3.Distance(p, _point);
}
Uwaga:
GetMeshBlockInfos()to operacja zero-I/O, zero-analizy, która odczytuje tylko metadane już w pamięci; możesz ją wywoływać swobodnie.- Filtr odległości używa
boundsWorld(prostokąt ograniczający w przestrzeni świata), a środek kamery przyjmujeCamera.transform.position.- Użyj
Bounds.ClosestPoint, aby obliczyć najbliższą odległość od punktu do powierzchni AABB: gdy kamera jest wewnątrz BBOX,ClosestPointzwraca samą kamerę, a odległość wynosi 0, więc ten blok również jest wybierany.- Promień filtra
100f(jednostka: metry) w przykładzie można dostosować w razie potrzeby.
Utwórz Przycisk i powiąż onRequireMeshes() z jego zdarzeniem onClick.
Po uruchomieniu kliknij przycisk, aby zobaczyć bloki siatki w promieniu 100 m od kamery.

Pobierz dane siatki w promieniu 100 m od kamery na żądanie
Kluczowe kroki
Kluczowe kroki pobierania i wizualizacji danych siatki sceny:
- Ukończ wstępną konfigurację, taką jak
SetPlatformType, i załaduj scenę LCC przezGetRender+Load. - Potwierdź, że dane przenoszą dane siatki kolizji (formaty natywne lub dane rekonstruowane we wcześniejszych wersjach mogą ich nie zawierać).
- Pełne pobieranie: wywołaj
GetMeshes(), aby odczytać wszystkie bloki naraz, i sam buforuj wynik, aby uniknąć powtarzanych wywołań. - Pobieranie na żądanie: najpierw użyj
GetMeshBlockInfos(), aby uzyskać prostokąty ograniczające bloków w świecie, odfiltruj id według odległości kamery, a następnie użyjGetMeshes(ids), aby odczytać tylko docelowe bloki. - Przy wizualizacji dołącz GameObject pod
Renderer.Transform, aby wyrównać z lokalnymi współrzędnymi, i zbuduj Mesh zIndexFormat.UInt32.
API pobierania siatki
Poniższe metody znajdują się wszystkie w Renderer; należy je wywoływać po zakończeniu ładowania sceny, a dane muszą przenosić dane siatki kolizji.
GetMeshBlockInfos
// Wartość zwracana: List<LCCMeshBlockInfo> → metadane bloków siatki kolizji; null, gdy nie ma siatki lub jest niedostępna
public List<LCCMeshBlockInfo> GetMeshBlockInfos()
Zwraca metadane wszystkich bloków siatki kolizji (id, prostokąt ograniczający świata/lokalny, liczba wierzchołków, liczba ścian). Zero-I/O i zero-analizy; odczytuje tylko metadane już w pamięci.
Przykład użycia:
var infos = renderer.GetMeshBlockInfos();
foreach (var info in infos)
{
Debug.Log($"{info.id}: verts={info.vertexCount}, faces={info.faceCount}, bbox={info.boundsWorld.size}");
}
Uwagi:
- Tylko PC / Mac + lokalne źródło danych; tylko wątek główny.
- Typowe użycie: najpierw użyj jej, aby uzyskać BBox świata każdego bloku, wybierz potrzebne id, a następnie wywołaj
GetMeshes(ids), aby odczytać tylko te bloki. - Format natywny (PLY / SOG / SPZ / splat / ksplat) nie ma siatki i zwraca
null(co jest normalne i nie jest rejestrowane).
GetMeshes
// Wartość zwracana: List<LCCMeshBlock> → wszystkie dane geometrii siatki kolizji; null, gdy niedostępna
public List<LCCMeshBlock> GetMeshes()
Synchronicznie zwraca dane geometrii wszystkich bloków siatki kolizji (wierzchołki + indeksy trójkątów, w lokalnym układzie współrzędnych). To ciężka operacja (synchroniczny odczyt z dysku + analiza + alokacja); wywołaj raz i sam buforuj wynik, nigdy co klatkę.
Przykład użycia:
var meshes = renderer.GetMeshes();
foreach (var block in meshes)
{
// block.vertices / block.triangles są w lokalnym układzie współrzędnych obiektu
}
Uwagi:
- Tylko PC / Mac + lokalne źródło danych; tylko wątek główny.
- Dla dużych scen użyj
GetMeshBlockInfos()do wyboru id +GetMeshes(ids)zamiast tego, aby uniknąć alokacji ogromnej ilości pamięci zarządzanej naraz. - Liczba wierzchołków pojedynczego bloku może przekroczyć 65535; przed zbudowaniem własnego Mesh musisz ustawić
mesh.indexFormat = IndexFormat.UInt32.
GetMeshes (według id)
// Wartość zwracana: List<LCCMeshBlock> → dane geometrii siatki kolizji dla określonych id; null, gdy brak trafień
public List<LCCMeshBlock> GetMeshes(IEnumerable<string> _ids)
Synchronicznie zwraca dane geometrii bloków siatki kolizji o określonych id (lokalny układ współrzędnych). Id są dostarczane przez GetMeshBlockInfos().
| Parametr | Typ | Opis |
|---|---|---|
_ids | IEnumerable<string> | Kolekcja docelowych id bloków (dostarczona przez GetMeshBlockInfos()) |
Przykład użycia:
var ids = new List<string> { "block-0", "block-3" };
var meshes = renderer.GetMeshes(ids);
Uwagi:
- Nieznane id są pomijane z ostrzeżeniem; zduplikowane id są deduplikowane (nie są wielokrotnie odczytywane z dysku); kolejność zwracania to kolejność przechodzenia id (po deduplikacji).
- Przekazanie
null, pustego wejścia lub brak trafień zwracanullz ostrzeżeniem. - Pozostałe ograniczenia są takie same jak w
GetMeshes().
Struktura danych LCCMeshBlockInfo
public struct LCCMeshBlockInfo {
public string id; // id węzła, odpowiadające LCCMeshBlock.id
public Bounds boundsWorld; // AABB w przestrzeni świata, używany do wyboru id
public Bounds boundsLocal; // AABB w przestrzeni lokalnej
public int vertexCount; // liczba wierzchołków
public int faceCount; // liczba ścian
}
| Pole | Typ | Opis |
|---|---|---|
id | string | Id węzła, odpowiadające LCCMeshBlock.id |
boundsWorld | Bounds | AABB w przestrzeni świata, używany do wyboru id |
boundsLocal | Bounds | AABB w przestrzeni lokalnej |
vertexCount | int | Liczba wierzchołków |
faceCount | int | Liczba ścian |
Struktura danych LCCMeshBlock
public struct LCCMeshBlock {
public string id; // id węzła
public Vector3[] vertices; // wierzchołki, lokalny układ współrzędnych
public int[] triangles; // indeksy trójkątów, 3 na trójkąt
public Bounds boundsLocal; // granice w tej samej przestrzeni co wierzchołki
}
| Pole | Typ | Opis |
|---|---|---|
id | string | Id węzła |
vertices | Vector3[] | Tablica wierzchołków, lokalny układ współrzędnych obiektu |
triangles | int[] | Indeksy trójkątów, 3 na trójkąt |
boundsLocal | Bounds | Granice w tej samej przestrzeni co wierzchołki |
Uwaga:
vertices/boundsLocalsą oba w lokalnym układzie współrzędnych obiektu. Aby je zwizualizować w Unity, dołącz GameObject zawierający Mesh podRenderer.Transform, aby wyrównać automatycznie. Liczba wierzchołków pojedynczego bloku może przekroczyć 65535; przed zbudowaniem własnego Mesh musisz ustawićmesh.indexFormat = IndexFormat.UInt32.