548 lines
22 KiB
Markdown
548 lines
22 KiB
Markdown
# Renderer Notes
|
||
|
||
Цель renderer-работы в этом проекте: добиться ощущения производительности оригинального клиента WoW 3.3.5a в Godot, без видимых фризов при переходе ADT -> ADT и без постоянного отката видимых участков к низкому качеству.
|
||
|
||
Этот документ фиксирует текущее состояние рендера, сделанные оптимизации и практические правила дальнейшей работы.
|
||
|
||
## Главный принцип
|
||
|
||
Оригинальный клиент WoW выживал на слабых CPU за счет агрессивного сокращения CPU work:
|
||
|
||
- мало draw calls;
|
||
- крупные батчи terrain;
|
||
- instancing/батчинг повторяющихся props;
|
||
- ленивое и заранее подготовленное подключение деталей;
|
||
- отдельные LOD/visibility решения для terrain, M2 и WMO;
|
||
- заранее подготовленные mipmaps и компактные texture formats.
|
||
|
||
В Godot мы идем тем же путем: runtime не должен строить тяжелую геометрию, декодировать много текстур или инстанцировать большие сцены в момент, когда игрок пересекает границу ADT.
|
||
|
||
## Основные файлы
|
||
|
||
- `src/scenes/streaming/streaming_world_loader.gd` - главный runtime streamer мира.
|
||
- `src/scenes/streaming/eastern_kingdoms_streaming.tscn` - текущая сцена для проверки Azeroth/Eastern Kingdoms.
|
||
- `addons/mpq_extractor/loaders/adt_builder.gd` - сборка ADT terrain, control splat материалов и MH2O liquids.
|
||
- `addons/mpq_extractor/loaders/m2_builder.gd` - сборка статического M2 mesh/material.
|
||
- `addons/mpq_extractor/loaders/wmo_builder.gd` - сборка WMO group meshes, doodads и MLIQ liquids.
|
||
- `addons/mpq_extractor/loaders/wow_liquid_material.gd` - общий shader material для ADT/WMO жидкостей.
|
||
- `src/scenes/sky/wow_sky_controller.gd` - DBC-based outdoor lighting/sky controller.
|
||
- `src/scenes/player/third_person_wow_controller.gd` - тестовый third-person controller.
|
||
- `src/native/src/adt_loader.cpp` - ADT/MH2O parsing.
|
||
- `src/native/src/blp_loader.cpp` - BLP decoding.
|
||
- `src/native/src/m2_loader.cpp` / `.h` - M2/SKIN parsing.
|
||
- `src/native/src/wmo_loader.cpp` - WMO/MLIQ parsing.
|
||
|
||
## Runtime Streaming
|
||
|
||
`StreamingWorldLoader` строит мир вокруг focus/camera/player. Карта берется из `WDT`, поэтому для Azeroth загружается реальный список ADT tiles, например:
|
||
|
||
```text
|
||
StreamingWorld: loaded 687 ADT tiles from WDT for Azeroth
|
||
```
|
||
|
||
Важные runtime очереди:
|
||
|
||
- `tile` - загрузка/парсинг ADT tile.
|
||
- `terrainup` - upgrade terrain до полного качества.
|
||
- `terrainctl` - подключение control splat terrain cache.
|
||
- `terrainsplat` - legacy/high quality splat path, сейчас не основной путь.
|
||
- `detail` - запуск деталей tile.
|
||
- `m2task` - background grouping M2 placements.
|
||
- `m2mesh` - загрузка/финализация M2 mesh resource.
|
||
- `m2build` - создание MultiMesh групп.
|
||
- `wmobuild` - подключение WMO instances.
|
||
- `wmogroups` - подключение render-cache групп WMO.
|
||
- `lod+` / `lod-` - создание/удаление tile LOD.
|
||
- `chunk+` / `chunk-` - legacy chunk mesh ops.
|
||
|
||
Фриз-профили пишутся строками вида:
|
||
|
||
```text
|
||
HITCH 22.75ms refresh=false queues[...] terrainup=21.99 terrainq=0.28 m2build=0.08
|
||
```
|
||
|
||
Главный смысл этих логов: если hitch больше порога, смотреть самый большой named section. Мы уже находили так bottlenecks:
|
||
|
||
- `details=4000-5800ms` - тяжелая синхронная сборка деталей.
|
||
- `m2build=20-75ms` - слишком крупные MultiMesh записи за один frame.
|
||
- `wmobuild=300-1100ms` - тяжелое инстанцирование WMO `.tscn`.
|
||
- `terrainsplat=150-220ms` - runtime high-quality splat generation.
|
||
- `terrainup=20-45ms` - слишком дорогой upgrade terrain за frame.
|
||
|
||
## Quality Presets And Budgets
|
||
|
||
Главные настройки находятся в `streaming_world_loader.gd`:
|
||
|
||
- `quality_preset`: `Performance`, `Balanced`, `High`, `Custom`.
|
||
- `tiles_per_tick`
|
||
- `max_concurrent_tile_tasks`
|
||
- `tile_finalize_ops_per_tick`
|
||
- `terrain_upgrade_finalize_ops_per_tick`
|
||
- `terrain_control_splat_cache_finalize_ops_per_tick`
|
||
- `m2_build_groups_per_tick`
|
||
- `m2_multimesh_batch_size`
|
||
- `m2_mesh_finalize_ops_per_tick`
|
||
- `wmo_build_instances_per_tick`
|
||
- `wmo_render_group_ops_per_tick`
|
||
|
||
Практическое правило: если новая оптимизация возвращает hitch, сначала уменьшать per-frame budgets, а не отключать качество целиком. Видимое качество должно приходить заранее, вне поля зрения игрока.
|
||
|
||
## Terrain Rendering
|
||
|
||
### Старый подход
|
||
|
||
Изначально terrain мог быть слишком мелко разбитым:
|
||
|
||
- много chunk/node операций;
|
||
- отдельные материалы/меши;
|
||
- слабый контроль над моментом upgrade;
|
||
- низкое качество или мыло при возврате в уже посещенный участок.
|
||
|
||
Это приводило к CPU overhead и видимому popping.
|
||
|
||
### Текущий подход
|
||
|
||
Основной путь качества сейчас - `ControlSplatADTTile`:
|
||
|
||
- geometry хранится как готовый tile mesh;
|
||
- texture set собирается в `Texture2DArray`;
|
||
- alpha maps собираются в один atlas;
|
||
- layer ids хранятся в layer index map;
|
||
- material подключается через `ADTBuilder.apply_control_splat_material`;
|
||
- runtime должен только загрузить cache resource и применить material, а не пересобирать splat texture на лету.
|
||
|
||
Кеш:
|
||
|
||
```text
|
||
res://data/cache/terrain_control_splat_v3/<Map>/<Map>_<x>_<y>.res
|
||
```
|
||
|
||
Формат:
|
||
|
||
```text
|
||
ControlSplatADTTile.FORMAT_VERSION = 3
|
||
StreamingWorldLoader.REQUIRED_CONTROL_SPLAT_TILE_FORMAT_VERSION = 3
|
||
```
|
||
|
||
Команда bake:
|
||
|
||
```powershell
|
||
$exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
||
& $exe --headless --path . --script res://src/tools/build_adt_control_splat_cache.gd -- --map Azeroth --jobs auto --texture-size 256 --lod 0 --force
|
||
```
|
||
|
||
Важно: `--force` пересобирает cache. Не запускать его случайно, если задача не про bake.
|
||
|
||
### Texture Quality
|
||
|
||
Что было сделано:
|
||
|
||
- перешли от baked albedo как основного visible quality к control splat;
|
||
- оставили baked/streaming terrain как fallback и warm path;
|
||
- добавили/используем full quality terrain радиус;
|
||
- добавили retention/cache для full-quality meshes, чтобы возврат в tile не сбрасывал видимый участок обратно в низкое качество;
|
||
- добавили логи `TERRAIN_QUALITY`, чтобы понимать, когда видимый набор tile действительно готов.
|
||
|
||
Ключевые настройки:
|
||
|
||
- `terrain_full_quality_enabled`
|
||
- `terrain_full_quality_radius_tiles`
|
||
- `terrain_control_splat_quality_enabled`
|
||
- `terrain_control_splat_primary`
|
||
- `terrain_control_splat_radius_tiles`
|
||
- `max_active_terrain_control_splat_tiles`
|
||
- `terrain_quality_mesh_cache_limit`
|
||
- `prewarm_tile_margin`
|
||
- `retain_tile_margin`
|
||
- `full_lod_prewarm_tiles`
|
||
- `boundary_prefetch_threshold`
|
||
|
||
Правило для High: игрок не должен видеть low-res terrain под ногами или прямо перед собой. Low quality допустим только далеко или вне видимости.
|
||
|
||
### Texture Seams
|
||
|
||
Была проблема видимых стыков и неправильного повторения texture pattern между чанками. Исправление в shader control splat:
|
||
|
||
- terrain texture sample теперь использует continuous `chunk_uv`, а не `local_uv`;
|
||
- alpha atlas sample clamp'ится внутри 64x64 chunk alpha cell;
|
||
- это уменьшает видимые seams на границах MCNK.
|
||
|
||
Файл: `addons/mpq_extractor/loaders/adt_builder.gd`, `_get_control_splat_shader()`.
|
||
|
||
## ADT Baking
|
||
|
||
Были ускорены bake scripts. Главный рабочий bake для качества terrain:
|
||
|
||
```text
|
||
src/tools/build_adt_control_splat_cache.gd
|
||
```
|
||
|
||
Что важно:
|
||
|
||
- параллельные workers;
|
||
- cache resource на ADT tile;
|
||
- не пересобирать в runtime;
|
||
- incomplete tile считается ошибкой worker и выводится warning;
|
||
- cache лежит в `data/cache` и не должен попадать в git.
|
||
|
||
Пример успешного результата:
|
||
|
||
```text
|
||
Starting parallel ADT control splat cache: jobs=15
|
||
Parallel control splat cache finished. jobs=15 failed_workers=0 elapsed=...
|
||
```
|
||
|
||
Если один worker падает:
|
||
|
||
```text
|
||
WARNING: Control splat produced incomplete tile: ...
|
||
failed_workers=1
|
||
```
|
||
|
||
Такой tile надо перезапечь отдельно или проверить исходный ADT.
|
||
|
||
## M2 Rendering
|
||
|
||
Цель: тысячи props не должны быть тысячами независимых Node3D/MeshInstance3D.
|
||
|
||
Текущий подход:
|
||
|
||
- ADT `MDDF` placements группируются по M2 path;
|
||
- для каждого M2 path создается один или несколько `MultiMeshInstance3D`;
|
||
- transforms пишутся батчами через `m2_multimesh_batch_size`;
|
||
- mesh/resource finalization ограничен `m2_mesh_finalize_ops_per_tick`;
|
||
- build групп ограничен `m2_build_groups_per_tick`.
|
||
|
||
Это убрало большие `m2build` фризы, где раньше один frame мог тратить 20-75ms только на M2.
|
||
|
||
Кеш:
|
||
|
||
```text
|
||
res://data/cache/m2_glb
|
||
```
|
||
|
||
Скрипты:
|
||
|
||
```text
|
||
src/tools/bake_m2_cache.gd
|
||
src/tools/collect_world_m2_list.gd
|
||
src/tools/m2_to_gltf.py
|
||
```
|
||
|
||
Что улучшали:
|
||
|
||
- M2 parser читает дополнительные material/texture/batch данные;
|
||
- BLP alpha fixes улучшили прозрачность листвы/декора;
|
||
- `M2Builder` улучшен для texture/material setup;
|
||
- runtime M2 строится через MultiMesh, а не как дерево отдельных props.
|
||
|
||
Ограничения:
|
||
|
||
- M2 animation/skinning пока не полноценные;
|
||
- particles/ribbons не реализованы;
|
||
- WoW material flags не полностью совпадают;
|
||
- billboards/leaf bones пока не как в клиенте.
|
||
|
||
## WMO Rendering
|
||
|
||
Цель: WMO не должны фризить сцену при догрузке, особенно большие здания/города.
|
||
|
||
Что сделано:
|
||
|
||
- `WMOLoader` читает root/group WMO данные и MLIQ;
|
||
- `WMOBuilder` строит group meshes;
|
||
- runtime registry дедуплицирует WMO по `unique_id`;
|
||
- WMO на границе ADT не дублируются;
|
||
- большие WMO не должны инстанцироваться как тяжелые `.tscn` во время движения;
|
||
- добавлен lightweight render-cache path для WMO groups;
|
||
- `wmo_render_group_ops_per_tick` ограничивает подключение WMO groups по кадрам;
|
||
- `wmo_max_runtime_scene_mb` защищает от тяжелого runtime `.tscn` instantiate.
|
||
|
||
Кеши:
|
||
|
||
```text
|
||
res://data/cache/wmo_tscn
|
||
res://data/cache/wmo_render_v1
|
||
```
|
||
|
||
Скрипты:
|
||
|
||
```text
|
||
src/tools/bake_wmo_cache.gd
|
||
src/tools/build_wmo_streaming_cache.gd
|
||
```
|
||
|
||
Что улучшали по качеству:
|
||
|
||
- включен texture repeat для WMO materials;
|
||
- улучшены BLP alpha/texture decode проблемы;
|
||
- WMO liquid mesh получает нормали/UV;
|
||
- WMO liquids используют общий `WowLiquidMaterial`.
|
||
|
||
Ограничения:
|
||
|
||
- portal/room based visibility WMO пока не повторяет клиент;
|
||
- occluders не являются полноценной заменой WoW portal culling;
|
||
- часть WMO material flags еще требует сверки с клиентом.
|
||
|
||
## Liquids
|
||
|
||
Open-realm не дал готовую реализацию water shader: в его docs liquid rendering отмечен как future pass. Поэтому добавлен собственный легкий Godot shader.
|
||
|
||
Файл:
|
||
|
||
```text
|
||
addons/mpq_extractor/loaders/wow_liquid_material.gd
|
||
```
|
||
|
||
Подключение:
|
||
|
||
- ADT `MH2O` через `ADTBuilder.build_tile_water_scene`;
|
||
- WMO `MLIQ` через `WMOBuilder._build_group_liquid`.
|
||
- Для cached terrain tiles (`streaming_tile`/`baked_tile`) вода не хранится в terrain cache: `StreamingWorldLoader` запускает отдельную background ADT load task только для `MH2O`, затем на main thread прикрепляет `Water` node к уже созданному tile root.
|
||
|
||
Что делает shader:
|
||
|
||
- один общий `ShaderMaterial` cache для ADT/WMO;
|
||
- world-space animated ripples;
|
||
- fresnel highlight;
|
||
- отдельные цвета/параметры для water, magma, slime;
|
||
- без screen texture/refraction, чтобы не раздувать GPU cost;
|
||
- shadows у liquid mesh выключены.
|
||
|
||
Почему так:
|
||
|
||
- дешево;
|
||
- не грузит дополнительные textures;
|
||
- не создает D3D12 descriptor pressure;
|
||
- дает рабочую визуальную воду уже сейчас.
|
||
|
||
Ограничения:
|
||
|
||
- нет настоящего depth fade;
|
||
- нет shoreline foam/blending;
|
||
- LiquidType.dbc texture selection пока не используется;
|
||
- waterfalls/flowing water требуют отдельного M2/material path.
|
||
|
||
## Sky And Lighting
|
||
|
||
Добавлен `WowSkyController`:
|
||
|
||
```text
|
||
src/scenes/sky/wow_sky_controller.gd
|
||
```
|
||
|
||
Что он делает:
|
||
|
||
- читает DBC данные для light profiles;
|
||
- определяет текущую area/zone по позиции;
|
||
- выбирает lighting/fog profile;
|
||
- логирует `SKY_LIGHT`;
|
||
- поддерживает LightSkybox model loading;
|
||
- двигает skybox model за камерой/player.
|
||
|
||
Пример лога:
|
||
|
||
```text
|
||
SKY_LIGHT time=13.00 map=0 area=279:... zone=36:... profiles=[...] skybox=0 fog=614..31556 density=0.500
|
||
SKYBOX_MODEL id=... path=...
|
||
```
|
||
|
||
Ограничения:
|
||
|
||
- зона/skybox transitions еще требуют ручной сверки с клиентом;
|
||
- не все outdoor skybox models используются на всех локациях;
|
||
- lighting пока приближение, а не 1:1 с fixed-function клиентом.
|
||
|
||
## Shadows
|
||
|
||
Цель по производительности сейчас важнее полного совпадения dynamic shadows.
|
||
|
||
Текущее правило:
|
||
|
||
- terrain shadows выключены;
|
||
- M2 shadows выключены;
|
||
- WMO shadows выключены;
|
||
- liquid shadows выключены.
|
||
|
||
Настройки:
|
||
|
||
```text
|
||
terrain_cast_shadows
|
||
m2_cast_shadows
|
||
wmo_cast_shadows
|
||
```
|
||
|
||
Почему:
|
||
|
||
- old WoW 3.3.5a использовал очень дешевую модель теней;
|
||
- полноценные Godot realtime shadows на большом мире быстро создают лишний GPU/CPU cost;
|
||
- сначала нужна стабильная streaming performance, затем можно добавлять selective blob/projected shadows.
|
||
|
||
Для будущего: если брать идеи Cataclysm, делать не full dynamic shadows everywhere, а ограниченные character/near-object shadows с жестким distance budget.
|
||
|
||
## Third Person Controller
|
||
|
||
Добавлен тестовый контроллер для оценки рендера "с земли":
|
||
|
||
```text
|
||
src/scenes/player/third_person_wow_controller.gd
|
||
```
|
||
|
||
Зачем:
|
||
|
||
- свободная камера плохо показывает реальные проблемы игрока;
|
||
- нужен WoW-like темп перемещения;
|
||
- sprint нужен для стресс-теста ADT boundary streaming.
|
||
|
||
Модель сейчас не важна, используется простая capsule.
|
||
|
||
## Texture/BLP Work
|
||
|
||
Что сделано:
|
||
|
||
- BLP loader улучшен для alpha handling;
|
||
- texture mipmaps генерируются для runtime ImageTexture там, где texture создается из decoded BLP;
|
||
- для WMO/M2 материалов включается anisotropic/mipmap filtering там, где texture подключается;
|
||
- включен repeat для WMO материалов.
|
||
|
||
Важная проблема Godot/D3D12:
|
||
|
||
```text
|
||
Cannot create uniform set because there's not enough room in the RESOURCES descriptor heap.
|
||
Please increase rendering/rendering_device/d3d12/max_resource_descriptors.
|
||
```
|
||
|
||
Это возникает, когда renderer создает слишком много уникальных texture/uniform resources. Наши меры:
|
||
|
||
- кешировать materials/textures;
|
||
- не создавать уникальный heavy shader/material на каждый chunk;
|
||
- использовать `Texture2DArray` для terrain control splat;
|
||
- ограничивать активные high-quality terrain tiles;
|
||
- избегать screen/refraction textures для water.
|
||
|
||
Если ошибка возвращается, увеличивать project setting `rendering/rendering_device/d3d12/max_resource_descriptors` и одновременно искать, где плодятся уникальные материалы.
|
||
|
||
## Caches
|
||
|
||
Активные cache directories:
|
||
|
||
```text
|
||
res://data/cache/baked_terrain_v2
|
||
res://data/cache/baked_terrain_stream_v1
|
||
res://data/cache/terrain_control_splat_v3
|
||
res://data/cache/terrain_splat_v1
|
||
res://data/cache/m2_glb
|
||
res://data/cache/wmo_tscn
|
||
res://data/cache/wmo_render_v1
|
||
```
|
||
|
||
Правила:
|
||
|
||
- cache не коммитить;
|
||
- при изменении resource format bump'ать `FORMAT_VERSION`;
|
||
- при изменении shader/material без изменения cached resource иногда достаточно restart scene;
|
||
- при изменении baked geometry/resource payload нужен rebake;
|
||
- WMO render cache нужен для больших WMO, иначе будут `wmobuild` фризы.
|
||
|
||
Версии:
|
||
|
||
```text
|
||
Baked terrain required: 4
|
||
Splat terrain required: 1
|
||
Control splat required: 3
|
||
WMO streaming resource: 1
|
||
```
|
||
|
||
## Debug Logs
|
||
|
||
Важные логи:
|
||
|
||
```text
|
||
HITCH ...
|
||
PERF ...
|
||
TERRAIN_QUALITY ...
|
||
SKY_LIGHT ...
|
||
SKYBOX_MODEL ...
|
||
```
|
||
|
||
Как читать `HITCH`:
|
||
|
||
- если большой `terrainup` - full-quality terrain подключается слишком тяжело;
|
||
- если большой `terrainctl` - control splat cache финализация слишком дорогая;
|
||
- если большой `terrainsplat` - runtime splat path не должен быть включен для обычной игры;
|
||
- если большой `m2mesh` - mesh resource finalization надо сильнее дробить;
|
||
- если большой `m2build` - уменьшить `m2_build_groups_per_tick` или `m2_multimesh_batch_size`;
|
||
- если большой `wmobuild` - использовать render cache, уменьшить `wmo_build_instances_per_tick`;
|
||
- если большой `wmogroups` - уменьшить `wmo_render_group_ops_per_tick`.
|
||
|
||
## Reference Findings
|
||
|
||
По open-realm:
|
||
|
||
- полезен как документация форматов/пайплайна;
|
||
- terrain/ADT/WMO/M2 идеи можно сверять;
|
||
- полноценный liquid rendering там не реализован;
|
||
- skybox/liquid не стоит напрямую переносить как готовый код.
|
||
|
||
По WoW 3.3.5a:
|
||
|
||
- старый клиент не рендерил все как modern physically based renderer;
|
||
- он агрессивно экономил CPU;
|
||
- основная цель для нас - frame pacing и отсутствие hitch, а не дорогие эффекты.
|
||
|
||
## Commands
|
||
|
||
Headless smoke:
|
||
|
||
```powershell
|
||
$exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
||
& $exe --headless --path . --quit
|
||
```
|
||
|
||
Run scene headless:
|
||
|
||
```powershell
|
||
$exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
||
& $exe --headless --path . src/scenes/streaming/eastern_kingdoms_streaming.tscn --quit
|
||
```
|
||
|
||
Build native GDExtension:
|
||
|
||
```powershell
|
||
cd src\native
|
||
.\build.bat Release
|
||
```
|
||
|
||
Build ADT control splat cache:
|
||
|
||
```powershell
|
||
$exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
||
& $exe --headless --path . --script res://src/tools/build_adt_control_splat_cache.gd -- --map Azeroth --jobs auto --texture-size 256 --lod 0 --force
|
||
```
|
||
|
||
Build WMO render cache:
|
||
|
||
```powershell
|
||
$exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
||
& $exe --headless --path . --script res://src/tools/build_wmo_streaming_cache.gd -- --map Azeroth
|
||
```
|
||
|
||
## Current Known Gaps
|
||
|
||
- M2 animation/skinning.
|
||
- M2 particles/ribbons.
|
||
- Full WoW material flag parity.
|
||
- True WMO portal/room culling.
|
||
- Selective cheap dynamic shadows.
|
||
- Liquid depth fade/shore blending.
|
||
- LiquidType.dbc based water textures.
|
||
- Better skybox transition validation per zone.
|
||
- Automated renderer regression scenes/screenshots.
|
||
|
||
## Practical Rule For Future Work
|
||
|
||
If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it.
|
||
|
||
High quality must appear before the player arrives, not after crossing the ADT boundary.
|