# 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//__.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 пока не как в клиенте. ### M2 Animation Research Проверка по `reference/whoa`, `reference/open-realm` и `reference/wow.export` показала, что анимации M2 нельзя надежно тащить как обычный Godot/glTF skeletal mesh. Оригинальная схема M2: - для каждой кости считается матрица через pivot transform: translation/rotation/scale вокруг `bone.pivot`; - parent chain считается вручную; - каждый skin section использует свой bone palette через `bone_combo_index`; - vertex bone indices интерпретируются относительно palette текущего skin section. Именно так делают: - `reference/whoa/src/model/CM2Model.cpp` - расчет `m_boneMatrices`; - `reference/whoa/src/model/CM2SceneRender.cpp` - upload bone palette из `boneCombos`; - `reference/open-realm/games/world-of-warcraft/renderer/m2/r_m2.c` - `M2_CalculateBoneMatrices` и `M2_UploadBatchBones`; - `reference/wow.export/src/js/3D/renderers/M2RendererGL.js` - ручной pivot-based runtime skinning. Вывод: старый экспериментальный путь через cached GLB ломал композитные doodad M2, потому что анимировал node с bind-pivot translation напрямую. Конвертер переведен на схему `pivot node -> animated bone node`, аналогичную wow.export `modelsExportWithBonePrefix`. Runtime принимает для M2-анимаций только GLB с меткой: ```text asset.extras.openwc_m2_anim_schema = pivot_prefix_v1 ``` Это защищает мир от старых битых GLB cache. Точечно пересобраны тестовые animated M2: ```text data/cache/m2_glb/GryphonRoost01.glb data/cache/m2_glb/Fish.glb data/cache/m2_glb/eagle.glb ``` Ограничение runtime: ```text enable_m2_animated_instances = true m2_animated_max_primitives = 3 m2_animated_denylist_patterns = ["gryphonroost"] m2_animated_allowlist_patterns = ["creature/fish/", "creature/eagle/", "world/critter/"] ``` `GryphonRoost` stays out of the animated GLB path. The cached GLB has `Stand` tracks, but the current converter/runtime breaks its composite skin/material layout. Use the native M2 runtime path for this class of doodad; the old GLB animation path remains disabled for `gryphonroost`. Native M2 animation first pass for composite doodads: - `M2Loader.load_m2_animated()` parses M2 bones, Stand track keys, `.skin` bone lookup/palette data, and expanded skinned surfaces. - `M2NativeAnimatedBuilder` builds a regular `ArrayMesh` and keeps the original per-surface skin data. - `M2NativeAnimator` evaluates the selected Stand sequence and applies WoW-style bone matrices: `T(pivot + translation) * R * S * T(-pivot) * parent`. - Vertex influences are resolved through `.skin` local bone indices and the M2 `boneCombos` palette, matching the original section/batch renderer model used by WoW/whoa. - `StreamingWorldLoader` routes only `gryphonroost` through this native path; simple critters still use the existing GLB allowlist and most M2 world props stay on static MultiMesh. - Current implementation rebuilds the animated `ArrayMesh` on CPU. This is correct enough for the problematic rare doodads and gives us the same data layout that can later move to shader/GPU skinning. Причина: композитные doodad вроде `GryphonRoost` снова ломают визуал при GLB-анимации. До M2-native renderer все world placement M2 должны оставаться статическими. GLB-анимация оставлена только как вручную включаемый debug experiment через allowlist. Дополнительно static M2 cache lookup ищет both original-case and lowercase paths. Это важно для ADT paths вроде `world/GENERIC/...`, потому что запеченный nested cache лежит lowercase. Static path также не использует GLB с `pivot_prefix_v1`, чтобы animated test cache не мог подменить корректный `.tscn`. Полноценный M2-native animation renderer/cache все еще остается правильным долгосрочным решением для 1:1 поведения, particles/ribbons и производительности на большом количестве animated props. ### Blizzlike Shader Research Идея для следующего visual fidelity слоя: уходить от Godot `StandardMaterial3D` для WoW assets к собственным WoW-style `ShaderMaterial`. Что видно по whoa: - M2 использует маленькие shader permutations вроде `Diffuse_T1`, `Diffuse_T2`, `Diffuse_Env`, `Combiners_Mod`, `Combiners_Add`, `Combiners_Mod2x`; - material state задается отдельно: blend mode, alpha ref, culling, depth test/write; - light model старый и ручной: ambient + diffuse + emissive + fog constants, а не PBR; - texture stages учитывают wrap/clamp, texture transform matrices и env/sphere map. Практический вывод для проекта: - terrain уже ближе к WoW, потому что использует `ShaderMaterial` и ручной light/fog; - M2/WMO пока используют `StandardMaterial3D`, поэтому выглядят не совсем blizzlike; - следующий шаг - `wow_m2_material` / `wow_wmo_material` с WoW blend modes, alpha ref, combiners и ручным fog/light. ### M2 ShaderMaterial First Pass Implemented file: `addons/mpq_extractor/loaders/wow_m2_material.gd`. `M2Builder._build_material()` now returns a WoW-style `ShaderMaterial` instead of `StandardMaterial3D`. The same path is reused by `M2NativeAnimatedBuilder`, so static M2 batches and the native animated `gryphonroost` path share material behavior. Current M2 shader layer: - maps M2 blend modes 0..6 to Godot shader variants: opaque, alpha-key, alpha blend, additive, mod and mod2x approximation; - uses alpha reference values observed in `reference/whoa/src/model/CM2SceneRender.cpp`: 0 for opaque, about 0.878 for alpha-key, 1/255 for blended passes; - applies M2 render flags for unlit (`0x01`), two-sided/culling (`0x04`), depth test (`0x08`) and depth write (`0x10`); - keeps BLP mipmap generation and anisotropic sampler hints; - respects M2 texture wrap flags by compiling repeat/clamp shader variants; - `M2Loader` exposes UV2, `texture_coord_combos`, `texture_combiner_combos`, `m2_flags`, batch `shader_id`, `texture_count` and texture-coordinate combo indices; - `M2Loader` also exposes `m2_colors`, `texture_weights`, `texture_transforms`, `texture_weight_combos` and `texture_transform_combos`; - `M2Builder` now resolves the first two texture stages from texture combos and passes combiner state into `WowM2Material`; - `WowM2Material` has a first-pass implementation for combiner ops: opaque, mod, decal/fade, add, mod2x, mod2xNA/addNA and a cheap env-map UV approximation; - static M2 color/alpha tracks and texture weight tracks are applied to `mesh_color`, matching the `whoa` render path where batch alpha is multiplied by color alpha and texture weight; - static texture transform translation/scale is applied per texture stage through shader UV transform uniforms; - animated texture transform translation tracks are approximated as GPU UV scroll (`translation_speed * TIME`) per texture stage, without per-frame CPU material updates; - native animated M2 rebuild path preserves UV2 and the same material combiner state. - uses a simple old-client-style ambient + diffuse light model and disables PBR/specular behavior. Known limits: - only the first two texture stages are represented; - texture transform translation animation is supported as linear UV scroll; rotation/scale texture animation and time-varying color/weight tracks are still future work; - exact env/sphere map math, model emissive tracks and full WoW render-state parity are still future work; - WMO has a matching first-pass shader layer, but still needs full material flag parity. ### WMO ShaderMaterial First Pass Implemented file: `addons/mpq_extractor/loaders/wow_wmo_material.gd`. `WMOBuilder._build_material()` now returns a WoW-style `ShaderMaterial` instead of `StandardMaterial3D`. WMO group meshes already carry `Mesh.ARRAY_COLOR` from group vertex colors, so the shader treats WMO as mostly baked-lit geometry: - blend modes 0..2 map to opaque, alpha-key and alpha blend variants; - alpha-key uses discard instead of transparent sorting; - texture repeat stays enabled for WMO wall/floor tiling; - BLP mipmap generation and anisotropic sampler hints are preserved; - shader is `unshaded` and multiplies texture color by WMO vertex color with a small ambient floor; - DBC zone light is applied as a soft tint only, so baked WMO vertex colors and texture albedo stay readable; - `color2` is passed as a secondary/detail tint, and `texture2` is used as a fallback detail texture when `texture1` is absent; - `WMOLoader` exposes MOMT `diffuse_color`, `emissive_color`, `texture1`, `texture2`, `flags2` and `color2`; - `WMOBuilder` loads `texture1` as a conservative secondary detail/multiply layer and passes MOMT diffuse/emissive colors into the shader; - culling is disabled like the previous WMO material path, which avoids missing interior/backface geometry. Known limits: - deeper MOMT shader semantics, exact secondary texture mode and `texture2` usage are still approximate; - WMO fog/light constants are not wired into this material yet; - this first pass prioritizes matching old-client baked lighting behavior over physical lighting. ## 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. ## Shared WoW Shader Globals Current terrain/M2/WMO/liquid shaders are intentionally `unshaded`: Godot lighting is not a good match for old WoW material rules, M2 blend modes, WMO baked vertex colors and control-splat terrain. To keep them visually tied to the active zone without updating every material instance, `WowSkyController` owns a shared global shader state: ```text wow_ambient_color wow_light_color wow_light_dir wow_fog_color wow_fog_range wow_fog_density wow_sun_elevation ``` These parameters are registered through `RenderingServer.global_shader_parameter_add` and updated from `Light.dbc` / `LightParams.dbc` sampling in `src/scenes/sky/wow_sky_controller.gd`. Consumers: - `addons/mpq_extractor/loaders/adt_builder.gd`: control-splat, fallback layered terrain and single-texture terrain. - `addons/mpq_extractor/loaders/wow_m2_material.gd`: M2 combiner material uses zone ambient/diffuse light and distance fog. - `addons/mpq_extractor/loaders/wow_wmo_material.gd`: WMO material keeps baked vertex lighting, but tints it by zone light and applies distance fog. - `addons/mpq_extractor/loaders/wow_liquid_material.gd`: liquids use the shared fog color/range/density. The fog distance is computed from view-space position passed from `vertex()` to `fragment()`. This avoids per-camera material updates and keeps shader code compatible with Godot 4.6's spatial shader parser. ## 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: 2 ``` ## 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. Current first pass covers common M2 combiners and WMO shader ids 0, 3, 5, 6, 8, 9, 12, 13, 15, 16, 17 approximately. - 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. ## 2026-07-04 WMO Shader Pass - WMO materials now consume shared `wow_light_dir` and `wow_sun_elevation` shader globals. - Cached WMO meshes are refreshed at attach time, so already baked render-cache meshes can receive the latest WMO material shader without immediately rebuilding the whole WMO cache. - The WMO shader now uses normals for a cheap WoW-style ambient + directional lighting term instead of relying only on texture * vertex color. - Added approximate pixel shader branches for common WMO modes: diffuse, env, env metal, two-layer diffuse, terrain-style two-layer, diffuse emissive and mod2x variants. - Still missing for true parity: second/third WMO vertex color streams, extra WMO texture coordinate sets, third+ texture samplers, exact env-map coordinate generation and portal/room visibility. ## 2026-07-04 M2 Shader Mapping Pass - M2 material build now maps raw skin `shaderID` through the same vertex/pixel shader selection rules used by `wow.export`. - `WowM2Material` receives `vertex_shader_id` and `pixel_shader_id` uniforms instead of relying only on generic two-stage ops. - Vertex shader ids 0..18 now drive UV source selection, UV2 use, environment coordinates and edge-fade modes. - Pixel shader ids 0..29 have first-pass two-texture combiner behavior for the common opaque/mod/add/mod2x/alpha cases. - M2 materials now load and bind up to four texture combo entries. This enables first-pass support for glow/specular/crossfade/guild/const combiner cases that need `tex3` or `tex4`. - Texture weight combos are exposed to the shader as `tex_sample_alpha`, matching the common WoW combiner weight slots. - Native animated M2 surfaces use the same `_build_material()` path, so this also applies to runtime-animated M2 objects. - Debug metadata is stored on materials: raw `wow_m2_shader_id`, mapped `wow_m2_vertex_shader_id`, and mapped `wow_m2_pixel_shader_id`. - Transparent M2 passes such as waterfalls now avoid depth writes/prepass (`depth_draw_never`). This prevents layered alpha planes from self-occluding into white/solid webbing while keeping cutout foliage on the opaque-depth path. - Scrolling alpha-blend M2 models with `waterfall` in the path get a conservative soft-alpha profile: lower opacity, stronger alpha falloff and UV-edge fade. This hides the rectangular card edges without changing normal foliage/cutout props. - `.skin` texture unit `priority` is exposed as `priority_plane` for future transparent-pass ordering. This needs the native GDExtension DLL to be rebuilt when the editor is not holding it open. - Waterfalls seen near terrain cliffs are usually WMO embedded doodads rather than ADT `MDDF` placements. WMO doodad transforms now use the converted MODD quaternion directly, without the old extra `+90 deg` M2 yaw offset. - WMO render/scene caches now carry version guards for this transform path. Rebuild `wmo_render_v1`/`wmo_tscn` after changing WMO doodad placement code; old caches are ignored by runtime. - Elwynn/Thunder Falls style outdoor waterfalls are ADT `MDDF` M2 placements. Regular ADT M2 doodads use the native cached Godot-space Euler directly via `Basis.from_euler(rot)`; do not apply a global yaw/order remap, because it flips ordinary props such as wagons, stalls, barrels, fences and trees. `ElwynnCliffRock01/02` are a model-specific exception: these are open-backed shell meshes, so they use a narrow `YZX` placement correction with `rot.y + 180 deg` to push the hollow side into the terrain. The waterfall transform correction is also filename-allowlisted to the actual sheet M2s (`NewWaterfall`, `ElwynnTallWaterfall01`). Sheet M2s keep the calibrated world-space `+90 deg` Godot-Y yaw to anchor the top to the upper pool, plus model-specific twist where needed. Tall waterfall sheets twist around their local fall line with an origin offset so top/bottom anchors stay fixed instead of rotating around the model pivot; `ElwynnTallWaterfall01` uses the opposite twist sign from `NewWaterfall` to face the correct side. Do not apply the raw-M2/WoWee render-space basis here; that double-converts M2 vertices. Do not cull boundary waterfall placements by `floor(world_pos / TILE_SIZE)`: tiles can carry a small cross-border placement that is still the active streamed instance at low M2 radius. Future cross-tile dedupe should use `MDDF.uniqueId` plus an ADT cache format bump/rebake. - Still missing for true parity: exact shader array entries beyond the common WotLK set, particles/ribbons, full edge-scan tuning, exact guild/color constants and specialized blend-state edge cases. ## 2026-07-07 World Shader Light Stabilization - DBC sky colors remain the source for `Environment` and sky rendering, but material shader globals now receive desaturated/neutralized ambient, diffuse and fog colors. This prevents Elwynn daylight profiles from tinting all ADT/M2/WMO albedo cyan or green. - ADT terrain shaders now transform normals with `MODEL_NORMAL_MATRIX` before comparing them against the world-space `wow_light_dir`. Using fragment `NORMAL` directly made terrain lighting depend on the local surface basis and produced blue/green patches on slopes and roads. - ADT, M2 and WMO material fog clamps `wow_fog_density` before mixing into `ALBEDO`; fog should add distance haze, not repaint nearby assets. - M2/WMO shaders now reduce zone-color strength and clamp final albedo/specular/emissive contribution. Additive/parity edge cases still need exact WoW pass ordering later, but ordinary Elwynn props should no longer produce overexposed or acid-green surfaces. - Human building WMO materials such as `HumanTwoStory` and `GoldshireInn` carry strong painted `MOMT.diffColor` values, including green trim colors. Textured WMO surfaces now use this as a soft hue tint instead of multiplying texture albedo by the raw RGB value; untextured WMO surfaces still use a muted material paint color. - WMO window materials such as `MM_ELWYNN_WND_EXT__01.BLP` carry high `MOMT.sidnColor`/emissive values. Emissive is texture-masked and capped for window paths so it brightens the pane pattern instead of flooding the whole quad white. - Runtime WMO cache material refresh is versioned separately from WMO geometry cache. Shader-only WMO material fixes should bump `WMO_MATERIAL_REFRESH_VERSION` instead of forcing a full `wmo_render_v1` rebake. ## 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.