Руководство по разработке получения данных Mesh сцены
При реконструкции данных LCC LCC Unity SDK также генерирует данные Mesh, используемые для коллизий. Помимо обнаружения коллизий, SDK предоставляет возможность извлекать эти данные Mesh, преобразовывать их в объекты Mesh Unity и визуализировать, что удобно для таких сценариев, как экспорт и запекание NavMesh.
Ниже на полном примере описаны шаги синхронного получения всех данных Mesh и получения данных Mesh указанного диапазона по требованию.
Настройка окружения
Создайте совершенно новую сцену в Unity. Unity по умолчанию создаёт Main Camera и Directional Light.
Настройте Main Camera
Выберите Main Camera в окне Hierarchy и задайте его свойства в окне Inspector, как показано ниже.

Настройка свойств Main Camera
Создайте LCCManager
Щёлкните правой кнопкой мыши в пустой области окна Hierarchy, выберите Create Empty во всплывающем меню, чтобы создать пустой GameObject, назовите его LCCManager, нормализуйте его Transform и добавьте к нему скрипт LCCManager через Add Component.

Создание GameObject LCCManager и добавление скрипта LCCManager
Создайте LCCRenderer
Щёлкните правой кнопкой мыши в пустой области окна Hierarchy, выберите Create Empty, чтобы создать пустой GameObject, назовите его LCCRenderer и задайте его Transform значения Rotation.x = -90 и Scale.x = -1.

Создание LCCRenderer и настройка его Transform (Rotation.x = -90, Scale.x = -1)
Примечание:
Настройки Transform GameObject LCCRenderer (Rotation.x = -90, Scale.x = -1) выравнивают его с системой координат данных LCC. Подробнее о выравнивании системы координат см. в разделе «Система координат данных LCC» документа Введение в Unity SDK.
Предварительные требования к данным
Предпосылкой для вступления в силу получения данных Mesh является то, что сцена несёт соответствующие коллизионные данные Mesh (генерируемые при реконструкции в LCC Studio). Кроме того, эта возможность имеет следующие ограничения:
- Только PC / Mac, и поддерживаются только локальные источники данных (источники данных HTTP не поддерживаются).
- Можно вызывать только из основного потока.
- Данные нативных форматов (PLY / SOG / SPZ / splat / ksplat) не несут коллизионных данных Mesh; вызов связанных методов возвращает
null.
Данные, реконструированные в более ранних версиях, могут не содержать коллизионных данных, в этом случае вызов GetMeshes() возвращает null.
Синхронное получение всех данных Mesh
Синхронное получение всех данных Mesh означает однократное чтение геометрических данных всех коллизионных блоков Mesh в сцене, реализуемое через метод Renderer.GetMeshes().
Создайте новый скрипт C# с именем Meshes.cs со следующим кодом:
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] прогресс загрузки: {p:P1}"),
() =>
{
Debug.Log($"[Meshes] файл загружен: {m_FilePath}");
m_isLoaded = true;
m_manager.SetAutoFov(false);
},
e => Debug.LogError($"[Meshes] ошибка загрузки файла: {e}")
);
}
public void onGetAllMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] Данные ещё не завершили загрузку; невозможно получить Meshes.");
return;
}
var meshes = m_renderer.GetMeshes();
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Meshes не получены (данные могут не содержать коллайдеров).");
return;
}
VisualizeMeshes(meshes);
}
private void VisualizeMeshes(List<LCCMeshBlock> meshes)
{
// Очищаем предыдущий результат визуализации, чтобы избежать накопления
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 };
// Количество вершин одного блока может превышать 65535, поэтому необходимо использовать 32-битные индексы, иначе геометрия будет повреждена
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] Созданы GameObject и визуализировано {meshes.Count} блоков 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) };
// Порядок обхода треугольников коллизионного меша не гарантированно согласован; отключаем отсечение задних граней для полного отображения
mat.SetFloat("_Cull", (float)CullMode.Off);
return mat;
}
}
Примечание:
GetMeshes()— это синхронная тяжёлая операция: она синхронно читает с диска, разбирает и выделяет память. В больших сценах, например в наборе данных из 262 блоков, может потребоваться прочитать большой объём данных с диска и выделить от нескольких ГБ до десятков ГБ управляемой памяти. Вызовите её один раз и кэшируйте результат самостоятельно; никогда не вызывайте её каждый кадр.vertices/boundsLocalнаходятся в локальной системе координат объекта, поэтому прикрепление GameObject визуализации подRenderer.Transformвыравнивает его автоматически.- Количество вершин одного блока может превышать 65535 (измеренный максимум 390 656); перед построением собственного
Meshнеобходимо задатьmesh.indexFormat = IndexFormat.UInt32, иначе геометрия будет повреждена.- Порядок обхода треугольников коллизионного меша не гарантированно согласован; в примере отключается отсечение задних граней (
_Cull = Off) для полного отображения.
Прикрепите скрипт к GameObject LCCRenderer и в Inspector перетащите GameObject LCCManager на свойство Manager и заполните File Path.
Затем создайте Button и свяжите метод onGetAllMeshes() с его событием onClick.
После запуска нажмите Button, чтобы увидеть все коллизионные Meshes в сцене.

Визуализация всех коллизионных данных Mesh после нажатия кнопки
Получение данных Mesh по требованию
Получение по требованию означает чтение только указанной части данных Mesh, а не чтение всех данных Mesh сразу, чтобы избежать больших накладных расходов на I/O и память. Ниже в качестве примера рассматривается чтение блоков Mesh в определённом диапазоне вокруг камеры.
Сначала используйте GetMeshBlockInfos() для получения метаданных всех блоков (включая ограничивающие параллелепипеды в мировом пространстве), вычислите расстояние от центра камеры рендеринга до поверхности BBOX блока в мировых координатах, отфильтруйте id блоков с расстоянием менее 100 м, а затем используйте параметризованный GetMeshes(ids) для чтения только этих блоков.
Добавьте следующие методы в Meshes.cs:
public void onRequireMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] Данные ещё не завершили загрузку; невозможно получить Meshes.");
return;
}
// 1. Чтение всех метаданных блоков без I/O, фильтрация блоков в пределах 100 м по расстоянию от камеры до поверхности BBOX
var infos = m_renderer.GetMeshBlockInfos();
if (infos == null || infos.Count == 0)
{
Debug.LogWarning("[Meshes] Информация о Mesh не получена (данные могут не содержать коллайдеров).");
return;
}
Vector3 camPos = m_camera.transform.position;
var ids = new List<string>();
foreach (var info in infos)
{
// Когда камера внутри BBOX, ClosestPoint возвращает саму камеру и расстояние равно 0, поэтому блок также выбирается
if (calculateMinDistanceToAABB(info.boundsWorld, camPos) < 100f)
ids.Add(info.id);
}
if (ids.Count == 0)
{
Debug.LogWarning("[Meshes] Нет блоков Mesh в пределах 100 м от камеры.");
return;
}
// 2. Читаем только отфильтрованные блоки по id и визуализируем их
var meshes = m_renderer.GetMeshes(ids);
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Не удалось получить Meshes по id.");
return;
}
VisualizeMeshes(meshes);
Debug.Log($"[Meshes] Выбрано {ids.Count} блоков в пределах 100 м от камеры, визуализировано {meshes.Count}.");
}
// Вычисляем ближайшее расстояние от точки до поверхности AABB (возвращает 0, когда точка внутри AABB)
private static float calculateMinDistanceToAABB(Bounds _aabb, Vector3 _point)
{
Vector3 p = _aabb.ClosestPoint(_point);
return Vector3.Distance(p, _point);
}
Примечание:
GetMeshBlockInfos()— это операция без I/O и без разбора, которая только читает метаданные, уже находящиеся в памяти; её можно вызывать свободно.- Фильтр расстояния использует
boundsWorld(ограничивающий параллелепипед в мировом пространстве), а центр камеры берётся изCamera.transform.position.- Используйте
Bounds.ClosestPointдля вычисления ближайшего расстояния от точки до поверхности AABB: когда камера внутри BBOX,ClosestPointвозвращает саму камеру и расстояние равно 0, поэтому этот блок также выбирается.- Радиус фильтра
100f(единица: метры) в примере можно настроить по мере необходимости.
Создайте Button и свяжите onRequireMeshes() с его событием onClick.
После запуска нажмите кнопку, чтобы увидеть блоки Mesh в пределах 100 м от камеры.

Получение данных Mesh в пределах 100 м от камеры по требованию
Ключевые шаги
Ключевые шаги для получения и визуализации данных Mesh сцены:
- Выполните предварительную настройку, такую как
SetPlatformType, и загрузите LCC-сцену черезGetRender+Load. - Подтвердите, что данные несут коллизионные данные Mesh (нативные форматы или данные, реконструированные в более ранних версиях, могут не нести).
- Полное получение: вызовите
GetMeshes()для однократного чтения всех блоков и кэшируйте результат самостоятельно, чтобы избежать повторных вызовов. - Получение по требованию: сначала используйте
GetMeshBlockInfos()для получения мировых ограничивающих параллелепипедов блоков, отфильтруйте id по расстоянию до камеры, затем используйтеGetMeshes(ids)для чтения только целевых блоков. - При визуализации прикрепите GameObject под
Renderer.Transformдля выравнивания с локальными координатами и постройте Mesh сIndexFormat.UInt32.
API получения Mesh
Следующие методы находятся в Renderer; их нужно вызывать после завершения загрузки сцены, и данные должны нести коллизионные данные Mesh.
GetMeshBlockInfos
// Возвращаемое значение: List<LCCMeshBlockInfo> → метаданные блоков коллизионного Mesh; null при отсутствии Mesh или недоступности
public List<LCCMeshBlockInfo> GetMeshBlockInfos()
Возвращает метаданные всех коллизионных блоков Mesh (id, мировой/локальный ограничивающий параллелепипед, количество вершин, количество граней). Без I/O и без разбора; только читает метаданные, уже находящиеся в памяти.
Пример использования:
var infos = renderer.GetMeshBlockInfos();
foreach (var info in infos)
{
Debug.Log($"{info.id}: вершин={info.vertexCount}, граней={info.faceCount}, bbox={info.boundsWorld.size}");
}
Примечания:
- Только PC / Mac + локальный источник данных; только основной поток.
- Типичное использование: сначала получить мировой BBox каждого блока, выбрать нужные id, затем вызвать
GetMeshes(ids)для чтения только этих блоков. - Нативные форматы (PLY / SOG / SPZ / splat / ksplat) не имеют Mesh и возвращают
null(что нормально и не записывается в журнал).
GetMeshes
// Возвращаемое значение: List<LCCMeshBlock> → все геометрические данные коллизионного Mesh; null при недоступности
public List<LCCMeshBlock> GetMeshes()
Синхронно возвращает геометрические данные всех коллизионных блоков Mesh (вершины + индексы треугольников, в локальной системе координат). Это тяжёлая операция (синхронное чтение с диска + разбор + выделение); вызовите её один раз и кэшируйте самостоятельно, никогда не вызывайте каждый кадр.
Пример использования:
var meshes = renderer.GetMeshes();
foreach (var block in meshes)
{
// block.vertices / block.triangles находятся в локальной системе координат объекта
}
Примечания:
- Только PC / Mac + локальный источник данных; только основной поток.
- Для больших сцен используйте
GetMeshBlockInfos()для выбора id +GetMeshes(ids), чтобы избежать выделения огромного объёма управляемой памяти сразу. - Количество вершин одного блока может превышать 65535; перед построением собственного Mesh необходимо задать
mesh.indexFormat = IndexFormat.UInt32.
GetMeshes (по id)
// Возвращаемое значение: List<LCCMeshBlock> → геометрические данные коллизионного Mesh для указанных id; null при отсутствии попаданий
public List<LCCMeshBlock> GetMeshes(IEnumerable<string> _ids)
Синхронно возвращает геометрические данные коллизионных блоков Mesh с указанными id (локальная система координат). id предоставляются методом GetMeshBlockInfos().
| Параметр | Тип | Описание |
|---|---|---|
_ids | IEnumerable<string> | Коллекция id целевых блоков (предоставляется GetMeshBlockInfos()) |
Пример использования:
var ids = new List<string> { "block-0", "block-3" };
var meshes = renderer.GetMeshes(ids);
Примечания:
- Неизвестные id пропускаются с предупреждением; дублирующиеся id дедуплицируются (не читаются с диска повторно); порядок возврата — это порядок обхода id (после дедупликации).
- Передача
null, пустого ввода или отсутствие попаданий возвращаетnullс предупреждением. - Остальные ограничения такие же, как у
GetMeshes().
Структура данных LCCMeshBlockInfo
public struct LCCMeshBlockInfo {
public string id; // id узла, соответствует LCCMeshBlock.id
public Bounds boundsWorld; // AABB в мировом пространстве, используется для выбора id
public Bounds boundsLocal; // AABB в локальном пространстве
public int vertexCount; // количество вершин
public int faceCount; // количество граней
}
| Поле | Тип | Описание |
|---|---|---|
id | string | id узла, соответствует LCCMeshBlock.id |
boundsWorld | Bounds | AABB в мировом пространстве, используется для выбора id |
boundsLocal | Bounds | AABB в локальном пространстве |
vertexCount | int | Количество вершин |
faceCount | int | Количество граней |
Структура данных LCCMeshBlock
public struct LCCMeshBlock {
public string id; // id узла
public Vector3[] vertices; // вершины, локальная система координат
public int[] triangles; // индексы треугольников, по 3 на треугольник
public Bounds boundsLocal; // границы в том же пространстве, что и вершины
}
| Поле | Тип | Описание |
|---|---|---|
id | string | id узла |
vertices | Vector3[] | Массив вершин, локальная система координат объекта |
triangles | int[] | Индексы треугольников, по 3 на треугольник |
boundsLocal | Bounds | Границы в том же пространстве, что и вершины |
Примечание:
vertices/boundsLocalнаходятся в локальной системе координат объекта. Чтобы визуализировать их в Unity, прикрепите GameObject, содержащий Mesh, подRenderer.Transformдля автоматического выравнивания. Количество вершин одного блока может превышать 65535; перед построением собственного Mesh необходимо задатьmesh.indexFormat = IndexFormat.UInt32.