Files
open-wc/RENDER.md
T

39 KiB
Raw Blame History

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_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 на лету.

Кеш:

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_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:

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 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.

Кеш:

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 пока не как в клиенте.

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 с меткой:

asset.extras.openwc_m2_anim_schema = pivot_prefix_v1

Это защищает мир от старых битых GLB cache. Точечно пересобраны тестовые animated M2:

data/cache/m2_glb/GryphonRoost01.glb
data/cache/m2_glb/Fish.glb
data/cache/m2_glb/eagle.glb

Ограничение runtime:

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.

Кеши:

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 прикрепляет 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:

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:

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: 2

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. 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.