Files
fparkan/docs/reference/render-frame.md
T
Valentin Popov d97c8d40bf
Docs Deploy / Build and Deploy MkDocs (push) Failing after 2m47s
Match free-flight camera to native projection
2026-10-18 06:09:48 +04:00

16 KiB
Raw Blame History

Кадр отрисовки

Кадр завершает цикл и не является отдельной операцией рендерера. До вызовов отрисовки собирается ввод, вычисляется такт, применяются отложенные операции, выбирается камера и обновляется слушатель 3D-звука.

Порядок кадра

системные сообщения и ввод
  -> расчёт симуляции
  -> отложенные операции над объектами
  -> анимация и преобразования
  -> камера и звуковой слушатель
  -> видимость и очереди отрисовки
  -> материалы и проходы отрисовки
  -> завершение рендерера
  -> обратные вызовы после отрисовки и UI

По анализу вызовов установлен общий порядок в World3D::stdRenderGame: камера передаётся в Terrain, задаются viewport и матрицы, вызываются граничные слоты рендерера, выставляется in_render, обходится мир, завершаются проходы мира и затенения, вызывается завершение рендерера, снимается in_render, затем рассылается end-of-render.

Элемент отрисовки

Подготовленный элемент отрисовки содержит:

  • мировую матрицу узла;
  • флаги пакета batch и диапазон индексов;
  • дескриптор материала WEAR;
  • активную фазу и коэффициенты MAT0;
  • дескриптор текстуры;
  • необязательный дескриптор карты освещения;
  • фазу отрисовки и ключ сортировки;
  • состояние конвейера устаревшей отрисовки.

Подготовленный элемент должен ссылаться на неизменяемые данные кадра. Изменение фазы или кэша текстур посреди прохода не должно менять уже собранную очередь.

Состояние MAT0 и прозрачные фоны

Для диапазонов мира миссии выбранный материал MAT0 задаёт нативную категорию конвейера. Категория определяет смешивание и альфа-тест, сохраняя настройки проверки глубины и отсечения граней. Если селектор материала анимируется, вместе с ним обновляется и категория. Принадлежность к очереди определяется отдельно: прозрачным считается диапазон мира с выбранной прозрачностью меньше единицы или с нативными флагами пакета batch 0x108. Такой диапазон при включённой проверке глубины не записывает глубину. За фиксированными диапазонами окружения сохраняются их явные состояния.

В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на своих местах; прозрачная очередь мира сортируется от дальних элементов к ближним и вставляется сразу после последнего помеченного фона. Порядок остальных фиксированных диапазонов сохраняется. Поэтому облака могут отрисовываться перед прозрачной листвой, а расположенные позднее солнце, погода и другие передние эффекты сохраняют свой относительный порядок. Это локальное правило очереди, а не перестановка всех проходов неба или эффектов.

Проверки

cargo test --locked --offline -p fparkan-render-vulkan --lib material_category_and_transparency_preserve_world_state_in_either_order Проверяет, что категория 1 сохраняет смешивание source-alpha и альфа-тест, сохраняет настройки проверки глубины и отсечения граней при любом порядке обновления и отклоняет неподдерживаемые категории. cargo test --locked --offline -p fparkan-render-vulkan --lib tree_canopy_queue_follows_sky_and_precedes_foreground_effects Проверяет заданный порядок очереди: градиент экрана, непрозрачная местность и здание, тень, туманность, купол и облака, прозрачная крона от дальних элементов к ближним, затем солнце и погода.

Свидетельства диспетчеризации камеры GOG

Для версии GOG World3D.dll с SHA-256 17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e, экспорт stdRenderGame находится по RVA 0x13BD0. Вначале он вызывает Terrain::stdSetCurrentCamera2(camera), сохраняет указатель камеры на время кадра и очищает его перед возвратом. sendEndOfRender — отдельный экспорт по RVA 0x13D90. Эти данные подтверждают только порядок вызовов в кадре: анализ не восстанавливает ABI камеры, матрицы или значения viewport. Реализованный путь превью принимает входные данные с матрицей selector0_words, полученные через процедуру захвата нативного кадра.

Процедура захвата записывает наблюдавшийся путь миссии и значение atmosphere_seconds, если проверка потока, поколения и часов прошла успешно. В превью с фиксированной камерой эта абсолютная фаза расписания используется, если не задан параметр --atmosphere-seconds; на всех кадрах чтения сохраняется та же фаза. Явный параметр командной строки имеет приоритет. Эта фаза не равна времени полной симуляции: погода, RNG и другие состояния миссии остаются неизвестны, поэтому она не подтверждает полного совпадения кадра.

Анализ принимающей стороны уточняет этот контракт. stdSetCurrentCamera2 (RVA 0x4FD40 в Terrain с SHA-256 af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead) запрашивает селектор 18 у переданного объекта и вызывает слот +12 полученного интерфейса. Указатель не сохраняется в stdGetCurrentCamera2: этот геттер возвращает глобальное значение Terrain, установленное селектором 8 при инициализации Terrain.

Terrain.dll!LoadCamera задаёт отдельный контракт фабрики. Точка входа GOG по RVA 0x4EBE0 использует соглашение о вызовах __stdcall и принимает четыре машинных слова, выделяет 0x1A4 байта, вызывает конструктор по RVA 0x4EC60 и возвращает интерфейс по смещению объекта +0x134. Конструктор передаёт аргументы 3 и 4 в инициализацию базового объекта по this + 4, записывает аргумент 3 в this + 0x138 и вызывает вспомогательную функцию по RVA 0x4CEF0, передавая ей аргументы 1, 2 и 4 вместе с this. Аргумент 1 — строка режима: ненулевое значение по this + 0x1A0 дают только REFLECTION и REFLECTION_SHIFTED. Это свидетельство ABI фабрики, а не восстановленный контракт матрицы вида или проекции.

Предпросмотр по захвату камеры D3D7

fparkan-game --legacy-camera-capture <json> принимает матрицу камеры selector0_words, прямоугольник области отображения viewport D3D7 [left, top, right, bottom], ближнюю и дальнюю плоскости и угол поля зрения. По этим данным превью строит матрицы вида и проекции D3D7, передаёт полученную матрицу clip_from_world камере превью и напрямую использует оси камеры из матрицы вида. Поэтому базис захвата, включая крен, не сводится к рысканию и тангажу. Прямоугольник задаёт физический размер области отрисовки: right - left на bottom - top. Размер окна превью закреплён: если при запуске или после ненулевого изменения окна физический размер не совпадает с заданным, приложение завершает работу, а не растягивает изображение. Сравнивайте результат с фрагментом исходного PNG по прямоугольнику [left, top, right, bottom]. В этом режиме FreeFlightCamera не создаётся, но при повторной отрисовке обновляются окружение и проецируемые тени для захваченной камеры. До обновления окружения, которое может создавать звуковые события, слушателю задаются захваченные позиция, направление вперёд и верхняя ось; начальная сортировка диапазонов мира использует ту же позицию.

Захват может содержать наблюдавшийся путь миссии и проверенное значение atmosphere_seconds; последнее используется только при отсутствии явного параметра командной строки. Это значение фиксирует фазу расписания атмосферы при сравнении с неподвижной камерой, тогда как FX продолжает получать реальное время кадра. simulation_time_seconds, погода, RNG и полное состояние миссии остаются неизвестными, поэтому совпадение камеры и фазы не подтверждает визуального совпадения с нативным кадром.

Для неба превью вычисляет направление как atan2(forward.x, forward.y). Оно совпадает со вторым выходным значением, вычисляемым в Terrain.dll по RVA 0x4F9C0; при выборе сектора экранного градиента CSky добавляет π по RVA 0x481C5. Это преобразование применяется только на границе неба и не меняет рыскание или управление движением FreeFlightCamera.

Камера свободного полёта

Начальный профиль использует near = 0.5, far = 700 и горизонтальный FOV 1.3 рад — параметры, наблюдавшиеся в проверенном контексте GOG Ngi32 (формула native projection). Для отношения ширины к высоте a превью вычисляет вертикальный угол

fn vertical_fov(self, aspect: f32) -> f32 {
    2.0 * ((self.horizontal_fov * 0.5).tan() / aspect).atan()
}

Код заменяет невалидный aspect на отношение 16:9; горизонтальный угол остаётся 1.3 рад после изменения размера окна, а новый viewport и матрица камеры применяются при следующей отрисовке.

Проекция направления не сравнивает косинус угла с ближней дистанцией: она отбрасывает только лучи за камерой и почти параллельные плоскости изображения (forward · direction <= 1e-4). Лучи в передней полусфере могут проецироваться за край viewport; владелец спрайта отдельно решает, нужно ли его рисовать.

Ограничения точного совпадения

VulkanStaticCamera::from_legacy_d3d7 выполняет восстановление вида и проекции D3D7 в fparkan-render, а затем один раз меняет знак вертикального члена проекции на границе адаптера Vulkan. В области отображения D3D7 положительная координата NDC Y направлена вверх экрана; Vulkan использует положительную высоту области отображения, поэтому адаптер меняет знак NDC Y, а шейдер сохраняет gl_Position. Общий путь from_row_major_view_projection не меняется: вызывающие его функции передают матрицы, уже подготовленные для Vulkan, как и камера свободного полёта.

  • точность и округление x87;
  • различия scalar/SIMD в g_FastProc;
  • порядок объектов, пакетов и прозрачных примитивов;
  • переходы состояний проверки глубины, отсечения граней, альфа-теста и смешивания;
  • пропуск mip-уровней, палитра и координаты Page;
  • запасной материал и выбор фазы;
  • последовательность RNG для FX и атмосферы;
  • переход на запасной набор возможностей устройства;
  • квантование времени симуляции.

Для отладки требуется детерминированный захват кадра: состояние камеры, идентификаторы видимых объектов, список элементов отрисовки, ключи конвейера, матрицы и хэши промежуточных буферов.