23 KiB
Кадр отрисовки
Кадр завершает цикл и не является отдельной операцией рендерера. До вызовов отрисовки собирается ввод, вычисляется такт, применяются отложенные операции, выбирается камера и обновляется слушатель 3D-звука.
Порядок кадра
системные сообщения и ввод
-> расчёт симуляции
-> отложенные операции над объектами
-> анимация и преобразования
-> камера и звуковой слушатель
-> видимость и очереди отрисовки
-> материалы и проходы отрисовки
-> завершение рендерера
-> обратные вызовы после отрисовки и UI
По анализу вызовов установлен общий порядок в World3D::stdRenderGame: камера
передаётся в Terrain, задаются viewport и матрицы, вызываются граничные слоты
рендерера, выставляется in_render, обходится мир, завершаются проходы мира и
затенения, вызывается завершение рендерера, снимается in_render, затем
рассылается end-of-render.
При retry кадра после пересоздания swapchain сохраняется прежний pending sample, включая уже вычисленные позы и состояния FX и материалов. Для нового drawable обновляются aspect, проекция и геометрия, зависящая от viewport; pose берётся из pending camera. Новые события управления камерой остаются в input controller и применяются следующим sample. Экранные billboards погоды повторно проецируются из закэшированных мировых головы и хвоста, без второго шага симуляции окружения и повторной отправки звуковых событий. Пауза нулевого drawable не добавляет время.
Pending frame хранит пакет MAT0 для мира и, при наличии GPU-окружения, отдельный пакет неба. Пакеты содержат выбранные селектор и категорию конвейера, фазовые коэффициенты материала, четыре UV-преобразования и кортеж фаз дополнительных текстур. Повторное применение пакета при retry или нескольких resize не выбирает фазу заново и не продвигает RNG. Перед отрисовкой сначала применяются материалы мира и их порядок прозрачности, затем MAT0 неба, после чего viewport-зависимый проход неба записывает свои переопределения.
Элемент отрисовки
Подготовленный элемент отрисовки содержит:
- мировую матрицу узла;
- флаги пакета
batchи диапазон индексов; - дескриптор материала WEAR;
- активную фазу и коэффициенты MAT0;
- дескриптор текстуры;
- необязательный дескриптор карты освещения;
- фазу отрисовки и ключ сортировки;
- состояние конвейера устаревшей отрисовки.
Подготовленный элемент должен ссылаться на неизменяемые данные кадра. Изменение фазы или кэша текстур посреди прохода не должно менять уже собранную очередь.
Состояние MAT0 и прозрачные фоны
Для диапазонов мира миссии выбранный материал MAT0 задаёт нативную категорию
конвейера. Категория определяет смешивание и альфа-тест, сохраняя настройки
проверки глубины и отсечения граней. Если селектор материала анимируется,
вместе с ним обновляется и категория.
Принадлежность к очереди определяется отдельно: прозрачным считается диапазон
мира с выбранной прозрачностью меньше единицы или с нативными флагами пакета
batch 0x108. Такой диапазон при включённой проверке глубины не записывает
глубину. За фиксированными диапазонами окружения сохраняются их явные состояния.
Для MSH без потока цветов type 16 диапазон, цвет которого вычисляет CPU, при
raw-флаге Batch20 0x0008 получает переопределение alpha только в одном
подтверждённом случае. В GOG Terrain.dll с SHA-256
af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead проверка
по RVA 0x1B0F0 задаёт alpha 0, когда установлен bit 0x0040 и сброшен
0x0010; поэтому для 0x0148 alpha равна нулю. Для 0x0108 и 0x0118
превью сохраняет alpha MAT0; для 0x0058 действует приоритет bit 0x0010,
также оставляя alpha MAT0. При наличии type 16 или отсутствии 0x0008
переопределения нет. Оно относится к одному draw range и не меняет общий
материал; alpha 0 временно задаёт число индексов 0, а при очистке
переопределения исходное число индексов восстанавливается.
Общий fade по расстоянию пока не реализован. Открыты точная метрика расстояния,
выбор первого CPU-принятого треугольника диапазона и эквивалентность frustum- и
culling-отбору оригинала. Поэтому состояния 0x0108 и 0x0118 оставляют alpha
MAT0, пока эти входные данные не установлены.
В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на своих местах; прозрачная очередь мира сортируется от дальних элементов к ближним и вставляется сразу после последнего помеченного фона. Порядок остальных фиксированных диапазонов сохраняется. Поэтому облака могут отрисовываться перед прозрачной листвой, а расположенные позднее солнце, погода и другие передние эффекты сохраняют свой относительный порядок. Это локальное правило очереди, а не перестановка всех проходов неба или эффектов.
Проверки
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; владелец спрайта отдельно решает, нужно ли его рисовать.
После движения свободная камера ограничивает X и Y прямоугольником
поверхностей карты, затем поднимается над найденным рельефом на заданный
зазор. Границы берутся из корня пространственного индекса TerrainWorld,
поэтому неиспользуемые вершины файла их не расширяют и повторного обхода
геометрии в каждом кадре нет:
position[axis] = position[axis].clamp(min[axis], max[axis]); // axis = 0, 1
position[2] = position[2].max(ground_height + ground_clearance);
Для каждого отрисовываемого корневого объекта класса FORT и каждого его
отрисовываемого компонента интерактивный путь создаёт отдельную запись проверки.
Она использует те же текущие позы узлов и преобразования присоединённых
компонентов, что и геометрия кадра. После ограничения целевой позиции по
TerrainWorld камера проверяет непрерывный отрезок от прежней позиции до цели.
Если после нативных MSH-фильтров остаётся пересечение, позиция возвращается к
началу шага, а рассчитанный из ввода новый поворот сохраняется. Маски batch и
type-7, а также продолжение отрезка по helper-link описаны в
разделе MSH о сегментном запросе.
Повторная отправка того же ожидающего кадра, например после изменения размера
области отображения, использует уже подготовленное состояние и не применяет
движение второй раз. --legacy-camera-capture сохраняет положение захваченной
камеры без изменений и не запускает проверку зданий. Это диагностическое
ограничение, а не восстановленное правило игровой или следящей камеры
оригинала. В проверке начальная поза берётся из декодированных связей CTLD;
проход двери отдельно проверяется после явной выборки записанного в MSH кадра 1
только для Node3. Так проверяется вход запроса столкновения и путь камеры;
видимая дверь не анимируется, а миссионные команды не воспроизводятся.
Высота полёта сверху не ограничена. Это ограничение диагностической камеры превью; точные правила игровых и следящих камер оригинала остаются неподтверждёнными.
Ограничения точного совпадения
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 и атмосферы;
- переход на запасной набор возможностей устройства;
- квантование времени симуляции.
Для отладки требуется детерминированный захват кадра: состояние камеры, идентификаторы видимых объектов, список элементов отрисовки, ключи конвейера, матрицы и хэши промежуточных буферов.