# Что перенять из графического стека Unreal Engine 5.8.2 Исследование исходников от 17 сентября 2026 года. Репозиторий: `../../../UnrealEngine`. Версия проверена в [Build.version](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Build/Build.version#L1); commit `16d75d84714512edfb744e1fd0a59e9c74d57873`. Рабочее дерево UE при начале исследования было чистым. Фокус — передовая графика для Faset Engine. Прочитаны выбранные C++-реализации и `.usf`/`.ush`-шейдеры, включая тела алгоритмов и места их подключения. Это выборочное исследование архитектуры, а не аудит всего UE. Движок не собирался, сцены и GPU-бенчмарки не запускались. Приоритеты и варианты прототипов ниже — инженерная оценка; чисел ускорения по результатам измерений здесь нет. ## Главный вывод Из UE стоит переносить **способы ограничивать работу**, а затем объединять их в систему. Nanite ограничивает и организует обработку геометрии, VSM — обновление теневых страниц, Lumen — трассировку и обновление освещения, MegaLights — число дорогих выборок света, Substrate — сложность обработки материалов. Результат зависит от хороших приближений, кэшей и проверки их актуальности. После завершения MVP первый серьёзный графический эксперимент — **GPU-driven visibility: indirect rendering + HZB + повторная проверка окклюзии**. Это полезная часть подхода Nanite, которая не требует сразу строить виртуализированную геометрию. Для первого небольшого визуального эффекта — **glints**. Для наиболее заметного изменения освещения — **гибридная GI с пространственным radiance cache**, если уже есть рабочая трассировка и temporal pipeline. ## Что выбрать в зависимости от цели 1. **Много объектов и высокая геометрическая плотность.** GPU culling → indirect draw → two-pass HZB → обычный mesh LOD; затем meshlet/cluster LOD → visibility buffer и сортировка shading work. Стриминг геометрии и software rasterizer добавлять после измерений на субпиксельных треугольниках. Первые этапы полезны самостоятельно. 2. **Динамическое непрямое освещение.** Гибридные screen/world traces, отдельное переиспользуемое представление освещения и бюджет обновления probes. Сначала ограничить прототип статической геометрией и diffuse GI. Полный Surface Cache Lumen с карточками не является обязательной первой ступенью собственной GI. 3. **Много динамических источников с мягкими тенями.** Подход MegaLights: выбирать небольшое число важных источников, трассировать visibility и восстанавливать изображение. Начинать после clustered light lists, motion vectors и базового denoiser. При 2–4 источниках общая инфраструктура может оказаться дороже обычного освещения. 4. **Чёткие тени на больших расстояниях.** VSM: виртуальные адреса, физический пул страниц, запросы от видимых поверхностей, кэширование и инвалидирование. Первая версия — один directional light и ограниченная сцена. Не начинать с большого числа локальных источников. 5. **Высокая детализация при меньшем внутреннем разрешении.** Идеи TSR: реконструкция с проверкой истории, обработка вновь открывшихся поверхностей и изменения шейдинга. Сначала правильные motion vectors и обычный temporal resolve; сложные эвристики TSR — следующий этап. 6. **Более богатые материалы.** Из Substrate взять ограниченные классы сложности и обработку по тайлам; из glints — фильтруемое распределение микробликов. Небольшой фиксированный clear-coat/двухслойный набор часто разумнее произвольного графа BSDF. 7. **Большие наборы текстур.** Virtual Texturing полезна, когда именно residency и объём текстур стали проблемой. Стриминг целых mip-уровней проще; переход на страницы имеет смысл по измеренному рабочему набору. ## Принятый план Faset — продвинутая графика после MVP Решения обновлены 18 сентября 2026. Подробные критерии продукта — в [PLAN.md](../../PLAN.md), технические границы — в [архитектуре](../ARCHITECTURE.md). Результаты чтения UE ниже сохраняются; они не означают, что эти системы уже есть в Faset, входят в MVP или обязательно будут воспроизведены целиком. **Принято:** собственный Vulkan 1.3 renderer/backend и Render Graph, SDL3, Slang → SPIR-V с совместимым HLSL; базовый путь без обязательного RT. Compiler работает в editor/cook, Player получает готовые shaders и metadata. Готовый SPIR-V не отменяет создания pipelines драйвером. Editor и Player — разные процессы; MCP доступен строго в редакторе, включая headless authoring/import/build, Play/Stop и логи редактора, без чтения или изменения runtime worlds/сессий игры. ### MVP (M2, M8–M9). Две демки и измеримый direct renderer Небольшая 2D-игра со спрайтами, прозрачностью, слоями и HUD; небольшая 3D-игра со статическими мешами, текстурами, простым PBR, солнцем и обычной shadow map. Обе проходят editor → save → Play → сборка самостоятельной игры на Linux и Windows. Начальный renderer: CPU frustum culling, direct draws, одна GPU queue, явные reads/writes, корректные barriers и отложенное освобождение ресурсов. Нужны имена проходов, validation, CPU/GPU timings и счётчики памяти; оптимизации graph culling, aliasing и async compute можно отложить. Готовность: предсказуемые save/build/Play, корректный resize/minimize, отсутствие ошибок validation, самостоятельный Player без редактора и MCP. Direct-путь остаётся эталоном для следующих этапов. Версия API не заменяет проверки features, formats, limits и драйверов конкретных GPU. ### P2. GPU visibility и обычный LOD Resident instance buffer → GPU frustum culling → fixed indirect bins → current HZB visualization → two-pass HZB с history reset → обычный mesh LOD по экранному размеру и hysteresis. Детальный контракт разобран в [исследовании 15](15-renderer-implementation-notes.md). Начальный материал — opaque, обычная hardware rasterization; 2D/прозрачность сохраняют отдельный порядок. Готовность: финальная depth/ID-картинка совпадает с baseline с учётом допустимых ties, нет пропавших объектов при открытии двери/camera cut, проверены пустые и предельно заполненные очереди. Измерять полный кадр и открытой, и закрытой сцены; HZB может добавить стоимость без выигрыша. Motion-vector target и TAA не являются предпосылкой первого HZB. ### P3, первая часть. Освещение и тени Расширить число локальных источников; вводить clustered light lists при подтверждённой необходимости. Для солнца — cascaded shadow maps, затем ограниченный atlas локальных теней. У shadow views собственные списки видимости: скрытый от основной камеры объект может отбрасывать видимую тень. Готовность — управляемые бюджеты, стабильность при движении камеры и понятный профиль обновлений. VSM остаётся отдельным дальнейшим экспериментом после обычных теней. ### P3, вторая часть. История и temporal Motion vectors камеры/объектов, согласованные jitter/unjitter transforms, reset при camera cut/resize, reprojection и rejection → TAA → позднее temporal upscaling. Проверять disocclusion, тонкую геометрию, движение и экспозицию, сравнивая с режимом без temporal. TSR не заменяет denoiser GI или direct lighting: сигнал и критерии доверия различаются. ### P4. Непрямой свет и исследовательские ветки Сначала запечённый непрямой свет и reflection probes, затем ограниченные screen-space дополнения с явным fallback. Динамическая GI без обязательного RT — самостоятельный исследовательский этап с бюджетами обновления/памяти, проверками света за камерой, утечек через стены и изменяющихся заслонов. Полный эквивалент Lumen не обещается. Дальнейшие ветки выбираются по измеренной проблеме, по одной: кластерный LOD/streaming и visibility buffer; виртуальные теневые страницы; стохастический direct lighting; VT; сложные материалы. Glints допустимы как изолированный BRDF-эксперимент. Это исследовательские возможности, не дополнительные обязательства MVP. На каждом этапе фиксируются сцена, путь камеры, настройки, GPU/driver, изображение, время и память; GPU-бенчмарки ещё не выполнены. ## Общие ошибки, которых помогают избежать исходники - **«Nanite — это просто mesh shaders».** Его полезность складывается из preprocessing, выбора детализации, culling, rasterization, material scheduling и residency. Не нужно делать mesh shaders условием первого прототипа. - **«Lumen — один ray tracing shader».** Это несколько представлений сцены, способов трассировки и кэшей, соединённых бюджетами и фильтрацией. Недостающий screen-space hit не равнозначен отсутствию геометрии в мире. - **«MegaLights делает любое число lights бесплатным».** Ограничивается дорогая выборочная оценка; выбор кандидатов, scene structures, денойзинг и качество также имеют цену. - **«Виртуальная память автоматически уменьшает работу».** Требуются реальные locality и повторное использование. Движущаяся геометрия и источники могут часто инвалидировать страницы, а резкое перемещение камеры создаёт всплеск запросов. - **«Temporal накопление скрывает все ошибки».** Неверные velocity и слишком доверчивая история дают ghosting. Если история отвергается почти всегда, сигнал остаётся шумным. Проверять следует видео и контролируемые движения. - **«Самая сложная версия UE — лучший старт».** Минимальная версия должна иметь собственный полезный результат и сравнимый baseline. Наличие множества permutations в UE показывает реальные ограничения интеграции, но не требует воспроизводить все варианты. ## Как читать дальнейший разбор В следующих разделах **«в исходниках»** означает подтверждённую реализацию этой ревизии, а **«прототип / перенять / проверка»** — предложение для собственного движка. Ссылки на исходники относятся к указанному commit; локальные checkout являются внешними необязательными материалами исследования и не входят в Faset. Термины: HZB — иерархическая пирамида глубины; closure — отдельная составляющая модели рассеяния; residency — какие страницы сейчас находятся в GPU-памяти; disocclusion — появление ранее закрытой поверхности; radiance cache — переиспользуемые оценки входящего света. ## Nanite / GPU Scene: что перенести в собственный renderer Основание: локальные тела C++/HLSL из `../../../UnrealEngine`, commit `16d75d84714512edfb744e1fd0a59e9c74d57873` (UE 5.8.2). Это исследование исходников, не выполненный GPU-профиль. Приоритеты, минимальные реализации и сценарии проверки ниже — инженерная оценка; чисел ускорения не измерял. Сосредоточился на треугольном пути: в этой ревизии уже есть отдельные ветки Nanite curves и voxels, поэтому нельзя автоматически приписывать всем путям свойства классического triangle Nanite. ### 1. GPU-driven visibility с двухфазным HZB — начать здесь **Что интересно.** Предыдущая глубина используется как предположение, которое исправляется в том же кадре. Main pass проверяет bounds в предыдущих transforms/view относительно предыдущего HZB; предполагаемо закрытые объекты отправляются в отдельную очередь. Main raster строит текущую глубину, затем новый HZB и post pass повторно проверяют отложенную очередь. В результате temporal occlusion не требует ждать CPU query и имеет путь восстановления для открывшихся объектов. **Доказательства.** `FBoxCull::HZB` строит `PrevFrustumCull` из `PrevLocalToTranslatedWorld` и `PrevTranslatedWorldToClip`, записывая результат в `bWasOccluded`: [NaniteCullingCommon.ush:623](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteCullingCommon.ush#L623). В post permutation используется текущий rect и текущий HZB: [там же:646](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteCullingCommon.ush#L646). Запись отложенного instance через wave-aggregated atomic и чтение количества для post — [NaniteInstanceCulling.usf:169](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteInstanceCulling.usf#L169), [243](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteInstanceCulling.usf#L243). Реальный порядок C++: main cull/raster [NaniteCullRaster.cpp:7008](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L7008), `BuildHZBFurthest` из scene depth и Nanite rasterized depth [7053](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L7053), post cull/raster [7071](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L7071). Это не просто декларация feature flag. **Не менее полезная основа — постоянная GPU Scene.** Primitive/instance данные адресуются ID, изменения накапливаются без повторного добавления primitive в dirty list, затем обновляются scatter upload. Видно в `FGPUScene::AddPrimitiveToUpdate` [GPUScene.cpp:1755](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/GPUScene.cpp#L1755), создании uploader [1218](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/GPUScene.cpp#L1218) и записи instance элементов в вычисленные scatter offsets [1358](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/GPUScene.cpp#L1358). Выигрыш архитектуры — culling, material lookup, тени и motion получают общие ID и таблицы; не нужно заново собирать данные каждого объекта на каждый draw. **Минимальный перенос.** Stable instance ID; GPU tables `{bounds, current/previous transform, mesh, material}`; dirty-range uploads; compute frustum/HZB; append-buffer видимых ID и indirect draw arguments. Сначала использовать обычные meshes/meshlets и hardware raster. Добавить очередь rejected, rebuild HZB и второй indirect draw. Для fixed-material opaque сцены это самостоятельный проект без Nanite builder и virtual geometry. **Ловушки.** Near-plane и деформация требуют консервативных bounds: сам UE пропускает HZB при пересечении near plane [NaniteCullingCommon.ush:635](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteCullingCommon.ush#L635). HZB-rect у UE привязан к центрам единственного sample; комментарий прямо предупреждает, что MSAA/conservative raster требуют другой формулы [NaniteHZBCull.ush:80](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHZBCull.ush#L80). Reverse-Z требует minimum reduction и соответствующего сравнения [195](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHZBCull.ush#L195). Добавить overflow counters для очередей и корректное сохранение previous transforms при переиспользовании ID. **Проверка.** 100 тыс. instances за крупными occluders: статичная камера, быстрый поворот, телепорт, движущаяся дверь. Сравнивать итоговую depth/ID-картинку с HZB-off reference, отдельно замерять main/post survivors, HZB build, culling и total GPU frame. Критерий успеха — отсутствие пропавшей видимой геометрии и выигрыш total frame на закрытой сцене, а не только меньше draw calls. На открытой сцене второй проход может оказаться чистой дополнительной стоимостью. ### 2. Visibility buffer + группировка shading по материалу — сильная архитектурная идея отдельно от Nanite **Что интересно.** Геометрический проход сохраняет адрес поверхности, затем shading восстанавливает атрибуты победившего треугольника и группирует пиксели по материалам. Geometry scheduling отделяется от дорогого material evaluation. Это позволяет иметь очень много кластеров без пропорционального количества CPU material draws; потенциальный выигрыш особенно интересен при дорогих материалах и overdraw. **Доказательства.** `PackVisPixelX` кодирует visible-cluster ID и triangle ID, `UnpackVisPixel` выделяет второй компонент depth: [NaniteDataDecode.ush:890](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteDataDecode.ush#L890). `WritePixel` пакует ID+depth в 64-bit value и выполняет `ImageInterlockedMaxUInt64`, согласованно выбирая depth и идентификатор: [NaniteWritePixel.ush:20](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteWritePixel.ush#L20). Material evaluation реально читает этот ID, получает visible cluster и page/cluster data [NaniteVertexFactory.ush:1187](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteVertexFactory.ush#L1187); ветка shading читает VisBuffer64 и запрашивает пересчёт barycentrics [1240](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteVertexFactory.ush#L1240). Binning состоит из count, reserve, scatter: wave count группирует material-bin matches [NaniteShadeBinning.usf:397](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteShadeBinning.usf#L397), выбор count/scatter shader path [861](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteShadeBinning.usf#L861), резервирование диапазонов [959](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteShadeBinning.usf#L959). Особенно ценная деталь: материалам без derivative ops выделяются отдельные pixels, остальным — quads; проверка `bNoDerivativeOps` находится [968](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteShadeBinning.usf#L968). Это не универсальная сортировка всех пикселей независимо от требований shader. **Минимальный перенос.** Обычный hardware depth+ID pass, таблицы vertex/index/material, один compute resolve для нескольких фиксированных PBR-material classes. Затем material bins и indirect compute dispatch. Для такого варианта не обязательно воспроизводить Nanite 64-bit atomics: обычные depth attachment и integer ID target подходят, если пишет только hardware path. Общая атомарная depth+ID операция нужна при независимых конкурентных software/hardware writers. **Ловушки.** UV derivatives, texture LOD, tangent handedness, degenerate triangles и deformation должны восстанавливаться согласованно с geometry pass. UE вычисляет triangle barycentrics [NaniteVertexFactory.ush:1043](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteVertexFactory.ush#L1043) и интерполирует UV/position/tangent данные [717](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteVertexFactory.ush#L717). «Shading once» не означает отсутствие helper lanes или повторного alpha-test: masked/WPO поверхности усложняют raster. Полный G-buffer не обязательно исчезает: visibility может быть только первым этапом material evaluation. Не рассчитывать на экономию памяти без измерения всех вспомогательных buffers и output targets. **Проверка.** Одинаковая сцена в G-buffer и visbuffer вариантах: число material classes 4/32/256, overlap слоёв 1/4/16, subpixel triangles, checker UV. Считать geometry+binning+resolve вместе; проверять mip selection и края, shader invocations, bandwidth и p95 frame time. При простых материалах расходы binning могут перевесить пользу. ### 3. Virtual geometry: согласованный LOD-срез, bounded residency и feedback **Что интересно.** Nanite объединяет offline и runtime алгоритмы: группы соседних кластеров упрощаются вместе, runtime выбирает достаточный уровень по screen-space error, GPU запрашивает недостающую детализацию, а резидентный более грубый уровень остаётся корректным drawable fallback. Ценная мысль — streaming и LOD составляют один протокол допустимых представлений поверхности. **Доказательства.** Builder объединяет children и упрощает triangles [ClusterDAG.cpp:1467](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Developer/NaniteBuilder/Private/ClusterDAG.cpp#L1467), заново делит результат в parent clusters [1558](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Developer/NaniteBuilder/Private/ClusterDAG.cpp#L1558), назначает всем parents группы одинаковые bounds/error [1587](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Developer/NaniteBuilder/Private/ClusterDAG.cpp#L1587). В simplifier позиции концов external edges блокируются [Cluster.cpp:1021](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Developer/NaniteBuilder/Private/Cluster.cpp#L1021). Поэтому независимое упрощение каждого meshlet не воспроизводит согласованность Nanite. В GPU traversal `ShouldVisitChildInternal` учитывает parent/min LOD errors и projected edge scales [NaniteClusterCulling.usf:258](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L258); cluster cut использует `SmallEnoughToDraw` [287](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L287). Ключ к graceful streaming: кластер принимается также при `NANITE_CLUSTER_FLAG_STREAMING_LEAF`, даже если критерий детализации ещё не достигнут [842](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L842). Leaf traversal формирует page-range request с priority [579](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L579). Менеджер рекурсивно добавляет page dependencies с повышенным приоритетом [NaniteStreamingManager.cpp:2579](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Engine/Private/Rendering/NaniteStreamingManager.cpp#L2579), увеличивает их refcount при регистрации [950](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Engine/Private/Rendering/NaniteStreamingManager.cpp#L950), при eviction выбирает только unreferenced LRU pages с нулевым refcount [2904](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Engine/Private/Rendering/NaniteStreamingManager.cpp#L2904). То есть простой LRU, игнорирующий зависимости, разрушает предпосылки traversal/decoding. **Минимальный перенос.** Сначала meshlet LOD hierarchy целиком в памяти, hardware raster и корректный cut. Затем фиксированный page pool, постоянно доступный coarse mesh, page table, GPU feedback, budgeted async upload и переход на детей только когда готова вся требуемая группа. Compression, GPU transcoding, partial group fixups, скелеты, voxels и curves — отдельные этапы. Это высокая сложность, но очень большой эффект для сцен с тяжёлой геометрией. **Дополнительный образец.** Persistent GPU workers обрабатывают общую очередь hierarchy nodes, а когда nodes ещё не готовы, берут cluster jobs: реальная ветка [NaniteHierarchyTraversal.ush:274](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHierarchyTraversal.ush#L274), fallback work [326](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteHierarchyTraversal.ush#L326). Для первой реализации послойные indirect dispatch проще отладить; MPMC scheduler оправдан только после профиля occupancy/launch overhead. **Проверка.** Asset set заведомо больше page pool, flythrough с возвратом и внезапной сменой направления; искусственная задержка I/O. Требовать ограниченного VRAM, отсутствия дыр/двойной геометрии на переходах и контролируемого LOD error; измерять missed requests, page churn, upload budget, visible cluster count. Полноценная Nanite-копия — большой самостоятельный renderer/asset pipeline, не библиотека для подключения за неделю. ### 4. Гибрид hardware/software raster для микротреугольников — исследовательский следующий шаг **Что интересно.** Один visibility target объединяет два raster пути: крупные/требующие clipping кластеры отправляются hardware rasterizer, мелкие — compute rasterizer. Это переносимая идея специализации под размер примитива, но наиболее аппаратно-зависимая из четырёх. **Доказательства.** `SmallEnoughToDraw` отдельно устанавливает `bUseHWRaster` по projected edge scale [NaniteClusterCulling.usf:297](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L297); near clipping принудительно включает HW [860](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L860). `EmitVisibleCluster` заполняет SW/HW списки с противоположных концов общего массива и отдельными atomic counters [688](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteClusterCulling.usf#L688). Compute entry вызывает `ClusterRasterize` [NaniteRasterizer.usf:2421](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteRasterizer.usf#L2421), triangle path готовит edge equations и вызывает adaptive raster [718](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteRasterizer.usf#L718). Внутри software raster есть ещё одна специализация: rectangle traversal для узких triangles, scanline для более широких или programmable shading [NaniteRasterizer.ush:292](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteRasterizer.ush#L292). Общая атомарная запись depth+ID описана выше. **Минимальный перенос.** После стабильного hardware visbuffer добавить opaque-only compute raster для кластера с маленьким screen bound. Всё остальное оставить HW. Подбирать порог на своей GPU по total raster time; не зашивать UE threshold как универсальную константу. Programmable raster/WPO/alpha cutouts вводить отдельно, когда оба пути совпадают по coverage. **Ловушки и проверка.** Нужны согласованные fill rules, subpixel precision, depth tie handling, synchronization, capability check для 64-bit image atomics. В текущем HW path есть явное предупреждение о проблемах depth precision у очень узких triangles [NaniteRasterizer.usf:3300](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteRasterizer.usf#L3300). Sweep треугольников от долей пикселя до сотен пикселей, front/back faces, shared edges и near-plane; сравнивать coverage с HW baseline, отдельно стрессировать overdraw и контенцию атомиков. Принимать только измеренный выигрыш в целевой сцене и на целевых GPU: наличие compute raster само по себе не доказывает ускорение. Рекомендуемый порядок: GPU Scene + indirect/two-pass visibility → простой visibility shading → resident cluster LOD → page streaming → гибридный raster и persistent traversal после профилирования. Дополнение по hardware prerequisites: mesh shaders не являются обязательным условием этой архитектуры и даже не единственным Nanite HW path. В реальном dispatch есть `DispatchIndirectMeshShader` для mesh-path и `DrawPrimitiveIndirect` в `else`: [NaniteCullRaster.cpp:3661](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Nanite/NaniteCullRaster.cpp#L3661). Поэтому `meshlet` здесь означает разбиение/единицу данных, а не обязательное использование mesh-shader API. В отличие от этого, приведённый UE visibility-write path явно ограждён `COMPILER_SUPPORTS_UINT64_IMAGE_ATOMICS` и имеет `#error` в неподдерживаемой non-depth-only ветке: [NaniteWritePixel.ush:27](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Nanite/NaniteWritePixel.ush#L27). Depth-only path использует обычный uint atomic. ## Lumen и MegaLights: механизмы для собственного renderer Проверены тела C++/HLSL в `../../../UnrealEngine`, commit `16d75d84714512edfb744e1fd0a59e9c74d57873`. Это статическое исследование: движок и GPU-бенчмарки не запускались. Оценки полезности, минимальные варианты и тесты ниже — мои инженерные предложения; описания UE привязаны к реализации. ### 1. Каскад трассировки: дешевое представление сначала, уплотнённый список незавершённых лучей затем **Что делает UE.** `TraceScreenProbes` сначала запускает экранную трассировку, после неё выбирает HWRT либо трассировку mesh SDF/heightfields, а финальным проходом обрабатывает оставшиеся лучи и применяет Radiance Cache/sky. Это именно условные альтернативы: HWRT и mesh SDF не обязательные последовательные ступени одной конфигурации. Сборка проходов: [LumenScreenProbeTracing.cpp:678](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Lumen/LumenScreenProbeTracing.cpp#L678), выбор HWRT: строка 795, software branch: 817, финальная compaction: 877, отключение global-SDF в HWRT permutation: 909. Экранный hit не принимается безусловно: HZB сообщает неопределённость, `bHit && !bUncertain` отбрасывает сомнительный результат, край экрана отсеивается стохастически, глубина прошлого кадра проверяется перед выборкой его цвета. Эти проверки находятся в [LumenScreenProbeTracing.usf:276](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenScreenProbeTracing.usf#L276), depth validation — 304–311, выборка radiance — 334–345. Это объясняет, почему просто добавить SSR/SSGI перед ray query недостаточно: нужна мера доверия и корректный переход между представлениями. `ScreenProbeCompactTracesCS` сохраняет лучи без hit, которые ещё не дошли до Radiance Cache, с дополнительными отсечениями по дистанции. `WavePrefixCountBits` считает локальные offsets; на группу делается одна глобальная atomic allocation, затем пишется плотный список. GPU сам формирует indirect dispatch arguments. Тела: [LumenScreenProbeTracing.usf:394](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenScreenProbeTracing.usf#L394), условие — 431, wave compaction — 455–481, indirect args — 508–523. Это переносимая оптимизация также для shadow rays, reflection rays и secondary bounces. **Минимум для своего движка.** G-buffer + HZB → экранный луч с hit/confidence/distance → append/scan оставшихся лучей → один существующий world-space backend → resolve. Не начинать с собственной SDF-системы, если уже есть BVH/ray queries. Первую версию сделать с простым append; wave-оптимизацию добавлять после измерений. Не терять mapping исходного pixel/ray ID при compaction, правильно сохранять достигнутую дистанцию и делать небольшой pullback для следующей ступени. **Условия и риски.** Нужны стабильная reprojection, motion vectors, предыдущее освещение, explicit GPU barriers и indirect dispatch. Повторное использование экранного света создаёт зависимость от видимости камерой и может образовать GI feedback; UE отдельно запрещает часть backface/foliage rays (`usf:99`). Качество geometry representations должно согласовываться, иначе границы экранной и мировой трассировки проявятся в движении. Compaction тоже стоит времени: при почти 100% misses она может проиграть прямой dispatch. **Проверка.** На одинаковых лучах сравнить world-only и hybrid: GPU ms по ступеням, долю rays, дошедших до world, количество indirect workgroups. Камера пересекает край экрана у тонкой перегородки; свет за камерой; движущийся объект; быстрый разворот. Проверять не только среднее изображение, но покадровую ошибку и вспышки на переходе backend. Успех — измеримое сокращение дорогих rays без систематического исчезновения света/occluders. ### 2. Radiance Cache: кэшировать дальнее освещение по запросу и распределять обновления на GPU **Что делает UE.** Потребитель помечает восемь соседних probes, необходимых для интерполяции: [LumenRadianceCacheMarkCommon.ush:111](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenRadianceCacheMarkCommon.ush#L111). Предыдущие probes сопоставляются с новым clipmap, используются повторно либо освобождаются; новые выделяются из пула. Это demand-driven cache, а не обязательная трассировка всей регулярной 3D-сетки каждый кадр. Сопоставление: [LumenRadianceCacheUpdate.usf:77](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenRadianceCacheUpdate.usf#L77); allocation: 289–333. Самая интересная отдельная технология — **гистограмма приоритетов вместо сортировки всех probes**. Новые probes получают bucket 0; остальные — логарифмический bucket по времени между последним trace/use с поправкой на clipmap (`GetPriorityBucketIndex`, строка 221). Гистограмма суммирует trace-cost, а не только число probes (270–281). Один маленький проход выбирает порог по бюджету (399–425), последний bucket ограничивается atomic-счётчиком (455–463). Новые probes без данных имеют особый путь: они обновляются, а превышающие бюджет могут трассироваться с меньшим разрешением (466–477). Поэтому нельзя обещать математически жёсткий лимит общего GPU-времени; это бюджет вычислительной работы с fallback для заполнения пустого кэша. Cache используется после локальной видимости. `GetRadianceCacheCoverage` задаёт минимальную дистанцию до интерполяции как `probe TMin + cellSize * sqrt(3)`; контракт требует предварительно помечать позиции. [LumenRadianceCacheInterpolation.ush:176](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenRadianceCacheInterpolation.ush#L176). Screen probes сокращают дальнюю трассировку до этой границы и затем применяют кэш: [LumenScreenProbeTracing.usf:754](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenScreenProbeTracing.usf#L754), применение — 825–835. Переносимая идея — переиспользовать дальнее низкочастотное освещение, сохранив локальную occlusion отдельно. **Минимум для своего движка.** Одна сетка вокруг камеры, radiance+depth на probe, pool+indirection, last-used/last-updated, 8–16 priority buckets и фиксированный ray budget; добавить clipmaps после работающего proof-of-concept. Начать с diffuse GI и rough reflections. Новым probes дать начальное приближение и отдельный режим заполнения. Отдельно показывать age, residency, miss rate и trace cost; без этих debug views сложно отличать неправильную трассировку от устаревшего кэша. **Риски и проверка.** Разреженные probes внутри стен, резкие перемены освещения, teleport и camera cut, забитый pool, clipmap seams. Предлагаемые benchmark-сцены: две комнаты с тонкой стеной; открывающаяся дверь; включение мощного emissive; телепорт на новую площадку. Считать число новых/повторно использованных probes, память, worst-frame cost и число кадров до восстановления заданного уровня ошибки против offline reference. Не принимать красивый статичный кадр как достаточную проверку. ### 3. Surface Cache: отделить стоимость материала и освещения поверхности от каждого ray hit **Что делает UE.** Card capture сохраняет diffuse color, card-space normal и emissive в atlas: [LumenCardBasePass.ush:134](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenCardBasePass.ush#L134), outputs 145–147. На hit выбираются подходящие cards по направлению normal и bounds, проецируется позиция, проверяется совпадение card-depth, затем читаются Direct/Indirect/FinalLighting atlases. Реальные тела: [LumenSurfaceCacheSampling.ush:235](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/SurfaceCache/LumenSurfaceCacheSampling.ush#L235), depth weights — 279–310, atlas reads — 312–314; selection — 464–525. Нормализованная radiance возвращается в `EvaluateRayHitFromCardSampleAccumulator` (578–592). Связь с HWRT явная: `CalculateSurfaceCacheLighting` по scene instance получает mesh-cards index и вызывает lookup, используя размер ray cone для радиуса фильтрации. Слишком близкие hits обнуляются для предотвращения self-intersection/GI feedback: [LumenHardwareRayTracingCommon.ush:647](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenHardwareRayTracingCommon.ush#L647). Таким образом точность ray intersection и стоимость hit lighting можно выбирать раздельно. Не следует утверждать, что весь Lumen всегда использует только это: есть отдельные hit-lighting пути. Кэш обслуживается постепенно. Раздельные бюджеты direct/indirect; приоритет страницы учитывает возраст, расстояние до камеры, близость frustum и high-res feedback: [LumenSceneLighting.usf:85](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Lumen/LumenSceneLighting.usf#L85), приоритет — 105–177, гистограмма — 180–205, выбор budget cutoff — 210–233. Lookup также пишет feedback и last-used page metadata: `LumenSurfaceCacheSampling.ush:594–618`. **Минимум для своего движка.** Не воспроизводить сразу automatic card generation и virtual paging. Для статической сцены можно начать с ограниченного набора planar captures или существующей UV-развёртки lightmap: baked material attributes + динамический low-resolution lighting atlas + validity. Трассировать существующим BVH и возвращать atlas radiance. Это адаптация принципа, не описание UE. Сначала только diffuse secondary lighting; зеркальные отражения требуют другого качества. **Цена и тест.** Инфраструктура ощутимая: parameterization/capture, atlas allocation, hit→surface mapping, invalidation для transform/material/light, обновление освещения. Thin geometry и вогнутые объекты дают coverage holes, view-dependent material нельзя точно представить одним nondirectional texel. Добавить debug invalid texels; UE тоже явно раскрашивает отсутствие данных (`Sampling.ush:626–630`). Тестировать материал с дорогой процедурной текстурой при росте secondary-ray count; сравнивать ray-hit shading ms/atlas-update ms/память. Отдельно проверять щели, окрашенный GI после движения объекта, смену emissive и старое освещение. Это более крупный проект, чем compaction или priority buckets. ### 4. MegaLights: фиксировать бюджет дорогой visibility, выбирать свет по вкладу и истории **Что делает UE.** Для каждого shading location рассматриваются lights его clustered cell; `GetLocalLightTargetPDF` оценивает освещение через shading routine без реальной shadow visibility, включает IES и формирует perceptual weight `log2(Lum + 1)`: [MegaLightsLightTargetPDF.ush:23](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsLightTargetPDF.ush#L23). `FLightSampler` хранит ограниченное число samples; `AddLightSample` потоково обновляет weighted selection и накопленную сумму: [MegaLightsSampling.ush:47](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsSampling.ush#L47), тело — 84–114. При финализации sample weight становится `WeightSum / selectedWeight`: [MegaLightsSampling.usf:544](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsSampling.usf#L544). История видимых lights уменьшает вероятность выбора скрытого источника, но не обязана обнулять её; при invalid history используется другой множитель. Учитывается изменение мощности, чтобы вновь ставший значимым источник не оставался подавленным: [MegaLightsSampling.usf:164](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsSampling.usf#L164), history multiplier — 203–210. Reprojected tile history и blue-noise offset — 325–387. Не называть эту конкретную цепочку автоматически «ReSTIR с temporal/spatial reservoir reuse»: здесь непосредственно подтверждены weighted sampling, visibility guiding и последующая фильтрация, не перенос reservoirs между pixels. Важное ограничение: ограничено число выбранных expensive samples, **не вся сложность независимо от количества lights**. Кандидаты всё ещё обходятся циклом (cell header — 317–319; loop — 409–440). Поэтому density lights влияет на candidate evaluation, а при большом числе существенных вкладов возрастает variance. Шум устраняется не просто TAA. Temporal denoiser использует shading confidence, neighborhood clamp отдельно для diffuse/specular и ограничивает число накопленных кадров; затем смешивает с `alpha = 1 / accumulatedFrames`: [MegaLightsDenoiserTemporal.usf:379](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsDenoiserTemporal.usf#L379), clamp — 390–419, accumulation — 422–446. Spatial filter учитывает depth, normals и luminance, причём specular normal tolerance зависит от ширины lobe: [MegaLightsDenoiserSpatial.usf:474](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/MegaLights/MegaLightsDenoiserSpatial.usf#L474). **Минимум для своего движка.** Clustered light lists, 1–4 stochastic shadow samples на pixel/half-res pixel, weighted streaming sampler, shadow ray backend, correct probability compensation, temporal accumulation с rejection. Visibility history guiding добавлять после unbiased/reference baseline; затем filter. Начать с point/spot и rough opaque, area lights добавить следующим этапом. Не применять denoiser к ошибочной вероятности: он может скрыть bias. **Проверка.** 10/100/1000 lights при фиксированном числе rays, но измерять отдельно candidate selection, visibility, shading, denoiser. Сопоставлять с exhaustive lighting/offline reference: яркий скрытый light, внезапное включение, движущаяся тень, glossy floor, history reset. Смотреть converged energy, variance, время восстановления после disocclusion и ghost trails. Реальный выигрыш — множество источников с предсказуемой стоимостью shadow queries; оплачивается шумом, памятью истории и сложностью устойчивого denoising. Приоритет для отдельного renderer: сначала compaction/staged tracing и visibility-budget sampling, затем GPU cache scheduling; Surface Cache — только если измерения покажут, что повторный hit shading действительно главный bottleneck. Никаких численных обещаний ускорения без реализации и benchmark. ## Продвинутая графика: VSM, virtual texturing, TSR Исследован локальный `../../../UnrealEngine`, commit `16d75d84714512edfb744e1fd0a59e9c74d57873` (UE 5.8.2). Ниже факты из прочитанных тел C++/HLSL-функций отделены от предложений для своего движка. Это статический разбор: UE не собирался, производительность на GPU не измерялась. Порядок внедрения зависит от исходного рендера: при отсутствии хорошего temporal pipeline начать с TSR-подобной реконструкции; при уже стабильном TAA и неудовлетворительных тенях — с VSM. VT окупается при реальной нехватке памяти на текстуры или дорогих материалах ландшафта. ### 1. Virtual Shadow Maps: кэшировать видимые страницы теней, отдельно статическую и динамическую часть **Что обнаружено.** Главное заимствование — превращение shadow rendering в обслуживание разреженного кэша, востребованного пикселями камеры. В UE размер страницы 128×128, а начальная таблица содержит 128×128 страниц: виртуальная сторона получается 16384 текселя. Это следует из вычисляемых констант, а не означает физическую 16K-текстуру для каждого источника света. [Константы VSM](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Shared/VirtualShadowMapDefinitions.h#L12). `InitPositionData` восстанавливает мировую позицию из глубины; `GeneratePageFlagsFromPixels` отмечает необходимые страницы directional lights через `MarkPageDirectional`. При этом marking предусматривает расширение на соседние страницы около границы, включая чередование диагонального направления. Это важно, потому что footprint фильтра тени может выйти за страницу, которую непосредственно запросил пиксель. [Восстановление позиции](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPageMarking.usf#L301), [расширение запросов](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPageMarking.usf#L480), [запрос directional page](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPageMarking.usf#L525). В `UpdatePhysicalPages` уже существующие страницы переиспользуются, получают возраст и переносятся в список запрошенных. Ключевая деталь: если страница сейчас не нужна, её dirty/invalidation flags сохраняются до будущего возвращения в кадр. Иначе ушедший за камеру объект оставит устаревшую тень после возвращения камеры. Отдельные флаги позволяют обновить только динамическую часть, сохранив статическую. [Управление возрастом и сохранение invalidation](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L280), [раздельная инвалидация](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L328). `AllocateNewPageMappings` берёт физическую страницу из available list, обязательно очищает прежнее отображение в page table, записывает новое и помечает обе части некэшированными. При переполнении пула запрос может остаться без физического backing: это реальное ограничение качества, которое должно быть видно в диагностике. `SelectPagesToInitializeCS` пропускает неизменённые страницы; `InitializePhysicalPagesIndirectCS` инициализирует обновляемую динамическую страницу сохранённой статикой. [Выделение](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L475), [пропуск cached pages](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L852), [копирование статической основы](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPhysicalPageManagement.usf#L993). **Как перенять.** Первый прототип: один directional light, фиксированный физический атлас, таблица virtual→physical, GPU bitset запросов, компактный список dirty pages, обычный depth raster для объектов, пересекающих страницы. Начать с одного уровня и твёрдых теней; затем добавить 3–5 clipmap уровней и привязку их координат к устойчивой сетке. UE также округляет центр каждого clipmap в пространстве света, что помогает сохранять кэш при движении камеры. [Snap clipmap origin](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VirtualShadowMaps/VirtualShadowMapClipmap.cpp#L356). Nanite для самого принципа не требуется: в этом checkout существует полноценный `RenderVirtualShadowMapsNonNanite`; но это не обещание дешёвого рендера больших классических мешей, пересекающих много страниц. [Путь обычной геометрии](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VirtualShadowMaps/VirtualShadowMapArray.cpp#L4389). Для своего варианта понадобятся стабильные instance IDs, world bounds, учёт изменения света/материалов/деформаций, compute atomics и корректные GPU barriers. Страницы надо инвалидировать по старому и новому положению переместившегося occluder — это предложение для архитектуры, а не утверждение о конкретной просмотренной функции UE. **Ловушки.** Смена направления солнца способна разрушить почти весь выигрыш кэша; анимированная листва и displacement требуют явной политики. Статический и динамический depth pools увеличивают расход памяти. Camera-visible marking не покрывает автоматически все прозрачные или объёмные receivers. Не начинать со SMRT: в UE `SMRTRayCast` действительно шагает по shadow depth samples и экстраполирует глубину/наклон, но это отдельная апроксимация мягких теней с численной терпимостью, а не трассировка полной геометрии. [SMRT](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapSMRTTemplate.ush#L26). **Проверка прототипа.** Сравнить кадр с режимом принудительного обновления всех страниц: статичная сцена после прогрева, локально движущийся объект, объект движется вне кадра и возвращается, camera teleport, вращение света, намеренно малый пул. Собирать requested/allocated/rendered/invalidated pages, промахи пула, долю повторно использованной статики, GPU ms marking/culling/raster/projection. Цель: после прогрева статичного вида обновления должны практически исчезнуть; выигрыш должен сохраняться в общих GPU ms, а не только в числе draw calls. **Приоритет: высокий для крупной статичной сцены; сложность высокая.** ### 2. Virtual Texturing: demand feedback, гарантированный грубый fallback и ограниченное обновление **Что обнаружено.** VT содержит полезный законченный контур управления качеством: материал читает page table на нужном mip, формирует идентификатор страницы, разреженно пишет feedback; CPU обрабатывает готовый readback, объединяет запросы, обновляет LRU резидентных страниц и заказывает отсутствующие. `FinalizeVirtualTextureFeedback` пишет только выбранные пиксели и ограничивает длину буфера. `CanMap`/`Map` опрашивают fence и прекращают перебор на неготовом результате. Это позволяет строить поток без обязательного ожидания GPU в том же кадре. [GPU feedback](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualTextureCommon.ush#L90), [page-table lookup](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualTextureCommon.ush#L361), [опрос fence](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureFeedback.cpp#L167). В `FeedbackAnalysisTask` соседние одинаковые запросы объединяются со счётчиком. `GatherRequestsTask` быстро распознаёт резидентную страницу и обновляет её использование, не создавая лишний load. `FTexturePagePool::Alloc` берёт LRU-страницу из heap, удаляет старые mappings и назначает её новому producer. [Объединение feedback](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureSystem.cpp#L1455), [resident fast path](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureSystem.cpp#L1606), [LRU allocation](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/TexturePagePool.cpp#L275). Наиболее ценная деталь — fallback является частью отображения страниц. `UnmapPage` ищет ближайшего резидентного предка и ставит в очередь update page table на более грубую страницу. Грубый корень ожидается закреплённым в памяти. Шейдер использует фактический mip из записи, вычисляет локальные UV, учитывает border и масштабирует градиенты. Поэтому eviction может снижать детализацию вместо появления пустой/чужой текстуры. [Ancestor fallback](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/TexturePageMap.cpp#L113), [UV и derivatives](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/VirtualTextureCommon.ush#L819). Планировщик различает streaming pages и runtime-generated pages: `SubmitThrottledRequests` расходует отдельные бюджеты, так как асинхронный I/O и генерация GPU-материала имеют разный профиль стоимости. Оставшийся бюджет может идти на continuous updates. `SubmitRequests` различает Pending/Available и ограничивает фактическое production. [Бюджеты SVT/RVT](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureSystem.cpp#L2190), [готовность producer](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/VT/VirtualTextureSystem.cpp#L2300). **Как перенять.** Практичный старт — один большой terrain albedo atlas: нарезанные offline mip tiles с border, небольшой physical atlas, page table, закреплённый самый грубый mip, feedback readback ring, CPU дедупликация, асинхронное чтение и upload budget. Добавить normal/roughness только после проверки согласованного residency нескольких слоёв. Система producer должна отвечать «pending/ready», а публикация нового mapping должна происходить после готовности данных. Для своего движка полезно ограничивать не только tiles/frame, но и bytes/frame и время генерации: одинаковое количество страниц не означает одинаковую стоимость. RVT — следующий самостоятельный эксперимент: bake нескольких дорогих слоёв ландшафта/декалей в востребованные страницы и затем дешёвая выборка. Этот механизм уменьшает повторную работу материалов, но требует региональной invalidation после изменений и контроля устаревших страниц. Он не нужен игре с небольшим обычным набором текстур: там цена dependent texture fetch, feedback и авторского tooling может превысить выигрыш памяти. **Проверка прототипа.** Teleport через terrain при пуле существенно меньше рабочего набора; медленное вращение, быстрый полёт, высокий anisotropy, переход mip и граница тайлов, намеренная задержка I/O. Условие корректности: ни одного sampling из перераспределённой чужой страницы, fallback всегда валиден, отсутствие seams. Измерять полезные загруженные bytes, residency hit rate, латентность запроса до показа, долю показа грубого mip, повторные загрузки/секунду, CPU feedback cost, GPU стоимость выборки. **Приоритет: условно высокий для больших материалов/мира; средний либо низкий без давления на VRAM.** ### 3. TSR: хранить доверие к истории и отдельно распознавать движение, disocclusion и мерцание **Что обнаружено.** Самое переносимое здесь — структура информации, которую temporal reconstruction сохраняет между кадрами. В `DilateVelocityCS` UE читает 3×3 depth/velocity neighborhood, выбирает ближайшую глубину, вычисляет движение от этой поверхности и ошибки репроекции. Это уменьшает неправильное использование фонового motion vector на границе объекта. Одновременно существуют флаги pixel animation и temporal responsiveness. [Depth-aware velocity dilation](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRDilateVelocity.usf#L177). `RejectShadingCS` формирует отдельно разрешение ослабить clamp и множитель доверия; при disocclusion без resurrection оба обнуляются. `UpdateHistoryCS` clamp-ит цвет истории по текущему окружению, ограничивает её вес по скорости движения, затем смешивает с текущим сигналом через HDR-aware weights. Вместе с цветом записывается validity, специально квантованная под 8-битное хранение. То есть история — это цвет плюс информация, насколько на него можно опираться, а не постоянный коэффициент lerp. [Выход rejection](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRRejectShading.usf#L858), [clamp, движение, blend и validity](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRUpdateHistory.usf#L1249). Особенно интересен `ComputeMoireError`: он следит за сменой знака временного градиента яркости, накапливает вариацию и счётчик, сбрасывает их при disocclusion и подавляет послабление для движущихся/изменённых пикселей. Затем оценка мерцания расширяет допустимый интервал истории в `MeasureRejection`. Так алгоритм разрешает дополнительное усреднение там, где jitter заставляет статичную мелкую деталь чередоваться, но старается не размазать настоящую анимацию. [Детектор мерцания](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRShadingAnalysis.ush#L411), [расширение допустимого диапазона](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/TemporalSuperResolution/TSRShadingAnalysis.ush#L567). UE также допускает history размером от 100% до 200% выходной стороны и отдельный resolve. Это не бесплатная резкость: удвоение обеих сторон даёт в четыре раза больше текселей соответствующих history buffers. [Размер истории](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/PostProcess/TemporalSuperResolution.cpp#L2054), [resolve](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/PostProcess/TemporalSuperResolution.cpp#L3388). **Как перенять.** Сделать собственный минимальный temporal upscaler: jitter; цвет, depth, корректные motion vectors для камеры/объектов/skinning; 3×3 dilation; reprojection; depth disocclusion; цветовой neighborhood clamp; отдельный validity buffer; spatial fallback для раскрытых областей. Начать с history в выходном разрешении, стабильной экспозиции и opaque геометрии. Затем добавить коррекцию экспозиции, responsive mask для анимированных материалов/частиц и простую temporal luminance статистику. Память предыдущих деформированных вершин и дисциплина jitter conventions зачастую важнее очередного фильтра в конце пайплайна. Не переносить сразу весь набор TSR permutations, half-precision tensor abstractions, resurrection, lens distortion и анализ тонкой геометрии. Это связанные оптимизации промышленной реализации, которые делают маленький прототип существенно сложнее. При корректном базовом upscaler можно отдельно проверить историю повышенного разрешения и antialiasing для тонких линий. Детектор мерцания принципиально допускает компромисс стабильности против ghosting: требуется управляемое ограничение его действия, а не глобальное увеличение history weight. **Проверка прототипа.** Зафиксировать воспроизводимые sequence: решётка/провода в статике; медленный pan; тонкий движущийся объект перед контрастным фоном; раскрытие фона; emissive flicker; листва; particles; резкий exposure jump; camera cut; переключение render scale. Сравнивать с высокоразрешённой эталонной последовательностью и простым TAA baseline. Помимо GPU ms и bytes истории нужны temporal variance в статике, ошибка после disocclusion, длина ghost trail и сохранение контраста тонких деталей. Нельзя принимать результат только по красивому неподвижному кадру. **Приоритет: высокий и наиболее универсальный из трёх; минимальный вариант средней сложности, достижение качества UE — большой отдельный проект.** Общая архитектурная идея всех трёх подсистем: тратить вычисления на востребованную часть сигнала, явно хранить состояние и уверенность кэша, проектировать отказ/устаревание как нормальный режим и визуализировать причины повторной работы. Сначала вывести в debug view pages/invalidations, fallback mip и history validity — это часть самой технологии, а не косметика после реализации. ## Дополнительные механизмы: RDG, Substrate, glints ### Render Dependency Graph — инфраструктура сложного рендерера **В исходниках.** API требует описывать используемые ресурсы в параметрах прохода; граф выводит барьеры и время жизни: [RenderGraphBuilder.h:45](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/RenderCore/Public/RenderGraphBuilder.h#L45). Это подтверждается реализацией, а не только комментарием API: `RenderGraphBuilder.cpp:1310` обходит producers от нужных выходов и снимает флаг culling; `:1327` компилирует граф; `:1383` описывает и обрабатывает недостижимые проходы; `:1426` объединяет совместимые raster passes. В `:1562` строятся fork/join для async compute, причем при отключенном async transient aliasing времена жизни расширяются на область параллельного исполнения (`:1590`, `:1646`). `:3217` передает fence-ограничения transient allocator при создании и освобождении ресурсов; `:3783` — компиляция барьеров. **Идея для собственного движка.** Объявление `pass → reads/writes → output` позволяет безопасно добавлять HZB, denoise, shadow pages, history buffers. Начать с одной GPU queue, явных read/write и imported/exported ресурсов и отладочного просмотра графа. Удаление ненужных проходов и lifetime-based reuse ресурсов добавить после корректного базового пути; только потом aliasing физической памяти и async compute. **Ловушка.** Два ресурса, стоящие последовательно в CPU-списке, могут одновременно использоваться GPU на разных очередях. Декларировать ресурсы следует на уровне mip/subresource, либо консервативно считать весь ресурс одним состоянием. History resources живут между кадрами и должны быть external/persistent. Экспорт и побочные эффекты — roots графа. Нельзя обещать, что сам граф ускорит каждый кадр: он имеет CPU overhead. **Проверка прототипа.** После отключения эффекта все изолированные предшественники исчезают из GPU capture; attachment/compute/readback цепочка работает с validation; повторное использование памяти не меняет изображение; отдельно измерять compile CPU time, GPU pass times и peak transient bytes. При нескольких frames in flight завершение CPU кадра не означает возможность переиспользовать память. ### Substrate — ограниченная сложность материала + специализированные тайлы **В исходниках.** [SubstrateTranslatorCommon.cpp:687](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Engine/Private/Materials/SubstrateTranslatorCommon.cpp#L687) получает бюджет bytes/closures per pixel. В `:695` при выходе за бюджет компилятор выбирает глубокий оператор для parameter blending; в `:705` после исчерпания этого упрощения отключает optional features; `:1319` проверяет сразу лимиты памяти и closures; `:1433` повторяет цикл до выполнения бюджета. Это **компиляционное** упрощение, не измеритель GPU времени и не автоматический runtime LOD по дистанции. [SubstrateMaterialClassification.usf:172](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Substrate/SubstrateMaterialClassification.usf#L172) читает компактный заголовок материала; `:178` различает simple/single/complex/special; `:358` пишет отдельные списки тайлов и indirect counters; `:443` конвертирует их в dispatch args. `SubstrateDeferredLighting.ush:112` перебирает closures и распаковывает BSDF. C++ wiring: [Substrate.cpp:1746](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Substrate/Substrate.cpp#L1746) выбирает permutation, `:1774` — tile size 8 или 16, `:1785` — indirect args. **Что перенять.** Для начала PBR + clear coat как два фиксированных класса, компактные feature flags, экранные тайлы simple/complex и специализированные lighting shaders. Следующий шаг — максимум 2–3 closures и явный бюджет material buffer. Самостоятельно спроектировать предупреждения authoring: что было упрощено и почему. Полный граф произвольных физических слоев — отдельная большая задача. **Ловушки.** Если complex-пиксели равномерно рассыпаны по экрану, сложными становятся почти все тайлы. Классификация стоит GPU времени и окупается не всегда. Несколько closures увеличивают расходы не только direct lighting, но и GI/reflections и историю. Влажность/лак можно показать обычным clear-coat BRDF, не строя весь Substrate. **Проверка.** Сравнить одинаковое изображение с универсальным shader и classified shaders на сценах: только simple, локальные complex, шахматное смешение; измерять classification + lighting целиком. Отдельно проверять material compiler, что лимит bytes/closures соблюдается и упрощение видно художнику. ### Glints — относительно изолированный эффект для материалов **В исходниках.** [GlintThirdParty.ush:13](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Substrate/Glint/GlintThirdParty.ush#L13) называет исходную работу Chermain et al. 2021; `:425` — `f_P`, который использует view/light direction и UV derivatives; `:518` выбирает LOD по footprint; `:543` оценивает распределения двух уровней; `:553` смешивает их. В [SubstrateEvaluation.ush:308](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Shaders/Private/Substrate/SubstrateEvaluation.ush#L308) результат используется в `Substrate_D_Glint`, а `:320` обеспечивает переход к GGX. [GlintShadingLUTs.cpp:122](https://github.com/EpicGames/UnrealEngine/blob/16d75d84714512edfb744e1fd0a59e9c74d57873/Engine/Source/Runtime/Renderer/Private/Substrate/Glint/GlintShadingLUTs.cpp#L122) получает LUT texture, `:140` выбирает словарь, `:156` задает его параметры. **Что это дает.** Материал с различимыми микробликами — например, краска с частицами, блестки или снег — вместо только гладкого среднего блика. Ключевая переносимая идея: фильтровать распределение микрофасеток с учетом footprint и масштаба, а не просто добавлять случайные яркие точки. **Прототип.** Один opaque-материал, tangent basis, UV derivatives, lookup-словарь, один источник света. Обособить glint BRDF от общей системы слоев; затем добавить normal-map filtering, area lights и environment lighting. По охвату архитектуры это существенно меньше Nanite/Lumen, но математика фильтрации все равно нетривиальна. **Проверка.** Плавно двигать камеру и свет, менять roughness/плотность, уменьшать объект до субпиксельного размера; проверять мерцание, устойчивость энергии и переход к обычному specular. Сравнивать с supersampled reference, измерять добавочное GPU время. Стабильность не доказывается одним скриншотом. **Публичный первоисточник.** https://xavierchermain.github.io/glint_anti_aliasing/ — статья, видео и ссылка на оригинальный код. Не делать вывода о лицензии всей интеграции UE из лицензии исходной исследовательской реализации. ## Публичные первоисточники для дальнейшего изучения Результаты выше основаны на локальной ревизии. Публичные материалы помогают восстановить замысел и найти самостоятельные алгоритмические описания: - [Nanite: A Deep Dive, SIGGRAPH 2021](https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf) — оригинальный дизайн. Ограничения ранней версии из доклада не следует автоматически переносить на UE 5.8.2. - [Lumen, SIGGRAPH 2022](https://advances.realtimerendering.com/s2022/SIGGRAPH2022-Advances-Lumen-Wright%20et%20al.pdf) — устройства представлений сцены и переиспользования освещения. - [MegaLights, документация Epic](https://dev.epicgames.com/documentation/unreal-engine/megalights-in-unreal-engine) — авторы отдельно отмечают влияние пересекающихся lights на выбор кандидатов и компромиссы denoising; это согласуется с просмотренными циклами shader-кода. - [MegaLights: Stochastic Direct Lighting, SIGGRAPH 2025](https://advances.realtimerendering.com/s2025/content/MegaLights_Stochastic_Direct_Lighting_2025.pdf) — материалы авторов об архитектуре прямого света. - [Geometric Glint Anti-Aliasing, страница авторов](https://xavierchermain.github.io/glint_anti_aliasing/) — описание фильтрации микробликов, публикация, видео и оригинальный исследовательский код. Полный перенос исходников модулей здесь не предлагается: описаны механизмы и собственные минимальные реализации. Выбор технологий и их эффективность нужно подтвердить на будущих целевых сценах и GPU.