Механизм рендеринга 3DGS и управление видеопамятью
Этот документ описывает, как данные попадают в видеопамять и участвуют в рендеринге, а также правила распределения и ограничения ёмкости видеопамяти. О настройке параметров изображения см. Визуальные настройки. Об операциях загрузки см. Быстрый старт.
Терминология: наименьшая единица загрузки и рендеринга после разбиения данных называется узлом (Node). Точность каждого узла обозначается его Level: Level 0 — наивысшая точность, чем больше Level, тем ниже точность. Эти определения совпадают с Параметрами производительности.
Рендеринг по частям и рендеринг с полной загрузкой
После загрузки данных возможны два способа их рендеринга: загружать видимые узлы по требованию (рендеринг по частям) или загружать весь набор данных сразу (рендеринг с полной загрузкой). Поведение по умолчанию различается по конвейерам:
Примечание: «по частям» в этом документе означает, что данные разбиты на пространственные узлы и загружаются по требованию; это отличается от растеризации Tiled, где тайлы — это тайлы пикселей экрана. Одно определяет, как данные попадают в видеопамять, другое — как рисуются уже находящиеся там данные. Они независимы.
| Конвейер | Форматы | Режим по умолчанию |
|---|---|---|
| LCC2 | .lcc2 / .ply / .spz / .sog | Рендеринг по частям |
| LCC | .lcc | Рендеринг с полной загрузкой; при превышении порога данными откатывается к рендерингу по частям |
Рендеринг по частям
Данные разбиваются на пространственные узлы, и каждый узел содержит несколько Level. Каждый кадр обрабатывается так:
Traversal (filter visible nodes by camera position and orientation, pick a Level for each node)
│
▼
Load (read the data of the selected nodes into memory)
│
▼
Upload (submit the data to video memory)
│
▼
Render
Близкие узлы используют низкий Level, дальние — высокий, невидимые узлы не загружаются. Узел, вышедший из вида, не освобождается сразу: он остаётся в кэше, чтобы его можно было сразу переиспользовать при повторном появлении в кадре. Только при недостатке видеопамяти давно не использованные узлы вытесняются по таким условиям, как время последнего рендеринга. Расход видеопамяти зависит в основном от текущего видимого диапазона, поэтому и возможна поддержка больших сцен.
Два конвейера отправляют отрисовку по-разному:
| Конвейер | Отправка отрисовки |
|---|---|
| LCC | Каждый узел отправляется отдельной отрисовкой, поэтому число draw call растёт вместе с числом узлов |
| LCC2 | Данные всех видимых узлов объединяются в один Buffer и отправляются одной отрисовкой |
Неизбежные издержки рендеринга по частям:
- Соседним узлам могут быть назначены разные Level, поэтому на границе различаются плотность и резкость и границы узлов становятся заметны
- При движении камеры Level постоянно переключаются, из-за чего возможны скачки детализации
Рендеринг с полной загрузкой
Шаг отбора узлов по камере пропускается. Весь набор данных загружается сразу и постоянно находится в видеопамяти, поэтому перемещение камеры больше не запускает обход и выбор Level.
Преимущества:
- Вся сцена использует один Level, поэтому нет разницы Level между узлами и не видно границ узлов
- Данные постоянно находятся в видеопамяти, поэтому перемещение камеры не вызывает загрузки, освобождения и скачков детализации
- Убираются издержки обхода и загрузки в видеопамять на кадр, частота кадров стабильнее
Плата за это — все данные постоянно находятся в видеопамяти, и расход растёт линейно с размером сцены, поэтому такой режим подходит только для небольших сцен.
Критерии рендеринга с полной загрузкой
Использование рендеринга с полной загрузкой определяется при загрузке переключателем Use Full Load и количеством splat на Level 0:
Load(data file)
│
▼
Read the Level 0 splat count
│
▼
Use Full Load enabled? ── No ──▶ Chunked rendering
│ Yes
▼
Splat count ≤ Full Load Splat Number? ── No ──▶ Chunked rendering
│ Yes
▼
Full load rendering
Оба свойства находятся в категории Performance панели Details у Actor:
| Свойство | Описание |
|---|---|
| Use Full Load | Разрешён ли рендеринг с полной загрузкой |
| Full Load Splat Number | Верхний предел количества splat на Level 0, при котором разрешён рендеринг с полной загрузкой (единица: 10 000, по умолчанию 1500) |
Значения по умолчанию различаются по типу Actor:
| Actor | Use Full Load по умолчанию | Причина |
|---|---|---|
| ALCCActor | Включено | Разница Level между узлами заметна и границы узлов видны, к тому же каждый узел стоит одного draw call, поэтому в небольших сценах рендеринг с полной загрузкой выглядит лучше |
| ALCC2Actor / APlyActor / ASpzActor / ASogActor | Выключено | Переключение Level идёт непрерывно по экранной погрешности и границы узлов малозаметны, к тому же нужен всего один draw call, поэтому загрузка по требованию по умолчанию экономит больше видеопамяти |
Когда включать рендеринг с полной загрузкой вручную в LCC2
Конвейер LCC2 по умолчанию отключает рендеринг с полной загрузкой. Включайте его вручную в таких случаях:
- Размер сцены ограничен, видеопамяти достаточно и нужно сохранить стабильными и качество изображения, и частоту кадров
- Камера движется быстро или часто телепортируется, загрузка узлов не успевает и появляются дыры или скачки детализации
- Презентации продуктового уровня, запись экрана, рендеринг секвенций и другие случаи, где изменения изображения во время загрузки недопустимы
- Различия плотности на границах узлов всё ещё заметны
Если после включения видеопамяти становится мало или падает частота кадров, значит сцена вышла за пределы применимости рендеринга с полной загрузкой — вернитесь к рендерингу по частям.
Примечание: на рендеринг с полной загрузкой всё равно действует максимальная дистанция рендеринга. Когда расстояние между камерой и данными превышает это значение, ничего не отображается.
Бюджет видеопамяти и ограничения ёмкости
Конвейер LCC2 объединяет постоянно находящиеся в памяти данные splat в один Buffer для рендеринга, поэтому объём данных, который может загрузить одна модель, зависит от ограничений ёмкости этих Buffer.
Структура Buffer и расход на один splat
Данные хранятся в двух Buffer:
| Buffer | Расход на один splat | Содержимое |
|---|---|---|
| Buffer базовых данных гауссиан | 40 байт | Положение, поворот, цвет, масштаб |
| Buffer сферических гармоник | 12 / 27 / 48 байт | Коэффициенты сферических гармоник, выделяются только для данных, которые их содержат |
Buffer сферических гармоник выделяется по количеству полос, фактически сохранённых в данных: 12 байт для полосы 1, 27 байт для полосы 2 и 48 байт для полосы 3. Данные с меньшим числом полос не резервируют место под самую высокую полосу, поэтому тот же объём видеопамяти вмещает больше splat.
Жёсткое ограничение одного Buffer
Unreal хранит размеры Buffer как 32-битные целые, что даёт теоретический предел 4 ГБ. Фактически плагин использует 4095 МБ (на 1 МБ меньше 4 ГБ), и этот запас предотвращает переполнение при подсчёте байтов во время загрузки. Ограничение применяется к каждому Buffer отдельно и не может быть увеличено настройками.
Настройка бюджета видеопамяти
Объём видеопамяти, который одна модель .lcc2 может держать постоянно, задаётся параметром LCC2 GPU Memory Budget (MB) в ProjectSettings > Plugins > LCC4Unreal:
| Параметр | Значение |
|---|---|
| По умолчанию | 2048 МБ |
| Диапазон настройки | 2048 ~ 8192 МБ |
| Вступает в силу | После изменения перезапустите редактор |
Бюджет — это сумма по двум Buffer, а не предел одного из них. Поэтому данные сферических гармоник полосы 3 считаются как 88 байт на splat (40 + 48), а данные без них — как 40 байт.
Этот бюджет ограничивает окно постоянного размещения при потоковой загрузке, поэтому применим только к .lcc2, где узлы можно подкачивать и выгружать. Одиночные форматы (.ply / .spz / .sog) должны находиться в памяти целиком и имеют собственные правила ёмкости; см. Ограничения загрузки одиночных форматов.
Дополнительный расход на Buffer сортировки
Перед рендерингом 3DGS splat сортируются по расстоянию до камеры. Результат сортировки зависит от вида, поэтому каждому виду нужен свой набор Buffer сортировки. Эта часть не входит в бюджет выше: каждый постоянно размещённый splat дополнительно стоит 16 байт на вид (по две копии ключа сортировки и индекса, используемых для попеременного чтения и записи при сортировке).
При бюджете 2048 МБ и данных со сферическими гармониками постоянно может размещаться примерно 24,4 млн splat, а Buffer сортировки для одного вида занимает около 372 МБ.
Левый и правый глаз стереорендеринга, каждый игрок при разделении экрана и каждый SceneCapture считаются отдельным видом. Buffer сортировки растут вместе с числом видов и не уменьшаются обратно после сокращения их числа, поэтому резервируйте видеопамять под максимальное число видов, которое встречалось.
Ограничения загрузки одиночных форматов
Одиночные форматы — это .ply / .spz / .sog, то есть форматы, которые содержат весь набор данных в одном файле без разбиения на узлы и информации о Level. Такие данные нельзя загружать частями: плагин может только декодировать их как один узел за один проход и держать их целиком в видеопамяти, поэтому существует определённый предел количества splat. Форматы .lcc и .lcc2 завершают разбиение на узлы ещё при экспорте и не относятся к одиночным форматам.
Как вычисляется предел
Предел для одиночных форматов не ограничивается LCC2 GPU Memory Budget. Этот бюджет задаёт размер окна постоянного размещения при потоковой загрузке, что имеет смысл только для .lcc2, где узлы можно подкачивать и выгружать. Одиночный файл — это один узел, который должен находиться в памяти целиком, и применение того же числа превратило бы его в искусственный жёсткий предел, отклоняющий модели, которые видеокарта спокойно вмещает. Поэтому ёмкость одиночного файла определяется напрямую оборудованием, и изменение бюджета на неё не влияет.
Плагин вычисляет предел перед загрузкой и отклоняет данные, которые его превышают. Предел равен меньшему из двух условий:
| Условие | Описание |
|---|---|
| Предел одного Buffer | 4095 МБ, делённые на шаг на один splat (40 Б без сферических гармоник, 12 ~ 48 Б с ними в зависимости от фактического числа полос) |
| Фактически доступная видеопамять | 90 % видеопамяти, доступной на карте в данный момент, с запасом для движка и драйвера |
Видеопамять измеряется по физическому расходу на splat, включая 16 байт Buffer сортировки. На платформах, где память карты нельзя запросить, используется только предел одного Buffer, а не угаданное число.
Число полос сферических гармоник напрямую меняет ёмкость: у данных с полосой 1 шаг сферических гармоник составляет 12 байт — четверть от полосы 3 (48 байт), поэтому та же карта вмещает заметно больше splat. Плагин считает по числу полос, фактически сохранённому в данных, а не всегда исходит из максимальных 48 байт.
Что происходит при превышении предела
Когда одиночные данные превышают предел, загрузка отклоняется, и Output Log выводит фактическое количество splat и текущий предел, а также указывает, вызван ли предел ограничением одного Buffer или доступной видеопамятью. Действуйте в следующем порядке предпочтения:
- Преобразуйте данные в
.lcc2, чтобы плагин загружал их по требованию, а не держал целиком в памяти; тогда суммарный объём данных больше не подпадает под этот предел. - Перейдите на данные с меньшим числом полос сферических гармоник или без них — это снизит расход видеопамяти на splat.
- Перейдите на карту с большим объёмом видеопамяти. Когда узким местом является доступная видеопамять, это единственная аппаратная мера, повышающая предел.
Переход на .sog или .spz не помогает превысить предел: сжатие этих форматов относится только к файлу на диске, и после декодирования каждый splat занимает ровно столько же видеопамяти.
Формат LCC2 не подпадает под этот предел
Формат .lcc2 разбивает данные на несколько узлов, и каждый узел декодируется и загружается отдельно. Любой отдельный узел остаётся далеко ниже приведённых пределов, поэтому суммарный объём данных не имеет жёсткого ограничения и может значительно превышать бюджет видеопамяти. Ограничиваются две величины времени выполнения:
| Ограничиваемая величина | Источник ограничения | Поведение при превышении |
|---|---|---|
| Splat, размещённые одновременно | Бюджет видеопамяти и предел одного Buffer | Давно не использованные узлы вытесняются с подкачкой и выгрузкой |
| Splat, отрендеренные в одном кадре | Около 89 млн (4095 МБ ÷ 48 Б) | Дальние узлы отбрасываются по расстоянию |
Объём рендеринга на кадр также ограничен Max Splat Num у Actor, и действует меньшее из двух значений.
Управление видеопамятью и настройка
Автоматическое освобождение при недостатке видеопамяти
Во время выполнения плагин постоянно отслеживает фактический расход видеопамяти на видеокарте. Когда расход превышает процент, заданный параметром Max GPU Usage Percentage For Release в ProjectSettings, узлы ранжируются по совокупности времени последнего рендеринга, частоты обращений, размера данных и Level, и первыми освобождаются давно не использованные. Узлы с высоким Level защищаются, чтобы при быстром повороте вида не появлялись большие дыры.
Связанные настройки:
| Настройка | Описание |
|---|---|
| Max GPU Usage Percentage For Release | Процент расхода видеопамяти, который запускает автоматическое освобождение (50 ~ 100 %) |
| GPU Release Percentage | Процент данных, освобождаемых при каждом срабатывании (10 ~ 100 %) |
| LCC2 GPU Memory Budget (MB) | Бюджет видеопамяти для данных splat одной модели |
Диагностика и корректировка недостаточного бюджета
Слишком маленький бюджет не приводит к сбою загрузки; вместо этого данные постоянно подкачиваются и выгружаются. Типичные признаки:
- При движении или повороте камеры появляются дыры, которые постепенно заполняются после остановки
- При покачивании вида одна и та же область постоянно то становится резкой, то размывается
- Локальная область остаётся размытой и не становится резкой даже при приближении
- Частота кадров колеблется в зависимости от количества содержимого в кадре и заметно падает на открытых видах
- В статичной сцене всё нормально, но при движении возникают рывки
Локальную размытость легко принять за плохое качество данных. Когда данные низкого Level не удаётся загрузить вовремя, плагин заполняет пропуск данными высокого Level, уже находящимися в видеопамяти, чтобы ни одна часть изображения не пропадала, а затем заменяет их, когда данные низкого Level готовы. Если из-за недостатка видеопамяти данные низкого Level постоянно вытесняются, эта область остаётся на высоком Level. Проверка — становится ли она резкой при приближении: обычно одной-двух секунд достаточно для заполнения, и если ничего не меняется, значит данные низкого Level не попадают в видеопамять.
Подтверждение: посмотрите прогнозируемый расход модели, выводимый в Output Log при загрузке, и сравните его с текущим бюджетом. Если прогнозируемый расход одной модели приближается к бюджету или превышает его, места для постоянного размещения недостаточно. Количество освобождений также можно наблюдать через stat LCC; постоянно растущее значение указывает на частое вытеснение.
Повышение бюджета работает только при наличии запаса на видеокарте. Бюджет — это лишь верхний предел запроса плагина; как только он превышает фактически доступную видеопамять карты, драйвер вытесняет данные в системную память, и падение частоты кадров оказывается хуже, чем при подкачке и выгрузке. Действуйте в следующем порядке:
- Уточните доступную видеопамять на карте. С учётом расхода на Buffer сортировки бюджет должен оставлять запас для самого движка и других ресурсов.
- Повышайте бюджет постепенно (2048 → 4096 → 8192) и после каждого перезапуска проверяйте, улучшились ли дыры и частота кадров.
- Если улучшение невелико, узкое место не в бюджете — сокращайте объём данных: уменьшите Max Splat Num, включите ограничение Max Distance или перейдите на данные без сферических гармоник.
Примечание: бюджет рассчитывается независимо для каждой модели. Когда в сцене размещено несколько LCC Actor, расход видеопамяти накапливается с числом моделей, поэтому соответственно уменьшайте бюджет на модель.