Guía de desarrollo para obtener los datos de malla de la escena
Al reconstruir datos LCC, el LCC Unity SDK también genera datos de malla usados para las colisiones. Además de la detección de colisiones, el SDK proporciona la capacidad de extraer estos datos de malla, convertirlos en objetos Mesh de Unity y visualizarlos, lo que es conveniente para escenarios como la exportación y el horneado de NavMesh.
A continuación se usa un ejemplo completo para describir los pasos para obtener síncronamente todos los datos de malla y para obtener datos de malla de un rango especificado bajo demanda.
Configuración del entorno
Crea una escena completamente nueva en Unity. Unity crea Main Camera y Directional Light por defecto.
Configurar Main Camera
Selecciona Main Camera en la ventana Jerarquía y establece sus propiedades en la ventana Inspector como se muestra a continuación.

Establecer las propiedades de Main Camera
Crear LCCManager
Haz clic con el botón derecho en un área vacía de la ventana Jerarquía, selecciona Create Empty en el menú emergente para crear un GameObject vacío, nómbralo LCCManager, normaliza su Transform y añádele el script LCCManager mediante Agregar componente.

Crear el GameObject LCCManager y añadir el script LCCManager
Crear LCCRenderer
Haz clic con el botón derecho en un área vacía de la ventana Jerarquía, selecciona Create Empty para crear un GameObject vacío, nómbralo LCCRenderer y establece su Transform como Rotation.x = -90 y Scale.x = -1.

Crear LCCRenderer y establecer su Transform (Rotation.x = -90, Scale.x = -1)
Nota:
Los ajustes de Transform del GameObject LCCRenderer (Rotation.x = -90, Scale.x = -1) lo alinean con el sistema de coordenadas de los datos LCC. Para más detalles sobre la alineación del sistema de coordenadas, consulta "Sistema de coordenadas de los datos LCC" en Introducción al SDK de Unity.
Requisitos previos de los datos
El requisito previo para que la obtención de datos de malla surta efecto es que la escena lleve los datos de malla de colisión correspondientes (generados cuando LCC Studio la reconstruye). Además, esta capacidad tiene las siguientes restricciones:
- Solo PC / Mac, y solo se admiten fuentes de datos locales (no se admiten fuentes de datos HTTP).
- Solo puede llamarse en el hilo principal.
- Los datos en formato nativo (PLY / SOG / SPZ / splat / ksplat) no llevan datos de malla de colisión; llamar a los métodos relacionados devuelve
null.
Los datos reconstruidos en versiones anteriores pueden no contener datos de colisión, en cuyo caso llamar a GetMeshes() devuelve null.
Obtener síncronamente todos los datos de malla
Obtener síncronamente todos los datos de malla significa leer los datos de geometría de todos los bloques de malla de colisión de la escena de una sola vez, implementado mediante el método Renderer.GetMeshes().
Crea un nuevo script C# llamado Meshes.cs con el siguiente código:
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] progreso de carga: {p:P1}"),
() =>
{
Debug.Log($"[Meshes] archivo cargado: {m_FilePath}");
m_isLoaded = true;
m_manager.SetAutoFov(false);
},
e => Debug.LogError($"[Meshes] error de carga del archivo: {e}")
);
}
public void onGetAllMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] Los datos no han terminado de cargarse; no se pueden obtener las Meshes.");
return;
}
var meshes = m_renderer.GetMeshes();
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] No se obtuvieron Meshes (los datos pueden no contener colliders).");
return;
}
VisualizeMeshes(meshes);
}
private void VisualizeMeshes(List<LCCMeshBlock> meshes)
{
// Limpia el resultado de visualización anterior para evitar acumular
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 };
// El recuento de vértices de un solo bloque puede superar 65535, por lo que deben usarse índices de 32 bits; de lo contrario, la geometría se corromperá
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] Se crearon GameObjects y se visualizaron {meshes.Count} bloques 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) };
// El orden de enrollado de los triángulos de la malla de colisión no está garantizado como consistente; desactiva el culling de caras traseras para una visualización completa
mat.SetFloat("_Cull", (float)CullMode.Off);
return mat;
}
}
Nota:
GetMeshes()es una operación pesada síncrona: lee síncronamente del disco, analiza y asigna memoria. En escenas grandes, p. ej. un conjunto de datos de 262 bloques, puede necesitar leer una gran cantidad de datos del disco y asignar varios GB a decenas de GB de memoria administrada. Llámala una vez y almacena en caché el resultado tú mismo; nunca la llames en cada fotograma.vertices/boundsLocalestán en el sistema de coordenadas local del objeto, por lo que adjuntar el GameObject de visualización bajoRenderer.Transformlo alinea automáticamente.- El recuento de vértices de un solo bloque puede superar 65535 (máximo medido 390 656); antes de construir tu propia
Mesh, debes establecermesh.indexFormat = IndexFormat.UInt32, de lo contrario la geometría se corromperá.- El orden de enrollado de los triángulos de la malla de colisión no está garantizado como consistente; el ejemplo desactiva el culling de caras traseras (
_Cull = Off) para una visualización completa.
Adjunta el script al GameObject LCCRenderer y, en el Inspector, arrastra el GameObject LCCManager a la propiedad Manager y rellena el File Path.
A continuación, crea un Button y asocia el método onGetAllMeshes() a su evento onClick.
Después de ejecutar, haz clic en el Button para ver todas las Meshes de colisión en la escena.

Visualizar todos los datos de malla de colisión tras hacer clic en el botón
Obtener datos de malla bajo demanda
La obtención bajo demanda significa leer solo la parte especificada de los datos de malla en lugar de leer todos los datos de malla de una sola vez, para evitar grandes costes de I/O y memoria. A continuación se usa la lectura de los bloques de malla dentro de un cierto rango alrededor de la cámara como ejemplo.
Primero usa GetMeshBlockInfos() para obtener los metadatos de todos los bloques (incluidas las cajas delimitadoras en espacio de mundo), calcula la distancia desde el centro de la cámara de renderizado a la superficie BBOX del bloque en coordenadas de mundo, filtra los ids de bloque con una distancia menor a 100 m y luego usa el GetMeshes(ids) parametrizado para leer solo esos bloques.
Añade los métodos siguientes a Meshes.cs:
public void onRequireMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] Los datos no han terminado de cargarse; no se pueden obtener las Meshes.");
return;
}
// 1. Lectura sin I/O de todos los metadatos de bloque, filtrando bloques a menos de 100 m por la distancia cámara-superficie de la BBOX
var infos = m_renderer.GetMeshBlockInfos();
if (infos == null || infos.Count == 0)
{
Debug.LogWarning("[Meshes] No se obtuvo información de Mesh (los datos pueden no contener colliders).");
return;
}
Vector3 camPos = m_camera.transform.position;
var ids = new List<string>();
foreach (var info in infos)
{
// Cuando la cámara está dentro de la BBOX, ClosestPoint devuelve la propia cámara y la distancia es 0, por lo que también se selecciona
if (calculateMinDistanceToAABB(info.boundsWorld, camPos) < 100f)
ids.Add(info.id);
}
if (ids.Count == 0)
{
Debug.LogWarning("[Meshes] No hay bloques Mesh a menos de 100 m de la cámara.");
return;
}
// 2. Lee solo los bloques filtrados por id y visualízalos
var meshes = m_renderer.GetMeshes(ids);
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Error al obtener Meshes por id.");
return;
}
VisualizeMeshes(meshes);
Debug.Log($"[Meshes] Seleccionados {ids.Count} bloques a menos de 100 m de la cámara, visualizados {meshes.Count}.");
}
// Calcula la distancia más cercana de un punto a la superficie del AABB (devuelve 0 cuando el punto está dentro del AABB)
private static float calculateMinDistanceToAABB(Bounds _aabb, Vector3 _point)
{
Vector3 p = _aabb.ClosestPoint(_point);
return Vector3.Distance(p, _point);
}
Nota:
GetMeshBlockInfos()es una operación sin I/O y sin análisis que solo lee los metadatos ya en memoria; puedes llamarla libremente.- El filtro de distancia usa
boundsWorld(caja delimitadora en espacio de mundo), y el centro de la cámara tomaCamera.transform.position.- Usa
Bounds.ClosestPointpara calcular la distancia más cercana de un punto a la superficie del AABB: cuando la cámara está dentro de la BBOX,ClosestPointdevuelve la propia cámara y la distancia es 0, por lo que ese bloque también se selecciona.- El radio de filtro
100f(unidad: metros) del ejemplo puede ajustarse según sea necesario.
Crea un Button y asocia onRequireMeshes() a su evento onClick.
Después de ejecutar, haz clic en el botón para ver los bloques de malla a menos de 100 m de la cámara.

Obtener datos de malla a menos de 100 m de la cámara bajo demanda
Pasos clave
Los pasos clave para obtener y visualizar los datos de malla de la escena:
- Completa la configuración previa como
SetPlatformType, y carga la escena LCC medianteGetRender+Load. - Confirma que los datos llevan datos de malla de colisión (los formatos nativos o los datos reconstruidos en versiones anteriores pueden no tenerlos).
- Obtención completa: llama a
GetMeshes()para leer todos los bloques de una sola vez y almacena en caché el resultado tú mismo para evitar llamadas repetidas. - Obtención bajo demanda: primero usa
GetMeshBlockInfos()para obtener las cajas delimitadoras de mundo de los bloques, filtra ids por distancia de cámara y luego usaGetMeshes(ids)para leer solo los bloques de destino. - Al visualizar, adjunta el GameObject bajo
Renderer.Transformpara alinearlo con las coordenadas locales, y construye la Mesh conIndexFormat.UInt32.
API de obtención de malla
Los siguientes métodos están todos en Renderer; deben llamarse después de que la escena termine de cargarse, y los datos deben llevar datos de malla de colisión.
GetMeshBlockInfos
// Valor de retorno: List<LCCMeshBlockInfo> → metadatos de los bloques Mesh de colisión; null cuando no hay Mesh o no está disponible
public List<LCCMeshBlockInfo> GetMeshBlockInfos()
Devuelve los metadatos de todos los bloques de malla de colisión (id, caja delimitadora de mundo/local, recuento de vértices, recuento de caras). Sin I/O y sin análisis; solo lee los metadatos ya en memoria.
Ejemplo de uso:
var infos = renderer.GetMeshBlockInfos();
foreach (var info in infos)
{
Debug.Log($"{info.id}: verts={info.vertexCount}, faces={info.faceCount}, bbox={info.boundsWorld.size}");
}
Notas:
- Solo PC / Mac + fuente de datos local; solo hilo principal.
- Uso típico: primero úsalo para obtener la BBox de mundo de cada bloque, elige los ids necesarios y luego llama a
GetMeshes(ids)para leer solo esos bloques. - Los formatos nativos (PLY / SOG / SPZ / splat / ksplat) no tienen Mesh y devuelven
null(lo que es normal y no se registra).
GetMeshes
// Valor de retorno: List<LCCMeshBlock> → todos los datos de geometría de la Mesh de colisión; null cuando no está disponible
public List<LCCMeshBlock> GetMeshes()
Devuelve síncronamente los datos de geometría de todos los bloques de malla de colisión (vértices + índices de triángulos, en el sistema de coordenadas local). Es una operación pesada (lectura síncrona del disco + análisis + asignación); llámala una vez y almacena en caché tú mismo, nunca en cada fotograma.
Ejemplo de uso:
var meshes = renderer.GetMeshes();
foreach (var block in meshes)
{
// block.vertices / block.triangles están en el sistema de coordenadas local del objeto
}
Notas:
- Solo PC / Mac + fuente de datos local; solo hilo principal.
- Para escenas grandes, usa
GetMeshBlockInfos()para elegir ids +GetMeshes(ids)en su lugar, para evitar asignar una gran cantidad de memoria administrada de una sola vez. - El recuento de vértices de un solo bloque puede superar 65535; antes de construir tu propia Mesh, debes establecer
mesh.indexFormat = IndexFormat.UInt32.
GetMeshes (por id)
// Valor de retorno: List<LCCMeshBlock> → datos de geometría de la Mesh de colisión de los ids especificados; null cuando no hay impactos
public List<LCCMeshBlock> GetMeshes(IEnumerable<string> _ids)
Devuelve síncronamente los datos de geometría de los bloques de malla de colisión con los ids especificados (sistema de coordenadas local). Los ids los proporciona GetMeshBlockInfos().
| Parámetro | Tipo | Descripción |
|---|---|---|
_ids | IEnumerable<string> | La colección de ids de bloque de destino (proporcionada por GetMeshBlockInfos()) |
Ejemplo de uso:
var ids = new List<string> { "block-0", "block-3" };
var meshes = renderer.GetMeshes(ids);
Notas:
- Los ids desconocidos se omiten con una advertencia; los ids duplicados se deduplican (no se leen del disco repetidamente); el orden de retorno es el orden de recorrido de los ids (después de la deduplicación).
- Pasar
null, una entrada vacía o sin impactos devuelvenullcon una advertencia. - Otras restricciones son las mismas que
GetMeshes().
Estructura de datos LCCMeshBlockInfo
public struct LCCMeshBlockInfo {
public string id; // id del nodo, correspondiente a LCCMeshBlock.id
public Bounds boundsWorld; // AABB en espacio de mundo, usado para seleccionar ids
public Bounds boundsLocal; // AABB en espacio local
public int vertexCount; // recuento de vértices
public int faceCount; // recuento de caras
}
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Id del nodo, correspondiente a LCCMeshBlock.id |
boundsWorld | Bounds | AABB en espacio de mundo, usado para seleccionar ids |
boundsLocal | Bounds | AABB en espacio local |
vertexCount | int | Recuento de vértices |
faceCount | int | Recuento de caras |
Estructura de datos LCCMeshBlock
public struct LCCMeshBlock {
public string id; // id del nodo
public Vector3[] vertices; // vértices, sistema de coordenadas local
public int[] triangles; // índices de triángulos, 3 por triángulo
public Bounds boundsLocal; // límites en el mismo espacio que los vértices
}
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Id del nodo |
vertices | Vector3[] | Matriz de vértices, sistema de coordenadas local del objeto |
triangles | int[] | Índices de triángulos, 3 por triángulo |
boundsLocal | Bounds | Límites en el mismo espacio que los vértices |
Nota:
vertices/boundsLocalestán ambos en el sistema de coordenadas local del objeto. Para visualizarlos en Unity, adjunta el GameObject que contiene la Mesh bajoRenderer.Transformpara alinearlos automáticamente. El recuento de vértices de un solo bloque puede superar 65535; antes de construir tu propia Mesh, debes establecermesh.indexFormat = IndexFormat.UInt32.