22 KiB
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, например:
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.
Фриз-профили пишутся строками вида:
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_tickmax_concurrent_tile_taskstile_finalize_ops_per_tickterrain_upgrade_finalize_ops_per_tickterrain_control_splat_cache_finalize_ops_per_tickm2_build_groups_per_tickm2_multimesh_batch_sizem2_mesh_finalize_ops_per_tickwmo_build_instances_per_tickwmo_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 на лету.
Кеш:
res://data/cache/terrain_control_splat_v3/<Map>/<Map>_<x>_<y>.res
Формат:
ControlSplatADTTile.FORMAT_VERSION = 3
StreamingWorldLoader.REQUIRED_CONTROL_SPLAT_TILE_FORMAT_VERSION = 3
Команда bake:
$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_enabledterrain_full_quality_radius_tilesterrain_control_splat_quality_enabledterrain_control_splat_primaryterrain_control_splat_radius_tilesmax_active_terrain_control_splat_tilesterrain_quality_mesh_cache_limitprewarm_tile_marginretain_tile_marginfull_lod_prewarm_tilesboundary_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:
src/tools/build_adt_control_splat_cache.gd
Что важно:
- параллельные workers;
- cache resource на ADT tile;
- не пересобирать в runtime;
- incomplete tile считается ошибкой worker и выводится warning;
- cache лежит в
data/cacheи не должен попадать в git.
Пример успешного результата:
Starting parallel ADT control splat cache: jobs=15
Parallel control splat cache finished. jobs=15 failed_workers=0 elapsed=...
Если один worker падает:
WARNING: Control splat produced incomplete tile: ...
failed_workers=1
Такой tile надо перезапечь отдельно или проверить исходный ADT.
M2 Rendering
Цель: тысячи props не должны быть тысячами независимых Node3D/MeshInstance3D.
Текущий подход:
- ADT
MDDFplacements группируются по 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.
Кеш:
res://data/cache/m2_glb
Скрипты:
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.tscninstantiate.
Кеши:
res://data/cache/wmo_tscn
res://data/cache/wmo_render_v1
Скрипты:
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.
Файл:
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 прикрепляетWaternode к уже созданному tile root.
Что делает shader:
- один общий
ShaderMaterialcache для 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:
src/scenes/sky/wow_sky_controller.gd
Что он делает:
- читает DBC данные для light profiles;
- определяет текущую area/zone по позиции;
- выбирает lighting/fog profile;
- логирует
SKY_LIGHT; - поддерживает LightSkybox model loading;
- двигает skybox model за камерой/player.
Пример лога:
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 выключены.
Настройки:
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
Добавлен тестовый контроллер для оценки рендера "с земли":
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:
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:
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фризы.
Версии:
Baked terrain required: 4
Splat terrain required: 1
Control splat required: 3
WMO streaming resource: 1
Debug Logs
Важные логи:
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:
$exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
& $exe --headless --path . --quit
Run scene headless:
$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:
cd src\native
.\build.bat Release
Build ADT control splat cache:
$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:
$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.