405 lines
37 KiB
Markdown
405 lines
37 KiB
Markdown
# Кадр отрисовки
|
||
|
||
Кадр завершает цикл и не является отдельной операцией рендерера. До вызовов
|
||
отрисовки собирается ввод, вычисляется такт, применяются отложенные операции,
|
||
выбирается камера и обновляется слушатель 3D-звука.
|
||
|
||
## Порядок кадра
|
||
|
||
Сам рельеф образует полноценную графическую сцену: список размещённых объектов
|
||
может быть пустым. Наличие MSH-компонентов объектов не является условием
|
||
отрисовки карты. Проверочные TMA без объектов позволяют отдельно загружать
|
||
карты ресурсов, для которых в установленной игре нет штатной миссии.
|
||
|
||
```text
|
||
системные сообщения и ввод
|
||
-> расчёт симуляции
|
||
-> отложенные операции над объектами
|
||
-> анимация и преобразования
|
||
-> камера и звуковой слушатель
|
||
-> видимость и очереди отрисовки
|
||
-> материалы и проходы отрисовки
|
||
-> завершение рендерера
|
||
-> обратные вызовы после отрисовки и UI
|
||
```
|
||
|
||
По анализу вызовов установлен общий порядок в `World3D::stdRenderGame`: камера
|
||
передаётся в Terrain, задаются `viewport` и матрицы, вызываются граничные слоты
|
||
рендерера, выставляется `in_render`, обходится мир, завершаются проходы мира и
|
||
затенения, вызывается завершение рендерера, снимается `in_render`, затем
|
||
рассылается `end-of-render`.
|
||
|
||
В интерактивном preview часы материалов и окружения прибавляют полное время
|
||
между обновлениями. Ограничение шага в 100 мс остаётся у движения свободной
|
||
камеры и не обрезает эти часы. В режиме replay окружение получает разницу
|
||
последних применённых значений общего control clock.
|
||
|
||
Если swapchain просит повторить ещё не представленный кадр, preview сохраняет
|
||
его общий clock, уже вычисленные позы, FX/RNG и фазы материалов. При изменении
|
||
drawable обновляются камера, aspect и viewport, после чего sky, environment
|
||
geometry и projected shadows пересобираются для нового размера.
|
||
Положение и ориентация камеры берутся из сохранённого состояния
|
||
ожидающего кадра; новый ввод мыши применяется при следующем сэмпле. Сэмпл
|
||
погоды остаётся прежним: экранные billboards дождя и снега повторно
|
||
проецируются из закэшированных мировых головы и хвоста, без второго шага
|
||
симуляции окружения или повторной отправки звуковых событий. Следующий такт
|
||
выбирается после успешного представления кадра; пауза нулевого drawable не
|
||
добавляет время.
|
||
|
||
## Прозрачность порталов MSH
|
||
|
||
GOG `Terrain.dll` (SHA-256
|
||
`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead`),
|
||
RVA `0x1B0F0`, заменяет diffuse alpha пакета без type 16 с batch bit `0x0008`.
|
||
При `0x0010` аргумент кривой — `sqrt(distance)`, границы — `2.6 / fov` и
|
||
`7.7999997 / fov`, наклон — `fov / (7.7999997 - 2.6)`. Без этого бита
|
||
`0x0040` задаёт ноль; иначе границы — `75 * 1.3 / fov`, `95 * 1.3 / fov`,
|
||
наклон — `fov / (95 * 1.3 - 75 * 1.3)`. До ближней границы alpha равна нулю,
|
||
после дальней — единице. Внутри перехода alpha равна произведению смещения от
|
||
ближней границы на наклон. Это замена alpha MAT0, а не дополнительный множитель.
|
||
|
||
Метод расстояния `0x246A0` через `0x247B0` читает первую индексированную
|
||
вершину пакета, преобразует её в мир и вычисляет квадрат расстояния до камеры.
|
||
Текущий Ngi32 dispatch `FastProc+0x2C` указывает на RVA `0x1D6D0`:
|
||
`RSQRTSS` возвращает приближение обратного корня, которое умножается на квадрат
|
||
расстояния. Получается приближённое евклидово расстояние; дополнительный `FSQRT`
|
||
ветви `0x0010` выполняется уже над ним. Хеш Ngi32 —
|
||
`bab9840d94f4e4e74ffc26677724fa896cf4823845504d09a9e025f80016edf5`.
|
||
Скаляр камеры в шестом float getter `0x4F5F0` и поле CShade `+0xBB4`
|
||
проверены в работающем оригинале: оба содержат текущий горизонтальный FOV.
|
||
|
||
`legacy_msh_portal_alpha` хранит кривую в `fparkan-render`. Её опорная вершина
|
||
принадлежит входному пакету: выбирать вместо неё первую видимую точку нельзя.
|
||
В живом краевом ракурсе AutoDemo первая вершина окна находится за правой
|
||
границей viewport (`NDC x ≈ 1,048`), а следующая — внутри (`x ≈ 0,815`).
|
||
Оригинал всё равно использует первую: расстояние `106,74783` даёт alpha
|
||
`0,51991343`. Выбор следующей изменил бы alpha примерно на `0,026`.
|
||
Передача пакета renderer-у происходит после записи этой alpha.
|
||
|
||
Адаптер изолирует инструкцию `RSQRTSS`; на x86-64 расстояние и alpha этого
|
||
ракурса совпадают с оригиналом побитно. Обычный `sqrt` давал alpha
|
||
`0,5196797`: при упаковке прозрачности этого достаточно для отличия отдельных
|
||
пикселей. Прежнее предположение о выборе вершины после clipping и связанный
|
||
счётчик `portal_fade_packet_selection_approx` удалены. На других архитектурах
|
||
сохраняется переносимый корень с явным `packet_distance_native_rsqrt_unsupported`;
|
||
совпадение камеры с вершиной использует нулевую дистанцию и
|
||
`packet_zero_distance_fallback` вместо native `0 × infinity`.
|
||
Поток type 16 ещё не декодирован. Проверка краевого окна не закрывает остальные
|
||
внутренние ракурсы завода.
|
||
|
||
## Дистанционная кривая тумана
|
||
|
||
Программный `CShade::ShadeIndexedStrided` в GOG Terrain.dll сравнивает квадрат
|
||
дистанции с квадратами ближней и дальней границ. По RVA `0x2EEED–0x2EF45`
|
||
видимость равна `1 - (distance² - near²) / (far² - near²)` между границами,
|
||
единице перед ближней и нулю за дальней. Такой же закон использует отдельный
|
||
путь цвета пакета `0x2F230`. Линейная интерполяция по самой дистанции давала
|
||
слишком плотный туман в середине диапазона.
|
||
|
||
В работающем оригинале границы 0 и `0x442389E6`, квадрат дистанции
|
||
`0x47707B04` дали видимость `0x3F5B2B94`. Rust-тест проверяет передаваемые GPU
|
||
квадраты границ по этому наблюдению. GLSL вычисляет квадрат расстояния вершины,
|
||
после чего растеризатор интерполирует коэффициент по треугольнику.
|
||
`native_fog.effective.sink_rgb` совпадает с `sky.packed[1]`, нижней границей
|
||
освещения CShade. Обычный GPU fog color подтверждён отдельно: `CSky::Render`
|
||
по RVA `0x48341` задаёт renderer default из цвета горизонта. В живом замере
|
||
`0xFF404064` дошёл до `IDirect3DDevice7::SetRenderState(34)` в том же игровом
|
||
тике. В другом ракурсе палитра `[0xFFA8A5CE, 0xFFABA9D5, 0xFFBAA9D5,
|
||
0xFFABA9D5]` дала default `0xFFA9A7D1`. Это не покрывает временные изменения
|
||
цвета отдельных проходов: например, native render path устанавливает чёрный
|
||
цвет и затем восстанавливает сохранённый.
|
||
Выбор проходов по native shade-state пока приближён общим туманом мира и
|
||
считается как `render_coverage fog_pass_eligibility_approx`.
|
||
|
||
## Радиус Control Op1
|
||
|
||
GOG `Effect.dll` (SHA-256
|
||
`386a3e8db1ce23f61957825d633c7dae1ed40c25cab0cf10a534385fd7cee789`),
|
||
RVA `0xF6E0`, преобразует направление текущей матрицей, сохраняет его длину
|
||
перед нормализацией и умножает на неё интерполированный локальный радиус.
|
||
Умножение видно по RVA `0xFB13`; перед LightManager `+0x10` неположительный
|
||
результат заменяется на `0.01`. В живом «Конструкторе» для синего источника
|
||
завода локальный радиус 7 и длина `0x409B1F91` дали радиус `0x4207BB9F`.
|
||
Это же значение проверяется Rust-тестом общего Op1 evaluator.
|
||
|
||
Случайные добавки цвета и радиуса ещё не воспроизводятся; приложение сообщает
|
||
число таких Control-команд как `control_op1_jitter_unsupported` и использует
|
||
их авторские значения. У синего источника в проверенном захвате добавки нулевые.
|
||
|
||
Флаг источника `0x80000000` отделяет локальный свет объекта от общего света
|
||
мира. В эталонной `Terrain.dll` (SHA-256
|
||
`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead`)
|
||
`CLightManager` RVA `0x4C1D0` ведёт отдельные числа всех активных и общих
|
||
источников; второй счётчик исключает этот бит. Живой вход `CShade` RVA
|
||
`0x2E4A0` подтверждает различие: завод получает жёлтый источник с flags
|
||
`0xA0000000`, а рельеф — только общие источники. Для выбранной вершины крыши
|
||
native diffuse `1,205386/1,095748/1,893271` и specular после переноса переполнения
|
||
`0,160597/0,147096/0,358346` воспроизводятся формулой shader с теми же входами
|
||
с отклонением менее `0,0002`. Менять формулу блика для этого дефекта не нужно.
|
||
|
||
В Vulkan каждый свет хранит `owner_scope` в последнем DWORD 48-байтовой
|
||
SSBO-записи; область диапазона передаётся через `firstInstance`. Нулевая
|
||
область света доступна всем диапазонам, ненулевая — только совпадающему
|
||
владельцу. Terrain и геометрия без владельца имеют область ноль. Один общий
|
||
буфер сохраняется; отдельные наборы света для каждого строения не создаются.
|
||
|
||
## Элемент отрисовки
|
||
|
||
Подготовленный элемент отрисовки содержит:
|
||
|
||
- мировую матрицу узла;
|
||
- флаги пакета `batch` и диапазон индексов;
|
||
- дескриптор материала WEAR;
|
||
- активную фазу и коэффициенты MAT0;
|
||
- дескриптор текстуры;
|
||
- необязательный дескриптор карты освещения;
|
||
- фазу отрисовки и ключ сортировки;
|
||
- состояние конвейера устаревшей отрисовки.
|
||
|
||
Подготовленный элемент должен ссылаться на неизменяемые данные кадра. Изменение
|
||
фазы или кэша текстур посреди прохода не должно менять уже собранную очередь.
|
||
|
||
## Состояние MAT0 и прозрачные фоны
|
||
|
||
Для диапазонов мира миссии выбранный материал MAT0 задаёт нативную категорию
|
||
конвейера. Категория определяет смешивание и альфа-тест, сохраняя настройки
|
||
проверки глубины и отсечения граней. Если селектор материала анимируется,
|
||
вместе с ним обновляется и категория.
|
||
Принадлежность к очереди определяется отдельно: прозрачным считается диапазон
|
||
мира с выбранной прозрачностью меньше единицы или с нативными флагами пакета
|
||
`batch` `0x108`. Такой диапазон при включённой проверке глубины не записывает
|
||
глубину. За фиксированными диапазонами окружения сохраняются их явные состояния.
|
||
|
||
Для MSH без потока цветов type 16 диапазон, цвет которого вычисляет CPU, при
|
||
raw-флаге `Batch20` `0x0008` получает переопределение alpha по
|
||
[дистанционной кривой портала](#прозрачность-порталов-msh). Bit `0x0010`
|
||
имеет приоритет; иначе bit `0x0040` задаёт нулевую alpha, а оставшаяся
|
||
ветвь использует границы 75/95 с поправкой на FOV. При наличии type 16 или
|
||
отсутствии `0x0008` переопределения нет. Оно относится к одному draw range и не меняет общий
|
||
материал; alpha `0` временно задаёт число индексов `0`, а при очистке
|
||
переопределения исходное число индексов восстанавливается.
|
||
|
||
Fade обновляется с камерой каждый кадр и сохраняет исходную опорную вершину
|
||
диапазона, в том числе когда часть окна выходит за границу экрана.
|
||
|
||
В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков
|
||
явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на
|
||
своих местах; прозрачная очередь мира сортируется от дальних элементов к
|
||
ближним и вставляется сразу после последнего помеченного фона. Порядок остальных
|
||
фиксированных диапазонов сохраняется. Поэтому облака могут отрисовываться перед
|
||
прозрачной листвой, а расположенные позднее солнце, погода и другие передние
|
||
эффекты сохраняют свой относительный порядок. Это локальное правило очереди, а
|
||
не перестановка всех проходов неба или эффектов.
|
||
|
||
## Проверки
|
||
|
||
`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`, полученные через
|
||
[процедуру захвата нативного кадра](../../tools/native-frame-capture/README.md).
|
||
|
||
Формат захвата сохраняет локальную матрицу `selector0_words` и, когда она
|
||
доступна, записывает `effective_world_matrix_words` с маркером пространства
|
||
`world`.
|
||
|
||
Процедура захвата записывает наблюдавшийся путь миссии и значение
|
||
`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 фабрики, а не восстановленный
|
||
контракт матрицы вида или проекции.
|
||
|
||
## Представление готового кадра
|
||
|
||
Vulkan сохраняет явно выбранный режим, если поверхность его поддерживает.
|
||
Приложение предпочитает MAILBOX: готовый новый кадр заменяет ожидающий,
|
||
а показ остаётся привязан к развёртке. При отсутствии MAILBOX выбирается
|
||
IMMEDIATE, затем FIFO. IMMEDIATE допускает разрывы изображения, зато не
|
||
навязывает ожидание вертикальной развёртки; FIFO остаётся запасным режимом.
|
||
Семантика режимов описана в [спецификации Vulkan](https://docs.vulkan.org/refpages/latest/refpages/source/VkPresentModeKHR.html).
|
||
|
||
Оконная ветка GOG Ngi32.dll (SHA-256 из [таблицы модулей](original-binaries.md))
|
||
по RVA `0x6E1B` вызывает `IDirectDrawSurface7::Blt` с `DDBLT_WAIT`.
|
||
Эта ветка наблюдалась при нативных захватах. Флаг ждёт готовности блиттера,
|
||
а не задаёт интервал развёртки — см. [описание Microsoft](https://learn.microsoft.com/en-us/windows/win32/api/ddraw/nf-ddraw-idirectdrawsurface7-blt).
|
||
Современный композитор и драйвер всё равно могут влиять на фактический показ;
|
||
это правило выбора API, а не гарантия одинаковой задержки двух движков.
|
||
|
||
## Предпросмотр по захвату камеры D3D7
|
||
|
||
`fparkan-game --legacy-camera-capture <json>` принимает матрицу камеры
|
||
`selector0_words`, прямоугольник области отображения `viewport` D3D7
|
||
`[left, top, right, bottom]`, ближнюю и дальнюю плоскости и угол поля зрения.
|
||
По этим данным превью строит матрицы вида и проекции D3D7, передаёт полученную
|
||
матрицу `clip_from_world` камере превью и напрямую использует оси камеры из
|
||
матрицы вида. Поэтому базис захвата, включая крен, не сводится к рысканию и
|
||
тангажу. Прямоугольник задаёт физический размер области отрисовки:
|
||
`right - left` на `bottom - top`. Размер окна превью закреплён: если при
|
||
запуске или после ненулевого изменения окна физический размер не совпадает с
|
||
заданным, приложение завершает работу, а не растягивает изображение.
|
||
|
||
Проекция во время рисования может отличаться от восстановленного состояния
|
||
камеры после обхода мира. В GOG у `CStridedPrimitive::RenderVB` (Terrain RVA
|
||
`0x1D110`) renderer содержит `near = 0.5`, а на границе World3D RVA `0x13CE4`
|
||
уже `near = 5`. Поэтому захват отдельно сохраняет `geometry_projection` при
|
||
первом вызове геометрии с той же мировой матрицей камеры. Поток, поколение,
|
||
renderer, viewport, far и FOV сверяются с последующей границей; только
|
||
подтверждённая запись используется при воспроизведении. Иначе пол в трёх
|
||
единицах от камеры может исчезнуть из-за чужой ближней плоскости. Старые JSON
|
||
без этой записи сохраняют прежнее поведение с явным счётчиком
|
||
`captured_projection_unverified`; их нужно переснять для проверки близких поверхностей.
|
||
Сравнивайте результат с фрагментом исходного PNG по прямоугольнику
|
||
`[left, top, right, bottom]`. В этом режиме `FreeFlightCamera` не создаётся,
|
||
но при повторной отрисовке обновляются окружение и проецируемые тени для
|
||
захваченной камеры. До обновления окружения, которое может создавать звуковые
|
||
события, слушателю задаются захваченные позиция, направление вперёд и верхняя
|
||
ось; начальная сортировка диапазонов мира использует ту же позицию.
|
||
|
||
Если в JSON присутствует пара `effective_world_matrix_space: "world"` и
|
||
`effective_world_matrix_words`, превью использует мировую матрицу. Матрица
|
||
`selector0_words` остаётся локальной и используется как запасной вариант только
|
||
при отсутствии мировых полей, если `parented_camera` не равно `true`. Для
|
||
родительской камеры без эффективной мировой матрицы вход отклоняется.
|
||
|
||
Захват может содержать наблюдавшийся путь миссии и проверенное значение
|
||
`atmosphere_seconds`; последнее используется только при отсутствии явного
|
||
параметра командной строки. Это значение фиксирует фазу расписания атмосферы
|
||
при сравнении с неподвижной камерой, тогда как FX по умолчанию получает реальное
|
||
время кадра. `simulation_time_seconds`, погода, RNG и полное состояние миссии
|
||
остаются неизвестными, поэтому совпадение камеры и фазы не подтверждает
|
||
визуального совпадения с нативным кадром.
|
||
|
||
Параметр `--control-clock-ms` воспроизводит единые часы MSH/FX от нуля до
|
||
выбранного значения в миллисекундах за указанное число кадров. Без этого
|
||
параметра часы идут в реальном времени. Для `--readback-sequence-out` этот
|
||
управляемый режим обязателен. Он не восстанавливает
|
||
`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](../tomes/05-render.md#камера-и-размещение-в-оригинале)).
|
||
Для отношения ширины к высоте `a` превью вычисляет вертикальный угол
|
||
`2 * atan(tan(horizontal_fov / 2) / a)`. Поэтому горизонтальный угол остаётся
|
||
`1.3` рад после изменения размера окна; новый viewport и матрица камеры
|
||
применяются при следующей отрисовке.
|
||
|
||
Проекция направления не сравнивает косинус угла с ближней дистанцией: она
|
||
отбрасывает только лучи за камерой и почти параллельные плоскости изображения
|
||
(`forward · direction <= 1e-4`). Лучи в передней полусфере могут проецироваться
|
||
за край viewport; владелец спрайта отдельно решает, нужно ли его рисовать.
|
||
|
||
После движения свободная камера ограничивает X и Y прямоугольником
|
||
поверхностей карты, затем поднимается над найденным рельефом на заданный
|
||
зазор. Границы берутся из корня пространственного индекса `TerrainWorld`,
|
||
поэтому неиспользуемые вершины файла их не расширяют и повторного обхода
|
||
геометрии в каждом кадре нет:
|
||
|
||
```rust
|
||
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 о сегментном запросе](msh.md#type-7-и-сегментный-запрос).
|
||
|
||
Повторная отправка того же ожидающего кадра, например после изменения размера
|
||
области отображения, использует уже подготовленное состояние и не применяет
|
||
движение второй раз. `--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 и атмосферы;
|
||
- переход на запасной набор возможностей устройства;
|
||
- квантование времени симуляции.
|
||
|
||
Параметр `--readback-sequence-out` сохраняет необработанные байты каждого
|
||
отправленного кадра; в имя файла записываются индекс кадра, значение общих
|
||
управляемых часов и размер изображения. Байты соответствуют выбранному формату
|
||
изображения swapchain Vulkan и не гарантируют порядок каналов R, G, B, A. Числовое
|
||
формат выводится как `readback_format`; расширение `.rgba` само по себе порядок
|
||
каналов не определяет. Последовательность не содержит структурированных ID
|
||
видимых объектов, списка элементов отрисовки, ключей конвейера, матриц или
|
||
хэшей промежуточных буферов.
|
||
|
||
При преобразовании кадра в PNG сохраняется исходный RGB, а прозрачность
|
||
выключается: окно показывает готовое непрозрачное изображение. Альфа-канал
|
||
GPU может содержать результат смешивания поверхностей; повторное применение
|
||
его к готовому цвету затемнило бы кадр ещё раз. Поэтому конвертер и сборщик
|
||
сравнительных пар игнорируют эту альфу, а raw-файл сохраняют без изменений.
|