aboutsummaryrefslogtreecommitdiff
path: root/docs/tomes
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tomes')
-rw-r--r--docs/tomes/05-render.md214
1 files changed, 201 insertions, 13 deletions
diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md
index 72b0685..4aba919 100644
--- a/docs/tomes/05-render.md
+++ b/docs/tomes/05-render.md
@@ -845,6 +845,17 @@ projected shadows. Радиус источника масштабируется
синий свет завода почти незаметным. Неположительный итог заменяется на 0,01.
Случайные добавки к цвету и радиусу пока явно считаются в
`render_coverage control_op1_jitter_unsupported`.
+
+Эффект может одновременно рисовать поверхность и издавать звук. Для Op2
+приложение создаёт отдельный звуковой источник у каждого экземпляра команды:
+несколько одинаковых механизмов слышны одновременно и не заменяют дождь.
+Позиция следует анимируемому узлу, а фаза включает, обновляет и останавливает
+loop. Обычная строка Control создаёт экземпляр с нулевой постоянной фазой;
+звуки с таким режимом ожидают отдельной управляющей команды. Периодические
+звуки работают по часам уже в preview. Устройство вывода остаётся в приложении,
+а правила переходов и параметров — в `fparkan-fx::ambient_sound`
+([подробнее](../reference/environment-effects.md#звуки-control)).
+
Туман также зависит от расстояния нелинейно: между ближней и дальней границами
движок интерполирует по квадрату дистанции. При границах 0 и 700 точка в 350
получает четверть тумана, а не половину. Это сохраняет контраст среднего плана.
@@ -998,6 +1009,12 @@ sun-ray queries поэтому используют исходные коордÐ
она не равна одному лишь обратному преобразованию камеры.
В проверенном GOG контексте Ngi32 использовались `n=0.5`, `z=700`, `f=1.3`.
+Ближняя плоскость относится к конкретному проходу. После рисования движок
+может вернуть прежние настройки камеры: захват только в конце мира видел
+`n=5` и при воспроизведении вырезал пол перед игроком. Формат захвата поэтому
+хранит проверенную проекцию вызова геометрии отдельно от состояния камеры;
+подробности приведены в [описании кадра](../reference/render-frame.md#предпросмотр-по-захвату-камеры-d3d7).
+
У GOG `Terrain.dll + 0x7355c` хранит указатель на активную внешнюю камеру.
Её selector 0 возвращает 16 слов начиная с `+0x20`, перенос находится в словах
3, 7, 11. Интерфейс `LoadCamera` расположен отдельно, по `+0x134` объекта.
@@ -1013,11 +1030,39 @@ Node38, а диапазоны граней — в 68-байтовых Slot68. В
проверяются на единственное назначение, а выходящий за массив, повторный или
неподдерживаемый диапазон считается ошибкой загрузки.
-Первый рендер карты, terrain-запросы высоты и пересечения, receiver-ы теней и
-обрезка контуром зданий используют только LOD0. Отбор сохраняет порядок
-исходных граней, а контурный проход отдельно переводит индекс спроецированной
-грани обратно в исходный. LOD1 пока не подставляется по расстоянию: такой
-переход не реализован.
+Рисуемый уровень выбирается по ячейкам, а не по расстоянию до отдельного
+треугольника. Начало сетки находится в первых двух float заголовка Slot68,
+размер ячейки — в границах первого слота LOD0. Атрибут 2 потока Node38 задаёт
+число ячеек по X; индекс ячейки равен `x * count_y + y`. Приложение проверяет,
+что границы всех слотов образуют эту регулярную сетку. Все 33 карты GOG
+проходят такую проверку, включая вырожденные грани на общей границе ячеек:
+принадлежность грани берётся из слота, а не угадывается по её центру.
+
+Координаты камеры переводятся в целые координаты сетки округлением вниз.
+Если `dx * dx + dy * dy >= 9` и горизонтальный FOV не меньше 0,5 радиана,
+выбирается LOD1. При увеличении масштаба, отсутствии дальнего слота или
+наличии геометрического patch остаётся LOD0. Камера за границей карты не
+прижимается к крайней ячейке для этого расчёта. Правило восстановлено по
+GOG `Terrain.dll` ([сборка и SHA-256](../reference/original-binaries.md)):
+`CLandscape::Render`, RVA `0x11340`, выбор уровня `0x11878–0x11A03`.
+Исполнение оригинала подтверждает выбор дальнего слота, а getter камеры
+`Ngi32.dll` RVA `0x4D960` возвращает именно FOV.
+
+Основания строений сохраняют подробную геометрию. Нативная регистрация
+patch (`Terrain.dll` RVA `0x12E20`) проверяет его треугольники против каждой
+ячейки через `mrnParaIntersectsPoly`. Поэтому учитываются и соседние ячейки,
+которых касается край треугольника. Перед проверкой координаты нормализуются
+в `f32` относительно центра ячейки; замена этой операции сравнением мировых
+границ меняет результат на общих рёбрах. На «Конструкторе» игнорируемый
+ресурсный тест сверяет все 22 ячейки с принудительным LOD0 с набором,
+полученным в живом оригинале.
+
+Vulkan получает оба уровня один раз. Диапазоны индексов разделены по
+материалу и исходной ячейке; на каждом кадре включается только выбранный
+уровень. Сгенерированные отмостки остаются включены. Terrain-запросы высоты
+и пересечения, receiver-ы теней и обрезка контуром зданий используют
+канонический LOD0. Отбор сохраняет порядок исходных граней, а контурный проход
+отдельно переводит индекс спроецированной грани обратно в исходный.
```rust
let assignments = mesh.face_lod_assignments()?;
@@ -1035,9 +1080,10 @@ for (face, assignment) in mesh.faces.iter().zip(assignments.iter()) {
исходные фрагменты рельефа вместе с треугольниками отмостки в одном наборе.
`TerrainWorld::with_basement_foundation` принимает этот набор во владение и
устанавливает те же координаты и маски в BVH высотных запросов и пересечений.
-Предпросмотр миссии на Vulkan пока строит только LOD0: связывание BAS-записей
-с этой подготовкой ещё не подключено, поэтому наличие API не означает, что
-геометрия миссии уже обрезана.
+Предпросмотр миссии связывает BAS-записи с этой подготовкой до загрузки
+материалов. Полученная поверхность общая для изображения, теней и запросов;
+поправка размещения основания один раз применяется к корню строения и его
+присоединённым компонентам.
Внешнее кольцо должно лежать на области LOD0, а каждый его сегмент — оставаться
покрытым текущей поверхностью. Непокрытый сегмент или пересекающиеся контуры
@@ -1048,13 +1094,33 @@ for (face, assignment) in mesh.faces.iter().zip(assignments.iter()) {
## Материалы ландшафта
-Рядом с `Land.msh` лежат `Land1.wea` и `Land2.wea`. Старший байт
-`TerrainFace28.material_tag` выбирает материал первой таблицы, младший —
-второй. `0xff` в старшем байте означает отсутствие первого слоя.
+Рядом с `Land.msh` лежат `Land1.wea` и `Land2.wea`. Младший байт
+`TerrainFace28.material_tag` выбирает базовый материал `Land1.wea`, старший —
+дополнительный материал той же таблицы. `0xff` в старшем байте означает
+отсутствие дополнительного слоя. `Land2.wea` содержит соответствующие этим
+двум строкам detail-материалы.
В AutoDemo встречаются теги `0102`, `0203`, `ff01`, `ff02`, `ff03`.
Менеджер World3D получает селектор `(table_index << 16) | material_index`
и возвращает выбранную фазу MAT0 размером 76 байт.
+Таблица, строка и блок анимации — разные части запроса. В быстром проходе
+с двумя текстурами `CShade` запрашивает для Land2 блок анимации **1**. Он может
+выбирать другую текстуру, а не только другие коэффициенты освещения:
+например, в «Конструкторе» это `L09M.0` вместо `L09.0`. Подмена блока первым
+приводит к умножению базового изображения на неподходящую detail-текстуру и
+меняет яркость и контраст земли. `TerrainMaterialSelection` сохраняет номер
+блока отдельно от packed material key; приложение использует его при загрузке
+дескриптора и в последующих кадрах. Если второго блока нет, общий MAT0 sampler
+возвращается к первому, как оригинальный `GetMaterialPhase`.
+
+В GOG `Terrain.dll` (сборка из [таблицы бинарников](../reference/original-binaries.md))
+быстрая ветвь находится в RVA `0x1A7AC–0x1A7FB`. Живое исполнение передаёт
+handle `0x00010002`, block `1` и устанавливает режим текстур `9`; первая
+текстура умножается на diffuse, вторая — на результат с множителем два.
+Ближний дополнительный проход с порогом `260 / FOV` относится к fallback без
+этого режима. На проверенном устройстве он не выбирается, поэтому переносить
+его дистанционный порог в быстрый проход нельзя.
+
Type 18 в `Land.msh` оригинал называет microtexture mapping chunk;
он содержит четырёхбайтовые записи. Type 11 хранит пары `u16`, выбранные
таблицей terrain slots. Их первое поле — ключ shade cache, а не номер строки
@@ -1072,12 +1138,103 @@ lightmap state передаются в `VulkanStaticMaterial`, а shader посÐ
сэмплирует эти четыре стадии. Animation clock обновляет выбранные фазы и
коэффициенты без пересборки terrain mesh, сохраняя native type-14 overlay mask.
+### Отражение на воде
+
+Поверхность воды нельзя рисовать тем же умножением текстур, что и землю.
+В проходе ландшафта GOG Terrain.dll флаг грани `0x20000` участвует в выборе
+отражения; при включённом EMBM живой водный кадр использует texture mode 10.
+Оригинал создаёт для этого отдельную камеру `REFLECTION_SHIFTED`.
+В [эталонной сборке](../reference/original-binaries.md) выбор грани находится
+в Terrain.dll RVA `0x28A50`, а подготовка EMBM-пакета — в RVA `0x1B540`.
+
+Для горизонтальной воды на высоте `h` камера отражения находится в точке
+`[eye_x, eye_y, 2h − eye_z]`. Но она не повторяет наклон основной камеры:
+смотрит вертикально вверх, а её окно лежит на прямоугольнике водоёма. Поэтому
+текстура отражения привязана к местности, а не к экрану игрока. В плоскости
+воды координаты изображения равны `U = 1 − Ynorm`, `V = 1 − Xnorm`.
+
+Terrain.dll RVA `0x4EFF0` передаёт shifted-проекции пять значений:
+`[min_y − eye_y, min_x − eye_x, span_y, span_x, eye_z − h]`. Например,
+для наблюдателя `[1200, 800, 185]`, воды `150` и границ
+`[1000, 500, 1500, 1200]` это `[-300, -200, 700, 500, 35]`.
+Матрица камеры и эти аргументы проверены исполнением оригинальной функции
+при двух положениях и разных поворотах основной камеры. Оракул подменяет
+ввод-вывод очереди, ландшафта и камеры; сам renderer и матрица глубины в эту
+проверку не входят. Vulkan-проекция сохраняет near/far основного вида;
+Rust-тест отдельно проверяет углы водоёма и границы глубины.
+
+Отражённый вид меняет сторону отсечения и отбрасывает геометрию ниже воды.
+Небо и оставшаяся сцена попадают в отдельную цветовую текстуру с собственной
+глубиной. В основном проходе водные треугольники читают её по координатам
+водоёма с добавленным смещением волн. Для неперспективной камеры API остаётся
+обычное зеркало с экранными координатами.
+
+Размер изображения отражения выбирается отдельно от размера основного кадра.
+Terrain.dll в конструкторе CShade (RVA `0x278E0`, цикл с `0x27FC7`) берёт
+ширину и высоту renderer через Ngi32.dll RVA `0x7FB0`. Сторона квадратной
+текстуры — наибольшая степень двойки, не превосходящая меньшую из этих
+величин и 1024; нижний предел равен 2. Например, экран 1280×1024 даёт
+отражение 1024×1024, а 1280×720 — 512×512. Getter размеров и исходный цикл
+выбора проверены исполнением DLL на десяти входах; создание нативной текстуры
+в этом оракуле пропущено. Vulkan создаёт цвет и глубину с этой стороной и
+задаёт каждому проходу собственные viewport и область очистки. Так уменьшение
+отражения не обрезает основной кадр и воспроизводит исходный масштаб его
+пикселей, от которого зависит чёткость берегов и ряби.
+
+Цветовой target переходит из attachment layout в shader-read layout после
+записи. Следующий зеркальный проход ждёт предыдущего чтения этой текстуры;
+основной и отражённый виды используют разные слоты uniform buffer. Во время
+записи отражения привязан отдельный нейтральный descriptor, чтобы проход
+не читал собственное вложение.
+
+Волны не требуют готовой текстуры из ресурсов: Terrain.dll RVA `0x2BF50`
+создаёт карту 128×128 со знаковыми парами Du/Dv. Для нормированных координат
+текселя `x` и `y` угол равен `(x + y − 1) × 12,566360473632812`, а компоненты —
+округлённые `64 cos(угол)` и `64 sin(угол)`. Все 32 768 байт Rust-генератора
+сверены с исполнением этой функции DLL при игровом x87 CW `0x007F`.
+
+Рябь привязана к водоёму, поэтому не плывёт вслед за экраном. Конструктор
+ландшафта собирает прямоугольник всех отражающих граней; U меняется против
+мировой Y, V — против X. Эти координаты повторяются сто раз, к обеим прибавляется
+`(queue_clock % 10000) / 10000`. Это самостоятельный clock: время суток и
+относительный clock неба не задают фазу волн. Захват хранит исходный
+`sky_uv_clock.raw_clock`; ограниченный replay фиксирует его, интерактивный
+продолжает с этого значения. Старые захваты без raw clock явно считаются в
+`captured_water_phase_unverified`.
+
+Карта Du/Dv смещает координаты следующей текстуры с диагональными коэффициентами
+`0,01`, без изменения яркости. Это правило
+[Direct3D BUMPENVMAP](https://learn.microsoft.com/en-us/windows/win32/direct3d9/bump-mapping-formulas).
+В Vulkan карта хранится в двух каналах несжатой текстуры и возвращается к
+знаковым значениям перед вычислением смещения.
+
+Матрица положения камеры, её окно, размер текстуры и координаты волн восстановлены, но весь
+контекст отражённого вида ещё не повторён. Диапазон глубины, небо, туман и FX
+заимствуются из основного вида. Для захвата с обрезанным viewport ещё не
+подтверждено соответствие размера окна FParkan полному renderer оригинала.
+Эти ограничения учитываются в `planar_reflection_render_context_approx`;
+совпадение генератора волн и окна не доказывает совпадения итогового отражения.
+Один target обслуживает ближайшую
+активную высоту воды; другие высоты и негоризонтальные поверхности явно
+считаются как неподдержанные. Это промежуточный водный проход, а не полная
+приёмка воды или лавы.
+
## Реализация Vulkan
`LegacyPipelineState` хранит blend, depth, cull и alpha-test. Из этих значений
получается ключ Vulkan pipeline. Alpha reference передаётся отдельно через
push constant; он не требует нового pipeline.
+Набор pipelines создаётся до первого draw. Для каждого исходного состояния
+готовятся его авторский вариант, прозрачный вариант с сохранённым alpha-test
+и пять категорий MAT0 с обеими политиками глубины. Cull и decal bias остаются
+исходными; одинаковые ключи объединяются. Смена категории материала или
+прозрачности диапазона выбирает готовый ключ, сохраняя descriptors, buffers и
+framebuffers. Vulkan `VkPipelineCache` принадлежит renderer, используется всеми
+созданиями pipelines и переживает пересоздание swapchain. На диск он пока не
+сохраняется. Счётчик `render_pipeline_cache` показывает число созданных
+pipelines, включая новые наборы после resize.
+
Рабочий путь `fparkan-game` загружает terrain и все выбранные mission roots
(по умолчанию весь список объектов), разрешая локальные material slots через
WEAR и MAT0 до объединения геометрии. Индексы GPU имеют тип `u32`: вся карта
@@ -1085,6 +1242,32 @@ WEAR и MAT0 до объединения геометрии. Индексы GPU
`u16`. Terrain передаёт в shader базовый, detail и overlay stages, lightmap и
animation phase; object batches сохраняют их legacy blend/depth/alpha state.
+При первоначальной загрузке одинаковые текстуры разных material slots
+используют один GPU image. Ключ включает размеры, RGBA8 и всю авторскую
+mip-цепочку: совпадения только базового изображения недостаточно. Хеш ускоряет
+поиск, а полное сравнение данных исключает ошибочное объединение при коллизии.
+Слоты сохраняют собственные descriptors, UV и параметры материала. При первой
+смене общей текстуры renderer создаёт отдельный image только для изменяемого
+слота; следующие фазы обновляют его на месте. Поэтому анимация одного материала
+не меняет соседний. Строка `render_texture_upload` показывает число слотов,
+начальных GPU images и отделений при обновлении. CPU-копии исходных пикселей
+в материалах эта оптимизация не объединяет.
+
+Рельеф образует неизменяемое начало общего vertex buffer. Control-анимация,
+FX и окружение обновляют следующие вершины, а новые индексы нужны только
+геометрии проекционных теней в конце index buffer. Поэтому кадр записывает
+лишь эти изменяемые части. При росте буфера новая память получает все данные,
+включая неизменяемое начало; уменьшение числа теней не уменьшает выделенную
+ёмкость. Это сохраняет номера вершин, диапазоны отрисовки и порядок проходов.
+
+Перед записью общих ресурсов renderer ждёт fences всех отправленных кадров.
+Одного fence текущего кадрового слота недостаточно: другой кадр ещё может
+читать ту же память. Ожидание всего устройства остаётся на пересоздании
+swapchain, диагностическом readback и завершении работы. Загрузка изменённых
+текстур пока использует отдельную синхронную transfer-команду. Строка
+`render_geometry_upload` показывает записанные vertex/index bytes и объём,
+который потребовался бы при полной перезаливке, без начальной загрузки.
+
После загрузки world path создаёт интерактивную free-flight камеру (`WASD`,
`E/Q`, `Shift`, `Ctrl`, RMB relative-look), обновляет listener и на каждом
кадре собирает environment frame. Для каждого отрисовываемого строения
@@ -1121,10 +1304,15 @@ drawable размер `(right - left) × (bottom - top)` и отклоняет Ð
`FreeFlightCamera` в этом режиме не создаётся, но redraw продолжает обновлять
environment и projected shadows от captured camera. Перед environment update и
его audio events listener получает position, forward и up captured camera;
-начальная сортировка world draw использует её же position. Phase capture может
+начальная сортировка world draw использует её же position. Облака в обычном
+запуске движутся с отдельным 50-секундным циклом UV; фон и звёзды имеют свои
+периоды. Изменение цвета неба по расписанию не заменяет это движение.
+Phase capture может
содержать наблюдаемый mission path и проверенный `atmosphere_seconds`; viewer
использует эту абсолютную фазу по умолчанию, если не указан явный CLI override,
-и удерживает sample атмосферы на ней через readback frames. Фазы FX следуют
+и удерживает sample атмосферы на ней через readback frames. Дополнительный
+`sky_uv_clock` фиксирует положение движущихся текстур на том же кадре;
+без него их начальная фаза не подтверждена. Фазы FX следуют
выбранному control clock: без `--control-clock-ms` он обновляется по elapsed
time, а с явным значением readback frames переигрывают заданный clock. Это не
полное simulation time: `weather`, RNG и