XGRIDSДокументация
  • 简体中文
  • English
  • 繁體中文
  • 日本語
  • Deutsch
  • Español
  • Italiano
  • Français
  • Русский
  • Polski
  • Nederlands
  • 简体中文
  • English
  • 繁體中文
  • 日本語
  • Deutsch
  • Español
  • Italiano
  • Français
  • Русский
  • Polski
  • Nederlands
  • Lixel CyberColor

    • Руководство пользователя LCC Studio

      • Начало работы
      • Версия и обновления
      • Загрузка и установка
      • Обзор интерфейса и навигация
      • Перед реконструкцией
      • Реконструкция модели
      • Реконструкция одиночной модели
      • Объединение карт
      • Воздушно-наземное объединение
      • Воздушная реконструкция
      • Мои модели
      • Другие функции
      • Настройки и учётная запись
      • Конвертер
      • Реконструкция по видео
      • Часто задаваемые вопросы
    • LCC Scene Editor

      • Версия и обновления
      • Учётная запись и вход
      • Обзор продукта и главная страница
      • Интерфейс редактора
      • Режимы навигации по сцене
      • Файл
      • Настройки
      • Операции редактирования
      • Окно
      • Глобальная панель инструментов
      • Ресурсы и свойства
      • Левая панель инструментов
      • Точки обзора
      • Портал
      • Skybox
      • Аннотации
      • Измерение
      • Flythrough
      • Отчёт по сцене
      • 3D Layout
      • Мини-карта
      • Preview Mode (Viewer)
      • Справка
      • Часто задаваемые вопросы
      • Точка появления
    • LCC Model Editor

      • Версия и обновления
      • Руководство пользователя
      • Обзор и интерфейс
      • Операции с файлами
      • Инструменты выбора
      • Редактирование моделей
      • Измерение
      • Цветокоррекция
      • Управление ресурсами
      • Настройки и справка
      • Часто задаваемые вопросы
    • Рекомендации по применению

      • Объединение нескольких крупных моделей 3DGS
  • Plugin & SDK

    • Unreal

      • Введение
      • Быстрый старт — Windows
      • Быстрый старт — Linux
      • Быстрый старт — Quest3
      • Редакции и лицензирование
      • Рендеринг
      • Растеризация Tiled (экспериментально)
      • Визуальные настройки
      • Нормали и освещение
      • Редактирование сцены
      • Параметры производительности
      • Руководство по оптимизации производительности
      • Интеграция со сторонними и движковыми плагинами
      • Прокси-меш
      • Анимация загрузки
      • Коллизии
      • Поддержка системы навигации
      • Поддержка однослойной воды
      • Локализация
      • Частые вопросы
      • Устранение неполадок
      • Журналы и диагностика
      • Свяжитесь с нами
      • Рекомендуемые практики

        • Переосвещение 3DGS с помощью меша LixelStudio
      • Справочник API

        • ALCCActorBase
        • ULCCComponentBase
        • ULCCComponent
        • ULCC2Component
        • Actor SOG / SPZ / PLY
        • ALCC2ProxyMesh
        • ALCCClippingVolume
        • ALCCSectionPlane
        • ALCCLoadVolume
        • ULCCUtilLibrary
        • Enums
        • Structs
      • Журнал изменений

        • v3.4.0
        • v3.3.1
        • v3.0.0
        • v2.2.1
        • v1.0.0
        • v0.9.0
        • v0.8.0
        • v0.7.1
        • v0.6.1
        • v0.5.2
        • v0.4.1
        • v0.4.0
        • v0.3.0
        • v0.0.5
        • v0.0.4
        • v0.0.3
        • v0.0.2
        • v0.0.1
    • Unity

      • Введение в Unity SDK
      • Технические возможности
      • Требования к среде разработки
      • Руководство по быстрому старту для PC
      • Руководство по оптимизации настроек проекта
      • Руководство по разработке для Pico 4 Ultra
      • Руководство по стримингу Pico 4 Ultra с PC
      • Руководство по разработке для Quest 3
      • Руководство по стримингу Quest 3 с PC
      • Руководство по разработке OpenXR
      • Руководство по бизнес-стримингу Pico 4 Ultra
      • Глоссарий
      • FAQ
      • Свяжитесь с нами
      • Руководство по разработке функций

        • Руководство по разработке смешанного рендеринга нескольких сцен
        • Руководство по разработке приёма теней
        • Руководство по разработке освещения в реальном времени
        • Руководство по разработке многокамерного рендеринга
        • Руководство по разработке рендеринга изображений высокого разрешения
        • Руководство по разработке совместимости форматов данных LCC и LCC2
        • Руководство по разработке эффекта загрузки
        • Руководство по разработке переключения режима рендеринга Mip
        • Руководство по разработке привязки к рёбрам и углам
        • Руководство по разработке проецируемых декалей
        • Руководство по разработке видеопроецируемой декали
        • Руководство по разработке подсветки
        • Руководство по разработке обрезки в реальном времени
        • Руководство по разработке коррекции цвета
        • Руководство по разработке получения данных Mesh сцены
        • Руководство по разработке в режиме редактора
        • Руководство по настройке параметров производительности рендеринга
        • Руководство по разработке совместной упаковки приложения и данных
        • Руководство по разработке системы коллизий
        • Руководство по проблемам с разрешениями при разработке на macOS
        • Руководство по разработке компиляции и упаковки для устройств iOS
      • Справочник по API

        • LCCManager
        • Renderer
        • Типы данных
      • Журнал изменений

        • v2.3.0
        • v2.2.10
        • v2.2.0
        • v2.1.19
        • v2.0.14

Руководство по разработке получения данных 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

Настройка свойств Main Camera

Создайте LCCManager

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

Создание GameObject LCCManager и добавление скрипта LCCManager

Создание GameObject LCCManager и добавление скрипта LCCManager

Создайте LCCRenderer

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

Создание LCCRenderer и настройка его Transform

Создание 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, а не чтение всех данных 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 в пределах 100 м от камеры по требованию

Ключевые шаги

Ключевые шаги для получения и визуализации данных Mesh сцены:

  1. Выполните предварительную настройку, такую как SetPlatformType, и загрузите LCC-сцену через GetRender + Load.
  2. Подтвердите, что данные несут коллизионные данные Mesh (нативные форматы или данные, реконструированные в более ранних версиях, могут не нести).
  3. Полное получение: вызовите GetMeshes() для однократного чтения всех блоков и кэшируйте результат самостоятельно, чтобы избежать повторных вызовов.
  4. Получение по требованию: сначала используйте GetMeshBlockInfos() для получения мировых ограничивающих параллелепипедов блоков, отфильтруйте id по расстоянию до камеры, затем используйте GetMeshes(ids) для чтения только целевых блоков.
  5. При визуализации прикрепите 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().

ПараметрТипОписание
_idsIEnumerable<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;       // количество граней
}
ПолеТипОписание
idstringid узла, соответствует LCCMeshBlock.id
boundsWorldBoundsAABB в мировом пространстве, используется для выбора id
boundsLocalBoundsAABB в локальном пространстве
vertexCountintКоличество вершин
faceCountintКоличество граней

Структура данных LCCMeshBlock

public struct LCCMeshBlock {
    public string id;           // id узла
    public Vector3[] vertices;  // вершины, локальная система координат
    public int[] triangles;     // индексы треугольников, по 3 на треугольник
    public Bounds boundsLocal;  // границы в том же пространстве, что и вершины
}
ПолеТипОписание
idstringid узла
verticesVector3[]Массив вершин, локальная система координат объекта
trianglesint[]Индексы треугольников, по 3 на треугольник
boundsLocalBoundsГраницы в том же пространстве, что и вершины

Примечание:

vertices / boundsLocal находятся в локальной системе координат объекта. Чтобы визуализировать их в Unity, прикрепите GameObject, содержащий Mesh, под Renderer.Transform для автоматического выравнивания. Количество вершин одного блока может превышать 65535; перед построением собственного Mesh необходимо задать mesh.indexFormat = IndexFormat.UInt32.