Files
fparkan/docs/specs/render.md
Valentin Popov 4ef08d0bf6 feat: add terrain-core, tma, and unitdat crates with parsing functionality
- Introduced `terrain-core` crate for loading and processing terrain mesh data.
- Added `tma` crate for parsing mission files, including footer and object records.
- Created `unitdat` crate for reading unit data files with validation of structure.
- Implemented error handling and tests for all new crates.
- Documented object registry format and rendering pipeline in specifications.
2026-02-19 16:07:01 +04:00

7.7 KiB
Raw Blame History

Render pipeline

Документ описывает полный процесс рендера кадра в движке Parkan: Iron Strategy, без привязки к внутренним адресам/именам дизассемблера.

Связанные страницы:

1. Инициализация рендера

На старте движок:

  1. Выбирает видеодрайвер (software или аппаратный).
  2. Создаёт render backend.
  3. Подключает библиотеки ресурсов:
    • Material.lib
    • Textures.lib
    • LightMap.lib
    • palettes.lib
  4. Инициализирует менеджеры:
    • material manager
    • texture/lightmap cache
    • effect manager
  5. Загружает базовые world-ресурсы (включая наборы объектов сцены).

2. Структура кадра

Кадр выполняется как последовательность:

  1. Simulation update
  2. Animation sampling
  3. Visibility / culling
  4. Material + texture resolve
  5. Mesh draw
  6. FX update + draw
  7. UI/overlay draw
  8. Present

3. Geometry path

3.1. Подготовка инстансов

Для каждого видимого объекта:

  1. Вычисляется world transform.
  2. Выбирается LOD.
  3. Для каждого узла выбирается slot через Res1.

3.2. Culling

Сначала отсекаются узлы/слоты по bounds (AABB/sphere) из Res2.

3.3. Батчи

Для каждого прошедшего slot:

  1. Берутся батчи из диапазона Res13.
  2. По materialIndex выбирается активный материал.
  3. По фазе материала выбирается текстура/lightmap.
  4. Выполняется DrawIndexedPrimitive:
    • индексный диапазон: indexStart/indexCount
    • базовая вершина: baseVertex
    • индексы читаются из Res6
    • вершины/атрибуты читаются из Res3/Res4/Res5 (+ optional streams)

4. Animation path

Для анимированных моделей:

  1. Для узла выбирается ключ через Res19 и fallback-логику.
  2. Декодируются pos + quat из Res8.
  3. При необходимости выполняется blending двух сэмплов.
  4. Узловая матрица передаётся в geometry path.

5. Material path

Material pipeline на кадре:

  1. По material handle выбирается запись MAT0.
  2. По игровому времени выбирается текущая фаза.
  3. Применяются коэффициенты фазы (цвет/альфа/параметры).
  4. Резолвятся ссылки на texture/lightmap.
  5. Невалидные ссылки обрабатываются fallback-стратегией.

Практическая цепочка привязки для большинства *.msh ассетов из *.rlb:

  1. Для модели выбирается одноимённый WEAR (<model_stem>.wea).
  2. Из WEAR берётся material-слот (по имени, legacyId не участвует в выборе).
  3. В Material.lib ищется MAT0 по имени (DEFAULT, затем индекс 0 как fallback).
  4. Из выбранной material-фазы берётся textureName.
  5. Texm ищется в Textures.lib (и/или lightmap-архиве для lightmap-ветки).

6. Texture path

При резолве текстуры:

  1. Ищется Texm entry по имени.
  2. Проверяется и декодируется заголовок.
  3. При необходимости применяется mipSkip.
  4. Для indexed-формата подключается палитра.
  5. Optional Page chunk интерпретируется как atlas-таблица.
  6. Объект текстуры кладётся/берётся из cache.

7. FX path

Эффекты выполняются параллельно mesh-рендеру:

  1. Для активных инстансов FX вычисляется runtime-коэффициент (time_mode + flags).
  2. Команды FX обновляют внутреннее состояние.
  3. Команды emit-этапа формируют примитивы/батчи эффектов.
  4. Эффекты рисуются в 3D-кадре с собственным счётчиком батчей.

8. Псевдокод кадра

void RenderFrame(Scene* scene, Camera* cam, float dt) {
    UpdateGame(scene, dt);

    for (Object* obj : scene->objects) {
        if (!obj->visible) continue;

        UpdateObjectAnimation(obj, scene->time);
        BuildObjectNodeTransforms(obj);
    }

    BeginFrame(cam);

    for (Object* obj : scene->objects) {
        if (!obj->visible) continue;
        RenderObjectMeshes(obj, cam);
    }

    UpdateAndRenderFx(scene, dt, cam);
    RenderUI(scene);
    Present();
}

9. Критичные условия для 1:1

  1. Та же политика округления/FP для анимации и FX.
  2. Та же логика fallback по материалам и текстурам.
  3. Та же очередность стадий кадра.
  4. Тот же контракт интерпретации Res1/Res2/Res13/Res6.
  5. Тот же контракт FXID командного потока.

10. Статус валидации

  • Порядок кадра и подключение Material.lib / Textures.lib / LightMap.lib подтверждены текущей runtime-валидацией проекта.
  • Детальные инварианты форматов зафиксированы в tools/msh_doc_validator.py и tools/fxid_abs100_audit.py.

11. Статус покрытия и что осталось до 100%

Закрыто:

  1. Высокоуровневый кадр: simulation -> animation -> culling -> material/texture resolve -> mesh draw -> fx -> ui -> present.
  2. Связка MSH/MAT0/WEAR/Texm/FXID в едином runtime-процессе.
  3. Форматная валидация входных данных на полном retail-корпусе.

Осталось:

  1. Полный pixel-parity контур с эталонными кадрами оригинального рендера по набору моделей/сцен.
  2. Формализация всех render-state деталей (точные blend/depth/cull/state transitions) для гарантии 1:1 в каждом draw-pass.
  3. Полный coverage-пакет по динамическим веткам (FX-heavy кадры, сложные material-режимы, lightmap-комбинации).

12. Object registry bridge (objects.rlb)

Для миссионного/юнитного рендера критично учитывать промежуточный слой прототипов:

  1. TMA/*.dat обычно дают не прямой *.msh, а ключ прототипа.
  2. Ключ резолвится через objects.rlb (реестр ссылок на реальные архивы ресурсов).
  3. Только после этого выполняется стандартный путь: MSH -> WEAR -> MAT0 -> Texm.

Детальная спецификация этого шага вынесена в отдельную страницу: