← Модуль 8/Mesh shaders и Nanite
EN
Модуль 8 · Технический deep-dive

Mesh shaders и Nanite

Крупнейший сдвиг геометрии за 20 лет: mesh shaders заменили пер-вершинный front-end конвейера на GPU-driven обработку кластеров, а Nanite сделал геометрию виртуализированной — как mipmaps и виртуальная память, но для полигонов. Итог — миллионы треугольников, автоматический per-pixel LOD и конец ручных LOD.
~17 мин🔬 GPU-геометрия
Суть за 30 секунд
Классический конвейер (input assembler → vertex → tess → geometry → raster → fragment) был пер-вершинным ~20 лет, и culling шёл поздно. Mesh shaders (2018–2020) заменили front-end: task-шейдер раздаёт работу, mesh-шейдер (в стиле compute) обрабатывает meshlet (кусок 64–128 вершин), сам куллит невидимые meshlet'ы и эмитит примитивы прямо в растеризатор — без input assembler и пер-вершинного горла. Nanite (UE5, 2022) построил на этом виртуализированную геометрию: меш дробится на микрокластеры (≤128 треугольников) в иерархию LOD, а GPU per-pixel выбирает уровень детализации ~1 треугольник на пиксель (как mipmaps выбирают детализацию текстуры). Двухфазно (visibility → material), плюс софтверный растеризатор для пиксельных треугольников. Результат: киношный ассет на миллионы полигонов кидаешь как есть — авто-LOD, авто-стриминг, ноль ручных LOD и pop-in. Стоимость геометрии отвязывается от сложности исходника и упирается в разрешение экрана. 2026: UE5.5 скиннинг в Nanite, Nanite 3.0, WebGPU.

Механизм: от пер-вершины к виртуализации

Пер-вершинное горло классики

20 лет конвейер начинался с input assembler, гнавшего вершины по одной (индексные буферы помогали, но модель фундаментально пер-вершинная), а culling треугольников шёл после вершинной обработки — на массивной геометрии ты тратил vertex-работу на невидимые и пиксель-мелкие треугольники ещё до того, как узнавал, что они не нужны.

Mesh shaders — GPU сам решает, что обрабатывать

Mesh shaders выкинули жёсткий front-end и заменили его двумя compute-подобными стадиями:

Это GPU-driven rendering: не CPU выдаёт по draw-call'у на объект (см. draw-call горло), а GPU сам решает, какую геометрию и с какой детализацией обрабатывать. Железо — Turing+ (RTX 20-серия), Xbox Series, PS5.

Nanite — виртуализированная геометрия

Nanite — это «mipmaps + виртуальная память, но для полигонов». Меш препроцессится в иерархию кластеров (meshlet'ов ≤128 треугольников) на многих уровнях детализации (кластерный DAG). В рантайме GPU per-pixel выбирает тот уровень кластера, что даёт ~1 треугольник на пиксель — не больше (лишняя детализация невидима), не меньше (не мылит). Точно как mipmaps выбирают разрешение текстуры под дистанцию. Ключевые трюки:

Стоимость отвязана от исходника

Главное следствие. Цель ~1 треугольник на пиксель значит, что число отрисованных треугольников ограничено экраном, а не ассетом:

Nrender≈ min(Nsrc,Npx)

4K — это ~8.3 млн пикселей, значит и потолок отрисованной геометрии ~несколько миллионов треугольников независимо от исходника: ассет на 10 млн и на 100 млн полигонов рендерятся примерно за одну цену (обрезаны экранным разрешением). Ты платишь за видимую детализацию на экране, а не за сложность модели — это и есть виртуализация. Отсюда «конец ручных LOD»: раньше художник лепил LOD0/1/2/3 и движок их переключал (pop-in); Nanite делает LOD непрерывным и автоматическим per-pixel — киношный ассет кидаешь как есть.

Честные границы

Nanite не бесплатен: visibility-буфер и софт-растеризатор имеют накладные, и ниже порога плотности геометрии классический рендер дешевле; первые версии плохо дружили с прозрачностью/альфа-листвой, скиннинг-анимация приехали позже (UE5.5), нужен современный GPU. Это инструмент под плотную статичную геометрию, а не универсальный тумблер — знать его профиль пригодности важнее, чем включать везде.

🕹 Во что поиграть — и что заметить

Nanite виден по отсутствию pop-in: геометрия не «морфит» ступеньками при приближении.

UE5-демо: Matrix Awakens / Land of Nanite 2020–21 · миллионы полигонов

Витрины Nanite: гигантские города и статуи из миллионов треугольников без единого видимого LOD-перехода. Подходишь вплотную — детализация просто есть, без ступенек и подмены меша.

🎮 Сделай: в Matrix Awakens (или UE5-игре) медленно подойди к детализированному объекту и ищи pop-in — момент, когда меш скачком становится детальнее. У Nanite его нет: LOD непрерывен per-pixel. Сравни с до-Nanite-игрой, где деревья/камни явно «переодеваются» при приближении.

Black Myth: Wukong / Clair Obscur UE5 · Nanite в проде

Зашипленные UE5-игры на Nanite: плотная киношная геометрия окружений тянется без ручных LOD-цепочек и без pop-in даже на консолях.

🎮 Сделай: включи (если есть) Nanite-визуализацию в UE-игре/редакторе — оверлей покажет плотность треугольников/кластеров per-pixel. Заметь, что на далёких объектах треугольников меньше (крупный кластер), вблизи — ~пиксельные. Это выбор LOD в реальном времени, а не переключение заранее сделанных мешей.

До-Nanite-игра контраст · видимый LOD-pop

Любая игра прошлого поколения: деревья, камни, персонажи вдали — низкополигональные и заметно «щёлкают» на более детальные при приближении. Это ручные LOD и их переключение — ровно проблема, которую Nanite убивает.

🎮 Сделай: в старой открытой игре иди к дереву/скале и лови ступенчатую подмену меша (LOD-pop). Затем оцени, сколько ручного труда художника — сделать 4 версии каждого ассета. Nanite виртуализирует этот труд, как mipmaps виртуализировали разрешение текстур.

Хардкор · архитектура Nanite: кластерный DAG, visibility-буфер, софт-растеризаторможно пропустить

Иерархия кластеров (DAG)

Меш офлайн бьётся на кластеры по ≤128 треугольников, а те рекурсивно упрощаются и группируются в родительские кластеры меньшей детализации — получается направленный ациклический граф уровней. В рантайме обход выбирает срез DAG, где ошибка проекции кластера ≤ ~пикселя: далеко берётся грубый родитель, близко — мелкие листья. Границы кластеров сшиваются заранее, чтобы не было щелей между разными LOD соседних кластеров (locked borders).

Visibility buffer вместо G-буфера

Проход 1 растеризует не цвет, а ID (кластер, треугольник) в экранный буфер — компактно. Проход 2 по этому буферу восстанавливает атрибуты и шейдит материал. Разделение «что видно» и «как покрасить» (как deferred) убирает overdraw-шейдинга по геометрии: материал считается ровно раз на видимый пиксель.

Почему софтверный растеризатор

Аппаратный растеризатор оптимизирован под треугольники в несколько пикселей: он всегда обрабатывает квады 2×2 (для производных текстур), поэтому на субпиксельном треугольнике 3 из 4 фрагментов — брак. При Nanite-плотности (~1 треугольник/пиксель) это катастрофа, поэтому мелкие треугольники Nanite растеризует сам, compute-шейдером (атомарные записи в visibility-буфер), а крупные отдаёт железу. Гибрид soft/hardware по размеру треугольника — ядро производительности Nanite.

Хардкор · модель mesh-шейдеров и GPU-driven cullingможно пропустить

Mesh shaders — это не «ещё стадия», а смена того, кто управляет геометрией.

Task → Mesh

Task-шейдер (amplification) запускается на группы работы и решает, сколько mesh-групп породить (может 0 — целиком закуллить ветку), передавая им payload. Mesh-шейдер — группа из десятков потоков кооперативно строит один meshlet: вершины в shared-памяти, per-primitive culling (backface, frustum, small-triangle), эмит компактного набора вершин+индексов прямо растеризатору. Нет отдельного input assembler и geometry-шейдера (который был медленным из-за неупорядоченного вывода).

GPU-driven против CPU-driven

Классика: CPU перебирает объекты, куллит, выдаёт draw-call'ы — CPU-bound на больших сценах (draw-call горло). GPU-driven: вся сцена — это буферы кластеров в GPU-памяти, а compute/task-шейдер сам куллит и формирует список работы (DrawIndirect), CPU почти не участвует. Это снимает пер-объектный CPU-оверхед и позволяет сцены с сотнями тысяч инстансов. Nanite — предельная форма: вся видимая геометрия обрабатывается несколькими indirect-диспатчами, а не миллионом draw-call'ов.

Аналогия
Nanite для геометрии — то же, чем mipmaps стали для текстур, а виртуальная память — для RAM. Mipmaps хранят текстуру во многих разрешениях и подставляют нужное per-pixel, чтобы далёкая текстура не стоила полного разрешения. Nanite хранит геометрию во многих уровнях детализации и подставляет ~1 треугольник на пиксель — платишь за видимое на экране, а не за сложность модели. И как виртуальная память тянет с диска только тронутые страницы, Nanite стримит только видимые кластеры. Старый способ (ручные LOD) — это как рисовать вручную по 4 размера каждой картинки и переключать их; Nanite это виртуализирует.
Почему это важно
Mesh shaders и Nanite — крупнейший сдвиг геометрии за 20 лет: они отвязывают стоимость рендера от сложности исходника (потолок — разрешение экрана), убивают ручные LOD и pop-in и переносят конвейер с CPU-выдаваемых per-object draw-call'ов на GPU-driven culling кластеров. Понимать это нужно любому, кто делает современный рендер. А ядро идеи — виртуализировать ресурс: хранить во многих уровнях детализации и платить только за видимое в нужной детализации — это глубокий переносимый паттерн, от mipmaps до paged-attention.
🔁 За пределами игр — куда это переносится
Урок — это виртуализация ресурса и адаптивная детализация под нужный выход: LOD, GPU-driven динамическая работа, стоимость по видимому.

ML / AI (твой домен): «плати за нужную детализацию, не за исходник» = адаптивный/условный компьют: mixture-of-depths, early-exit и каскады (дешёвая модель первой, дорогая — только на трудных входах), coarse-to-fine инференс. GPU-driven culling (GPU сам решает свою работу) ⇄ data-dependent графы и MoE-роутинг (модель маршрутизирует свой компьют). «Отвязать стоимость от сложности исходника, ограничить выходом» ⇄ инференс, ограниченный бюджетом токенов/разрешением, а не размером модели. Софт-растеризатор-для-мелких-треугольников (железо неэффективно ниже порога → делаем в compute) ⇄ специализация горячего пути под размер (мелкие matmul → другой kernel). А сама виртуализация = паттерн, объединяющий виртуальную память, mipmaps, стриминг — и в ML это PagedAttention (vLLM) (KV-кэш страницами, как виртуальная память!) и gradient checkpointing (пересчёт vs хранение). Meshlet ⇄ patch/tile снова (тайлы → ViT-патчи).

Графика / стриминг: LOD, mipmaps, virtual texturing, потоковая загрузка по видимости — одна семья приёмов «детализация по требованию».

Системы: виртуальная память и пейджинг, ленивая загрузка, CDN-edge-кэш нужного разрешения — плати за тронутое, а не за всё.

Принцип: не храни и не считай в полной детализации то, что не будет видно/использовано в ней. Виртуализируй ресурс по уровням и трать пропорционально нужному выходу.

🔧 Запусти и поковыряй — на домашнем компе
Во что играть — выше (🕹). Здесь — потрогать Nanite в редакторе.
🔧 Поковырять (UE5) ~40 мин, Unreal Engine 5
В UE5 импортируй высокополигональный ассет (Megascans/скан), включи Nanite и посмотри Nanite-визуализации: Triangles, Clusters, Overdraw. Полетай камерой — увидь, как плотность кластеров меняется с дистанцией per-pixel. Сравни стоимость сцены с Nanite и без (traditional LOD) в профайлере — заметь, где Nanite выигрывает (плотная геометрия), а где нет (редкая/прозрачная).
🧪 Потестить (границы) ~15 мин
Для трёх типов ассета — плотная статичная скала, анимированный персонаж, листва с альфа — реши, стоит ли Nanite и почему (плотность/скиннинг/прозрачность). Затем прикинь Nrender для своей сцены на 1080p vs 4K — как меняется потолок геометрии с разрешением?
Чеклист: увидел per-pixel выбор LOD в Nanite-визуализации; сравнил стоимость с/без Nanite; определил профиль пригодности по 3 ассетам; связал потолок геометрии с разрешением экрана.
Связи
основа
Рендер-конвейер — mesh shaders заменяют front-end конвейера и переносят culling на GPU (снимая draw-call горло).
основа
Железо-ограничения — виртуализация детализации по уровням — прямой потомок mipmaps и «индекс вместо данных».
смежное
Движки — Nanite (+Lumen) — главный графический ров UE5, определяющий выбор движка под фотореализм.
дальше
Джоб-системы — GPU-driven — часть общего сдвига «параллелизм и планирование работы».
Вопросы пытливого ума
Что именно Nanite «виртуализирует»?
Детализацию геометрии — ровно как mipmaps виртуализируют разрешение текстуры. Меш хранится не одной версией, а иерархией уровней детализации (кластерный DAG), и GPU per-pixel выбирает уровень, дающий ~1 треугольник на пиксель. «Виртуальная» — потому что физически в кадре присутствует лишь нужный срез иерархии, а остальное лежит на диске и стримится по видимости, как страницы виртуальной памяти. Ты работаешь с «бесконечно детальным» ассетом, но платишь только за видимую на экране детализацию. Это не сжатие и не упрощение при импорте — это рантайм-подстановка нужного уровня, как mipmap-выбор.
Зачем Nanite отдельный софтверный растеризатор для мелких треугольников?
Потому что аппаратный растеризатор оптимизирован под треугольники в несколько пикселей и всегда работает квадами 2×2 (нужны для производных при семплинге текстур). На субпиксельном треугольнике (а Nanite целит в ~1 треугольник/пиксель) 3 из 4 фрагментов квада — вне треугольника, то есть 75% работы впустую. При миллионах таких треугольников это убивает производительность. Поэтому Nanite мелкие треугольники растеризует сам, compute-шейдером с атомарными записями в visibility-буфер (никаких квадов, попиксельно), а крупные оставляет железу. Этот гибрид soft/hardware по размеру — одна из главных причин, почему Nanite вообще быстр.
Значит, с Nanite геометрия «бесплатна» — можно лить полигоны без счёта?
Нет — стоимость отвязана от исходника, но не нулевая. Потолок отрисованных треугольников ограничен экраном (~1 на пиксель), поэтому ассет на 10 и на 100 млн полигонов стоят примерно одинаково — но эта «одинаковая» цена не ноль: visibility-буфер, софт-растеризатор, обход DAG имеют фиксированный оверхед. Ниже некоторой плотности геометрии традиционный рендер дешевле Nanite. Плюс есть ограничения (прозрачность, скиннинг до UE5.5, память под данные Nanite). Правильная формулировка: Nanite делает стоимость геометрии функцией разрешения экрана, а не сложности модели, сняв ручные LOD, — но у него свой порог окупаемости и профиль пригодности.
«Конец LOD» — это правда или маркетинг?
Правда для ручных LOD плотной статичной геометрии — и это большое дело. Художникам больше не нужно лепить LOD0/1/2/3 каждого ассета и настраивать дистанции переключения, а игроки не видят pop-in: детализация непрерывна per-pixel. Но «конец LOD» не значит «конец идеи уровней детализации» — Nanite сам есть механизм LOD, просто автоматический и непрерывный (кластерный DAG — это LOD-иерархия). И вне зоны Nanite (прозрачное, листва с альфа, кое-где скиннинг, слабое железо) традиционные LOD ещё живут. Так что точнее: «конец ручного LOD-труда для геометрии, которую покрывает Nanite», а не исчезновение самого понятия.
Почему пер-вершинная модель была узким местом, раз индексные буферы переиспользуют вершины?
Индексные буферы убирают дублирование вершин, но не меняют модель: геометрия всё равно течёт через фиксированный input assembler по вершине, а решения о видимости (culling треугольников) принимаются поздно — после вершинной обработки. На массивной геометрии это значит тратить vertex-работу на треугольники, которые окажутся невидимыми или субпиксельными. Mesh shaders меняют модель на кластерную и GPU-driven: работа группируется в meshlet'ы, culling делается рано и на GPU (task-шейдер может вообще не запускать невидимый кластер), а вывод примитивов гибкий (не через жёсткий ассемблер). Дело не в дублировании вершин, а в том, когда и кто решает, что обрабатывать.
Что почитать