Advance to engine c962cd472. Document the render-pixel current-to-previous contract, native TAA/TAAU consumer and the remaining DLSS/object-motion/linear-HDR work. Co-Authored-By: Kimi Code <noreply@moonshot.ai>
86 KiB
HD-рендер AirDef: устройство, статус, план
Документ для передачи работы (человеку или агенту). Ведётся по ходу разработки: каждый этап, попавший в
main, отмечается здесь вместе с коммитом. Если что-то в коде расходится с этим текстом — прав код, а текст
надо поправить.
Что это
HD — третий пресет графики мода (/airdef shader off|on|hd): отложенное освещение в стиле шейдерпаков
(BSL/Complementary-класс), но написанное с нуля внутри нашего форка Horizon (Minecraft 26.3 на Rust/wgpu).
Код шейдерпаков (BSL, Complementary, Iris/OptiFine и прочих) не копируем: их лицензии запрещают перенос, а в
Horizon действует правило clean-room. Исключение — Photon (sixthsurge), коммит 26c2906ee02cc484359b835fba6993d443d45966: он под MIT, из него можно
переносить идеи и код с указанием авторства (комментарий у кода + gfx/THIRD_PARTY_PHOTON.md). Остальное пишем по знанию техник (статьи, общеизвестные алгоритмы).
Пресеты должны оставаться: off (ваниль), on (пост-эффекты мода), hd (этот рендер), позже rt (аппаратная
трассировка, отдельно).
Где код
| Что | Где |
|---|---|
Оркестровка HD: Hd, общие цели, буфер кадра, resize/write, сборка шейдеров (assemble: прелюдия + тело), контракты цвета/глубины/альф/времени (комментарий в начале файла) |
horizon/crates/horizon-client/src/gfx/hd/mod.rs |
Униформ кадра HdFrame (Rust) и сцена HdScene/HdLight/HdSettings, свет солнца/луны/облаков (light_of, cloud_light); общая WGSL-прелюдия (HdFrame, FullOut, vs_full) |
gfx/hd/frame.rs, gfx/hd/prelude.wgsl |
Тени (каскады, Shadows, cascades()); G-буфер (GBuffer, форматы) |
gfx/hd/shadows.rs; gfx/hd/gbuffer.rs |
AO (GTAO + блюр); освещение fs_light (+ тайлы источников: tiles.rs); вода (проход геометрии и композит) |
gfx/hd/ao.rs + ao.wgsl, ao_blur.wgsl; gfx/hd/lighting.rs + light.wgsl, gfx/hd/tiles.rs; gfx/hd/water.rs + water.wgsl |
TAA/TAAU и джиттер; bloom; tonemap (+ Hd::finish) |
gfx/hd/taa.rs + taa.wgsl; gfx/hd/bloom.rs + bloom.wgsl; gfx/hd/tonemap.rs + tonemap.wgsl |
| Объёмные облака (рэймарч слоя, тени облаков на землю) и объёмный туман, их цели и проходы; CPU-шум (2D, 3D Worley) | gfx/hd/volumes.rs + cloud_common.wgsl, cloud.wgsl, cloud_shadow.wgsl, fog.wgsl; gfx/hd/noise.rs |
Атмосфера: сборка текстуры неба (WGSL), чтение неба/дымки/тона света в шейдерах, цвет солнца (sun_transmittance) |
gfx/hd/sky.rs + sky.wgsl, sky_funcs.wgsl (+ THIRD_PARTY_PHOTON.md) |
Встраивание в кадр: G-буфер-проход, каскады теней, вызовы hd.light / hd.finish, джиттер, тайминги |
horizon/crates/horizon-client/src/gfx.rs (Renderer::render, ищи hd_on, AirDef fork:) |
G-буфер террейна: fs_gbuffer, fs_shadow |
horizon/crates/horizon-client/src/terrain.wgsl |
G-буфер сущностей и моделей мода: fs_entity_gbuffer, fs_entity_shadow |
horizon/crates/horizon-client/src/entity.wgsl |
| Материалы (классы спрайтов: листва, растения, металл, полированное), ветер, мокрые поверхности | gfx/hd/materials.rs; terrain.wgsl (terrain_vertex/wind_offset, material_params), entity.wgsl, gfx/hd/light.wgsl (shade_surface) |
| Пост-проход мода (взрывы, дым, вспышки, свечение) и его прелюдия | airdef-mod/shaders/effects.wgsl, прелюдия в horizon/.../gfx/mod_post.rs |
| Источники света мода (пламя, ракеты, взрывы, прожекторы) | airdef-mod/src/lib.rs, fn lights |
API мода: render.set-lights, render.set-lighting (уровень API 10), render.set-spot-lights (16) |
horizon/crates/horizon-mod-sdk/wit/horizon.wit, horizon/crates/horizon-mods/src/runtime.rs |
Форк ведётся в horizon/ (ветка airdef), в репозиторий мода попадает патчами:
cd horizon && git add -A crates && git commit -m "..." # коммит в форке
cd .. && rm -f horizon-patches/*.patch && git -C horizon format-patch -q $(cat HORIZON_COMMIT)..airdef -o ../horizon-patches
tools/airdef.py fork сверяет отпечаток HORIZON_COMMIT + патчей (horizon/.git/airdef-stamp) и пересобирает
ветку, если они изменились. После ручной перегенерации патчей у себя обнови отпечаток (см. fork_stamp в
tools/airdef.py), иначе следующий airdef.py fork пересоберёт ветку (безопасно: прежняя остаётся в airdef-prev).
Конвейер кадра (HD включён)
Цели мира (G-буфер, AO, освещение, пост-проход мода) — в «масштабированном» разрешении мира (world_post; при HORIZON_HD_SCALE < 1 оно меньше окна), цели после TAA (история, bloom, tonemap) — в разрешении окна. HDR — Rgba16Float. Глубина камеры — reversed-Z
(Depth32Float, 1 = близко, 0 = далеко). Позиции везде относительно камеры (camera-relative), мир — в f64
на CPU.
- Небо (
hd.sky, проходhd sky, перед освещением): текстура 128×72 (Rgba16Float): 128 азимутов (от солнца, 0..π) × 64 высот (v = √(h/(π/2))), плюс строка 64 с «окружающим светом» (текселы 0 — зенит, 1 — среднее у горизонта). Каждый тексел — марш по лучу (32 шага) однократного рассеяния Рэлея/Ми + озон, плоский член многократного рассеяния (MULTI_SCATTER), солнце и луна (луна — напротив) как источники; пропускание до источника — аналитическое (Chapman, как у Photon). Зависит только от высоты солнца → перерисовывается, лишь когда она сдвинулась больше чем на 0.001. Читают еёfs_lightи композит воды (привязки 15 и 10). - Каскады теней (2 шт.,
CASCADES = [40, 176]блоков полуразмера,SHADOW_SIZE = 2048, массив глубины из 2 слоёв). Источник — солнце днём, луна ночью (light_of). Матрицыcascades()привязаны к текселям (тени не «ползут»). Во время прохода буфер глобалов мира подменяется на глобалы каскада (copy-трюк) и потом возвращается. Рисуются секции террейна в коробке дальнего каскада (hd_shadow_visible) и сущности-тенеотбрасыватели (shadow_casters: без оверлеев 4/9/10/40 — 40 это светящиеся модели мода). - G-буфер (свой depth): albedo
Rgba8Unorm(a = эмиссия), нормальRgba16Float(мировая, изdpdx/dpdy), уровни светаRgba8Unorm(x = небо, y = блоки — сырые координаты лайтмапа /240), motionRg16Float(current→previous в render-пикселях, обе проекции с jitter). Террейн + дальние LOD-тайлы + сущности. Пока записывается camera/static motion; previous object transforms и прежний ветер — следующий этап #124. Полупрозрачное в G-буфер не попадает (идёт ванильным путём); вода — отдельный путь, см. п. 3 и «Вода». - Вода (
water_pass, только если на экране есть полупрозрачные секции, нетframe.underwaterи неHORIZON_HD_WATER=0): те же секции ещё раз сfs_water(terrain.wgsl), который отбрасывает всё, кроме слоёв спрайтовwater_still/water_flow(номера слоёв передаются вRenderer::newизsprite_layersи ставятся override-константамиWATER_STILL/WATER_FLOW; стекло, лёд и прочее остаются ванильными). Цели: цвет (спрайт × биомный тинт, a = свет блоков,Rgba16Float), нормаль грани (a = свет неба,Rgba8Unorm) и собственная глубина воды (Depth32Float, 0 = воды нет; тест глубины только среди воды, перекрытие землёй решают потребители). Пайплайныgbuffer_water(секции) иflat.water(flat-путь). 3a. GTAO (полразрешения,Rg16Float: окклюзия, дистанция) + depth-aware блюр 5×5. 3 среза × 5 шагов, радиус 1.8 блока, шум IGN по кадру. Сила —HdFrame.ambient.w(HORIZON_HD_AO=<число>, 0 = выкл). 3b. Облака и туман (hd.volumes, до освещения, после прохода неба):hd cloud shadows(карта 256² оптической толщины облаков по ходу солнца, ±1200 блоков вокруг камеры),hd clouds(половина разрешения мира,Rgba16Float: свет + пропускание; рэймарч слоя 192.33…264 блока, 8–26 шагов с джиттером IGN, свет — 3 шага с удвоением, небо — 1, 4 октавы многократного рассеяния Photon; история ping-pong с репроекцией по направлению и сдвигу камеры, вес до 0.9, отбрасывается при расхождении) иhd fog(половина разрешения: 18 шагов от камеры до глубины мира, не дальше 192 блоков, плотность — высотная дымка + утренний туман у земли + дождь + шум, свет — солнце × тень каскадов × фаза Photon + свет неба; вторая цель — длина луча для апсэмплинга). Ванильные облака при этом не рисуются (hd_volumetric_cloudsвgfx.rs).fs_lightнакладывает облака на небо (c·T + S), туман на всё (fog_at: 4 текселя с весом по близости длины луча), а тень облаков (cloud_shadow) умножает прямой солнечный свет.HORIZON_HD_CLOUDS=0/HORIZON_HD_VOLFOG=0выключают (облака тогда ванильные, лучи — старые 14 шагов). - Освещение
fs_light→lit(HDR): пиксель неба (глубина мира 0, Оверворлд, без env-тумана) — это небо атмосферы + то, что мир нарисовал поверх ванильного неба (max(scene − vanilla_sky(view), 0): спрайты луны, звёзды; квадрат ванильного солнца вырезается вокруг направления солнца и заменяется круглым диском с гало); облака (глубина > 0, нет в G-буфере, y > 8, дальше 48 блоков) на закате/рассвете окрашиваются цветом солнца. Свет неба: ванильныйlightmap× подкраска цветом неба (зенит сверху, горизонт сбоку; яркость 1) × форма по нормали (вверх 1.1, вниз 0.66), только там, где есть небесный свет. Тени: ближний каскад — PCSS-lite (поиск блокировщиков 8 выборок, ширина полутени от расстояния до блокировщика, 12 выборок спирали Фогеля, повёрнутых IGN; сглаживает TAA), дальний — 3×3. Туман: цвет — небо над горизонтом в направлении пикселя (haze_colour) + воздушная перспектива1 − exp(−d/700)× 0.5. Далее прежнее: пиксель — вода, еслиwater_depth >= gи перед водой в мировой глубине ничего нет (d - wd <= 0.2%); тогда освещается дно по данным G-буфера (одна выборка тени, без AO), а в альфуlitпишется 0.5 (признак воды).max(небо·ambient.x, блоки·ambient.y)·AO + солнце·N·L·тень + точечные источники× albedo + эмиссия; затем ванильный туман заново, объёмные лучи (14 шагов, Henyey-Greenstein g=0.6), ореолы вокруг источников. Точечные источники — по тайлам 16×16 (tile_lists, буферtiles: на тайл 4 слова — начало/длина списка для поверхностей и для ореолов, потом индексы; переполнение → «все источники»). 4a. Композит водыfs_water(hd.water_composite,lit→lit_water, дальше вездеhd.lit_view(); на пикселях без воды копия). Волны — сумма 10 синусоид в мировых координатах (векторы — целые кратные 2π/256, скорости — целые циклы за 600 с: период 256 блоков и 10 минут, не «плывут» с камерой; мелкие волны гасятся по размеру пикселя против мерцания); нормаль = нормаль грани + градиент (только на горизонтальных гранях). Преломление: смещение луча относительно плоской воды, выборкаlitпо uv со сдвигом (отбраковка по глубине: не берутся банк/сущности перед водой); толщина = расстояние от поверхности до дна; поглощениеexp(-(1-цвет)·0.8·(t+0.3))- рассеяние цветом воды при свете «объёма» (лайтмап + солнце с тенью). Отражение: SSR по глубине G-буфера (24 шага, растущие,
джиттер IGN по кадру, 5 шагов бинарного уточнения, толщина
0.4+0.08·s), при промахе — небо/туман (sky_color,fog_color, тусклее под навесом). Волны: к 10 крупным добавлены 16 мелких (длины 0.35–3 блока, уклон до 0.05, гасятся по размеру пикселя) — густая мелкая рябь для искорок блика; блик GGX с a=0.07. Цвет воды светлее: биом × 0.85, поглощение 0.8, рассеяние 0.85 (ночью до 0.4). Небо отражения и туман — из атмосферы. Френель Шлика F0=0.02 (SSR пропускается при F<0.04: взгляд вниз), блик солнца GGX (a=0.05) × тень, туман поверх. Подводный вид иHORIZON_HD_WATER=0— композит и проход воды не работают,litкак раньше.
- рассеяние цветом воды при свете «объёма» (лайтмап + солнце с тенью). Отражение: SSR по глубине G-буфера (24 шага, растущие,
джиттер IGN по кадру, 5 шагов бинарного уточнения, толщина
- Пост-проход мода
lit→fx(effects.wgsl: взрывы, дым, вспышки, свечение; при HD его собственная «подсветка огнём» выключена — свет даёт HD). - TAA — после пост-прохода мода (
fx, либоlitбез него), до bloom: история пинг-понгом, в альфе истории — дистанция для отбраковки дизокклюзии; телепорт > 64 блоков сбрасывает историю. Если world/G-buffer depth совпадают, history UV = current UV + motion/render_size; вода, прозрачность и небо используют прежний depth/camera fallback. Джиттер добавляется кview_projectionтолько при HD (публичная матрица без джиттера); рука и HUD рисуются после tonemap и не дрожат. TAAU: если мир меньше окна (HORIZON_HD_SCALE=0.67), тот же проход (fs_taau, в таймингахhd taau) восстанавливает полное разрешение. История, bloom и tonemap — в разрешении окна. Для каждого пикселя выхода берётся 3×3 выборки мира вокруг (с учётом джиттера вtaa_params.zw), веса — полином, аппроксимирующий Lanczos-2, по расстоянию от центра пикселя выхода до выборки; variance clip в YCoCg по тем же 3×3; глубина — ближайшая из 2×2; история (Catmull-Rom, полное разрешение) вес 0.92, но ближе к ней там, где ближайшая выборка далеко от центра пикселя. Джиттер — Halton(2,3) наmax(16, ceil(8·ratio²))кадров (0.67 → 18, 0.5 → 32). При масштабе 1 работает прежнийfs_taaбез изменений. СHORIZON_HD_TAA=0и масштабом < 1 остаётся простой билинейный апскейл в tonemap. - Bloom (prefilter + 6 уровней: 13-tap вниз, tent вверх) и tonemap: до 0.8 как есть (ванильный вид), выше — мягкий roll-off по ярчайшему каналу; лёгкий грейд (+12% насыщенности, мягкая S-кривая).
Bind groups кэшируются и пересоздаются только при resize/смене источника (hd_bind_groups_last_frame() → 0 в
установившемся режиме).
Привязки fs_light (group 0)
| # | Что |
|---|---|
| 0 | HdFrame (uniform) |
| 1, 2 | цвет и глубина мира (ванильный проход) |
| 3, 4, 5 | albedo, нормаль, глубина G-буфера |
| 6 | источники света (storage, HdLight: pos+radius, color+intensity) |
| 7 | уровни света (небо, блоки) |
| 8, 9 | массив карт теней, comparison-сэмплер |
| 10, 11 | лайтмап, линейный сэмплер |
| 12 | тайлы источников (storage) |
| 13 | AO |
| 14 | глубина воды (water_depth) |
| 15 | текстура неба (sky_lut) |
| 16, 17 | объёмный туман (свет+пропускание) и длина его луча |
| 18, 19 | объёмные облака, карта теней облаков |
| 20 | длина луча каждого текселя облаков (cloud_dist_tex, для апсэмплинга по глубине) |
HdFrame (Rust и WGSL держать в синхроне!): inverse_view_proj, resolution (w, h, 1/w, 1/h), params (число
источников, экспозиция, bloom, счётчик кадров), cascades[2], sun (направление, сила), sun_color (rgb, ночь),
fog_color, fog, ambient (тень неба, блоки, плотность воздуха, AO), shadow (вкл, R0, R1, тайлов в строке), prev_view_proj, taa, taa_params, view_proj (с джиттером, для лучей воды), water (x: эффект идёт, y: секунды волн mod 600, zw: камера x, z mod 256), sky_color (w: есть небо), sky_sun (xyz: на само солнце, w: 1 — небо рисует атмосфера), sky_extra (x: конец ванильного тумана неба, z: пасмурность от дождя 0..0.85), sunrise (ванильная полоса восхода/заката — вычитается из мира), clouds (низ слоя относительно камеры, толщина, вкл, секунды ветра), cloud_cam (x, z камеры по модулю 65536, добавка покрытия от дождя, история облаков годна), cloud_dir / cloud_sun (откуда и каким цветом светят облаку: солнце до чуть ниже горизонта, ночью луна), vfog (туман вкл, высота камеры, утренний туман).
Композит воды (WATER, group 0): 0 HdFrame, 1 lit, 2 глубина G-буфера, 3 цвет воды, 4 нормаль воды, 5 глубина воды, 6–7 тени, 8 лайтмап, 9 сэмплер, 10 текстура неба. Проход неба (SKY_PASS, group 0): только HdFrame.
Контракты (цвет, глубина, альфа, время)
Полная таблица «проход: что читает → что пишет» — в начале gfx/hd/mod.rs, подробности — в начале файла каждого прохода.
- Цвет. От освещения до tonemap всё в «показанном» пространстве ванили (гамма, основные цвета Rec. 709), значения могут быть > 1
(
Rgba16Float); tonemap не трогает ≤ 0.8. Исключения: текстура неба — линейная яркость Rec. 709 (марш в Rec. 2020; показывают читатели черезsky_show); albedo G-буфера —Rgba8Unormв том же показанном пространстве. - Глубина. Камера: reversed-Z,
Depth32Float, 1 — близко, 0 — далеко/ничего (очистка в 0; глубина воды 0 — воды нет). Тени: обычная Z ортопроекции света (0 — у света, 1 — далеко), сравнение<=. - Альфа. albedo.a — эмиссия (свет добавляет
rgb·a·1.5);lit.a: 1 — обычный пиксель, 0.5 — вода (в rgb дно без воздуха), 0.6 — прозрачное перед водой;air.a— пропускание воздуха; облака и туман:a— пропускание (c·a + rgb); вода: цвет.a — свет блоков, нормаль.a — свет неба; история TAA: a — дистанция; AO: x — окклюзия, y — дистанция. - Координаты. Позиции относительно камеры; для узоров (волны, шум облаков, ветер) мировая координата передаётся по модулю периода
(256, 65536). Мировая проекция с джиттером;
prev_view_proj— с джиттером дляfs_taaи без дляfs_taau(выбираетHd::write). - Время. Три разных часа: счётчик кадров (
params.w, mod 64, вращение шума), время воды (water.y, секунды стенных часов mod 600), игровое время (clouds.w, секундыHdScene.secondsmod 300000, ветер облаков). Реплей, замораживающий игровое время, не останавливает воду. - Истории. Две цели TAA и две цели облаков принадлежат своим проходам, создаются в
resize, сбрасываются при resize, телепорте (> 64 блоков), выключенном TAA; одинHdобслуживает одну камеру, истории разных камер не смешиваются. - Размеры. world — масштабированная цель мира (G-буфер, AO ½, свет, вода, облака/туман ½), out — окно (TAA, bloom, tonemap).
- Bind groups кэшируются по видам, которые читают; пересоздаются при resize и смене входного вида; нет роста в установившемся кадре.
Как проверять
cd horizon && cargo build --profile fast -p horizon-client --features mods # без предупреждений
cargo fmt -p horizon-client
cargo test -p horizon-client --lib hd # naga-разбор всех HD-шейдеров + тест тайлов
cd ../airdef-mod && cargo horizon-mod build && cargo test # мод и его шейдер
WGSL-ошибки всплывают только в рантайме — поэтому каждый новый шейдер добавляй в список shaders() в тестах
gfx/hd/mod.rs: lighting_shader_parses (naga-разбор каждого итогового шейдера), every_wgsl_file_is_tested (каждый .wgsl
входит в проверенный шейдер), bindings_match_the_layouts (привязки WGSL = *_BINDINGS в Rust: номера, виды, размерности),
frame_layout_matches_the_prelude (поля и размер HdFrame в Rust и в прелюдии).
Игра офскрин (только одна копия за раз; данные в run-home/, мир Polygon):
cd run-home && HORIZON_HOME=$PWD HORIZON_NO_AUDIO=1 ../horizon/target/fast/horizon-ctl --socket $PWD/ctl.sock \
launch --world Polygon --offscreen --wait --game ../horizon/target/fast/horizon-client \
--resources "$(ls -d "$HOME/Library/Application Support/horizon-launcher/versions"/*/.local/horizon-client/26.3 | sort -V | tail -1)"
H="../horizon/target/fast/horizon-ctl --socket $PWD/ctl.sock"
$H command "time set 4500"; $H command "tp @s 40 76 40 135 25" # полигон с техникой; z ≥ 60 — море
# команды мода — через чат: $H key t tap; $H type "/airdef shader hd"; $H key return tap
$H key f1 tap; $H screenshot out.png; $H bench 4; $H quit
Тайминги проходов на GPU: HORIZON_SHADER_PACK_TIMING=1 (лог в run-home/ctl.log) или список F3.
Ориентиры (M5, 2560×1440): тени 0.6 + 2.85 мс (дальний каскад через кадр, ≈1.4 в среднем), G-буфер 0.8,
AO 2.9, освещение 5.2, TAA 2.7, bloom 1.1, tonemap 0.2 — кадр ≈ 15 мс GPU (замеры шумные).
TAAU (2560×1440, GPU avg кадр, сцена Polygon): scale 1.0 — 18.1 мс; 0.67 — 13.4 мс; 0.5 — 10.4 мс (освещение 6.1 → 2.7 → 1.6, AO 3.5 → 1.4 → 0.8, hd taau ≈ 3.5 мс на любом масштабе, дальний каскад теней 3.4 мс не зависит от масштаба).
Следующие цели по скорости: освещение (лучи 14 шагов на полном разрешении → полразрешения + апскейл) и AO.
Проверка мерцания: два скриншота с неподвижной камеры подряд и разница по пикселям (Pillow ImageChops.difference)
— на мире Hills с травой и деревьями; после исправлений ≈2% пикселей с разницей > 20/255 (дрожание TAA на пиксельных
текстурах и анимация воды), а при ошибке теней было 8.5%. Сравни с HORIZON_HD_TAA=0 / HORIZON_HD_AO=0 /
HORIZON_HD_SHADOW_FAST=0, чтобы найти виновника.
Графический полигон: python3 tools/hd_bench.py (--list — сцены; --only polygon_noon — одна сцена; --env HORIZON_HD_TAA=0 --out run-home-bench/out/taa-off — A/B; --baseline run-home-bench/out/before — diff ×6 и
пороги; --budget-ms 18 — GPU-бюджет). Сам копирует Polygon/Hills в run-home-bench/home, сокет
run-home-bench/c.sock, одна игра за раз; отчёт report.md с таблицей, проходами GPU и кадрами. Время и погода
заморожены (gamerule advance_time/advance_weather false); вода и листва идут по часам стены, поэтому кадры сравниваются
по статичным пикселям: 3 кадра подряд, пиксели с размахом > 16/255 (вода, облака, листва) не участвуют в сравнении с базой
и в мерцании (маска <сцена>_motion.png); --no-freeze — живой замер производительности; --help — все пороги.
Отладка: временно выводи промежуточные величины цветом в fs_light (так нашли пустую карту теней и неверное
направление солнца). Помни: солнце в мире идёт по оси X ((-sin a, cos a, 0)), а Globals.sun неба — в своей
плоскости YZ.
Статус
| Этап | Состояние | Коммит |
|---|---|---|
1. G-буфер, точечный свет мода в HDR, bloom, тон-кривая; API set-lights/set-lighting |
готово | 0a56f56 (форк) |
| 2. Тени солнца/луны, переосвещение мира, лучи, ореолы, туман, грейд | готово | патч 12 |
| Кэш bind groups, тени только от тенеотбрасывателей (#12, #13) | готово | патч 13 |
| Тайловые списки источников (#10) | готово | патч 14 |
| Тайминги HD-проходов (#9), GTAO | готово | патч 15, ed2db85 |
Отсечение взрывов в effects.wgsl (#11), порядок дыма (#16), метрика перекрытия свечения (#14) |
готово | 67a2a52, ed2db85 |
Тени: только грани к солнцу, без «закопанных» секций, дальний каскад через кадр (HORIZON_HD_SHADOW_FAST=0 — по-старому) |
готово | патч 16 |
TAA: джиттер Halton(2,3) ×16, перепроекция по глубине и сдвигу камеры, variance clip в YCoCg, история Catmull-Rom 0.9, лёгкий шарп в tonemap (HORIZON_HD_TAA=0 — выкл) |
готово | патч 16 |
TAAU: мир в HORIZON_HD_SCALE (0.25–1, по умолчанию 1 = как раньше), восстановление в TAA-проходе fs_taau; без LOD-bias текстур (не доступен) |
готово | патч 18 |
| Исправлено мерцание теней у игрока: ближний каскад терял секции в кадры, когда дальний держится; держимая матрица дальнего каскада сдвигается на шаг камеры; у растений (наклонные квады) нормаль «вверх», а не к камере; TAA держит 94% истории в покое | готово | патч 18 |
Вода: проход fs_water, преломление, SSR, Френель, блик (HORIZON_HD_WATER=0 — выкл) |
готово | патч 19 (+20: без ванильной текстуры на поверхности, цвет темнее и глубже) |
| Вода: светлее, густая мелкая рябь (26 волн), блик a=0.07 (мерцание на реке ≈1.4% пикселей >20/255) | готово | патч 21 |
| Небо и атмосфера (Рэлей/Ми/озон, текстура неба, воздушная перспектива, цвет солнца из пропускания, голубоватый ambient по нормали, PCSS-lite ближнего каскада, диск солнца, цвет облаков на закате; порт констант и Chapman из Photon, MIT) | готово | патч 22 |
Закат как у Photon: атмосфера в Rec. 2020 (коэффициенты и матрицы Photon), фаза Клейна-Нишины, тинт и усиление солнца на закате/рассвете/синем часе (get_sun_tint), насыщенность неба у низкого солнца |
готово | патч 23 |
| Объёмные облака (рэймарч, мультирассеяние Photon, тени на землю, TAA-история, ванильные скрыты) | готово | патч 24 |
| Объёмный туман (лучи через тени каскадов, высотная дымка, утренний туман, полразрешения) | готово | патч 25 |
API моделей по хэндлам (#30, уровень API 11: model-id, model-generation, set-models-by-id) |
готово | патч 17 |
#41: пасмурность неба и сила солнца берутся из мирового уровня дождя (Sky.rain, ставит apply_weather), а не из наличия геометрии осадков |
готово | в рабочем дереве форка |
#38: TAA без апскейла ищет историю по матрице прошлого кадра с джиттером (HdScene.prev_view_proj_jittered); fs_taau по-прежнему берёт матрицу без джиттера |
готово | в рабочем дереве форка |
#35: ванильные облака (HD-облака выключены) красятся закатным цветом в собственном проходе (hd::vanilla_cloud_tint → цвет в clouds::Input), эвристика по глубине G-буфера убрана из fs_light |
готово | в рабочем дереве форка |
#37: воздух (ванильный туман, aerial, объёмный туман, лучи, ореолы) на воде считается до поверхности и кладётся один раз поверх готовой воды: fs_light пишет во вторую цель air (рассеяние + пропускание), fs_water применяет её после поглощения |
готово | в рабочем дереве форка |
#36: ванильная вода не рисует цвет в HD (fs_translucent при Globals.hd_flags.x, глубину оставляет); прозрачное перед водой (d ближе wd) не отключает HD-воду: вода считается на освещённом дне и добавляется к сцене с весом 0.7; преломление не берёт тексели с таким прозрачным (textureGather по альфе) |
готово (проверены стекло; частицы и scale 0.67 не проверялись) | в рабочем дереве форка |
#45: gfx/hd.rs разделён на модуль gfx/hd/ (по файлу на проход: shadows, gbuffer, ao, sky, volumes, lighting+tiles, water, taa, bloom, tonemap, frame), WGSL вынесен в .wgsl (include_str!, общая прелюдия), у каждого прохода своя структура ресурсов/пайплайнов; контракты цвета, глубины, альф и времени записаны в hd/mod.rs и в начале файлов проходов; тесты привязок и размера HdFrame. Вид не менялся (сверка tools/hd_bench.py) |
готово | в рабочем дереве форка |
#39: веер восхода vanilla_sunrise_weight отсекается до цикла по 16 треугольникам выше горизонта (знак x стороны солнца и наклон ≤ α/3; проверено перебором 200 тыс. лучей без пропусков); hd lighting на рассвете 5.3 → 3.3 мс (M-серия, 2560×1440) |
готово | в рабочем дереве форка |
| Облака в воде, перистые, туман по рельефу, лучи, тень облаков при низком солнце, дальний дым (768 блоков) | готово, в рабочем дереве форка; тайминги не сняты чисто | не закоммичено |
| Материалы и блики (план 5): G-буфер levels.zw = шероховатость, металличность; GGX-блик солнца с Френелем и отражение неба (sky_along/atmosphere_sky) в shade_surface; мокрые поверхности и лужи в дождь (HdFrame.surface); HORIZON_HD_SPECULAR=0 — выкл. | готово | в рабочем дереве форка |
| Ветер (план 7): листва и растения качаются в вершинной стадии террейна (Globals.hd_wind, hd_materials), те же смещения в тенях; HORIZON_HD_WIND=0 — выкл. | готово | в рабочем дереве форка |
| #67: облака марш идут до ближайшей поверхности (fs_cloud читает глубину мира, вторая цель — длина луча, история отбрасывается при другой длине); fs_light кладёт облака на все пиксели (cloud_at: 4 текселя с весом по близости длины луча), под туман и над поверхностью, для воды — в air; луч внутри слоя вдоль горизонта больше не пропускается | готово; проверено только пустой полигон (нет гор/техники в слое, снимки scratchpad/c*.png), бюджет GPU на Mac не мерился | в рабочем дереве форка |
| #68: шум облаков по Y — от низа слоя, а не от камеры; шкалы тумана 170→128, дыма 22→32, луж 0.45/1.3→0.5/1.25 делят период обёртки 65536/256; тест noise_scales_divide_the_camera_wrap | готово | в рабочем дереве форка |
| #70: Game::sky_darken — всегда реальное время мира (ИИ, спавн, блоки, статистика); shown_sky_darken (время реплея) — только тени сущностей и свет глаза; небо/луна/цвета остаются по показанному времени | готово (собрано, игровой проверки день/ночь реплея нет) | в рабочем дереве форка |
| Объёмный дым и следы ракет (#54, п. 8 плана): API уровня 14 render.set-smoke, сетка дыма 128×64×128 по 3 блока (gfx/hd/smoke.rs, smoke_grid.wgsl), марш в проходе тумана, «туннель» в тумане и облаках, HORIZON_HD_SMOKE=0; мод: airdef-mod/src/smoke.rs (следы дронов и ракет, колонны над пожарами, дым взрывов) | готово, в рабочем дереве форка; вид не настраивался (см. «Дым и следы») | не закоммичено |
| Цветные тени (план 6): ближний каскад без листвы в глубине + карта цвета 1024² (Rgba16Float, shadow_tint_pass): стекло/лёд умножают свет на цвет, листва пропускает ×0.55 зелёного; в альфе расстояние до источника света (min-blend); fs_light берёт 8 отсчётов (shadow_tint); HORIZON_HD_COLOR_SHADOW=0 — выкл. | готово; радуга от цветного стекла видна в игре, листва — слабым зеленоватым пятном; без A/B с выключенным | в рабочем дереве форка |
| Рябь от дождя на воде (остаток плана 7): кольца в сетке 1.6 блока (ripple_gradient, water.wgsl), уровень дождя в sky_extra.w; HORIZON_HD_RAIN_RIPPLES=0 — выкл. | готово; в игре едва различимы дуги в бликах (частицы дождя закрывают), не настраивалось | в рабочем дереве форка |
| Motion blur и DoF кинокамеры (план 9): gfx/hd/film.rs + film.wgsl, проход после TAA, только при film_quality(); HORIZON_HD_MOTION_BLUR=0, HORIZON_HD_DOF=0, HORIZON_HD_DOF_FOCUS, HORIZON_HD_DOF_STRENGTH, HORIZON_HD_FILM_QUALITY=1 (принудительно, для проб) | DoF виден в игре (до правки стоил 14–17 мс); motion blur в игре не подтверждён; правка стоимости DoF (планарная глубина, 24 отсчёта, радиус 0.7%) собрана и прошла тесты, но глазами не проверена | в рабочем дереве форка |
План (по порядку) и ТЗ
Каждый пункт — самостоятельная задача. Перед началом прочитай gfx/hd/mod.rs (контракты) и файл нужного прохода.
- TAAU — временной апскейлер (поверх TAA) — СДЕЛАНО (патч 18, см. статус; масштаб —
HORIZON_HD_SCALE, переопределяет «масштаб разрешения» настроек Horizon только для HD; поля вHdSettingsнет). Осталось: LOD-bias текстур террейна, MetalFX. Исходное ТЗ: Мир и G-буфер рендерятся вrender_scale(например 0.67), TAA-проход восстанавливает полное разрешение: история в полном разрешении, текущий кадр семплируется с учётом джиттера (Catmull-Rom/Lanczos-веса по субпиксельной позиции), клиппинг окрестности как в TAA. Настройка масштаба (HORIZON_HD_SCALEили поле вHdSettings). Цель: при 0.67 картинка близка к нативной, FPS заметно выше. #124 развивает этот путь в общий temporal backend:c962cd472уже добавляет dense camera/static motion в G-buffer и использует его в TAA/TAAU. DLSS SR технически возможен черезdlss_wgpu 6.0.0(wgpu30, Vulkan, отдельный NGX SDK), но требует previous transforms, linear-HDR границы и platform build fixes; это не готовая поддержка. MetalFX — отдельный Metal backend через тот же контракт, FG отдельно. - Вода — СДЕЛАНО (патч 19, см. «Вода» ниже). Исходное ТЗ: отдельный G-буфер-путь для воды (маска + нормаль с анимированными волнами), SSR (трассировка по
глубине, откат на небо/туман), преломление (смещение по нормали), затухание по глубине воды (цвет биома),
блики солнца (GGX-подобный), френель. Подводный вид не ломать (
frame.underwater). - Небо и атмосфера — СДЕЛАНО (патч 22, см. «Небо и свет» ниже). Исходное ТЗ: рассеяние Рэлея/Ми (предвычисленная LUT пропускания и небесного цвета в маленькой
текстуре), солнце/луна дисками, звёзды из ванили; туман и лучи берут цвет из той же модели. Учесть дождь
(
weather), закат, Незер/Энд (там — ванильное небо). - Объёмные облака — СДЕЛАНО (патч 24, см. «Закат, облака и туман»). Перистые и облака в воде сделаны (см. «Облака, перистые, туман по рельефу»).
- Блики и материалы — СДЕЛАНО (см. «Материалы, мокрое, ветер»). Исходное ТЗ: specular солнца для металла техники (маска по оверлею/альбедо или атрибут модели из мода), мокрые поверхности и лужи в дождь.
- PCSS и цветные тени через стекло/листву — СДЕЛАНО (см. «Цветные тени, рябь, кино-пост»). Не сделано: цвет в лучах тумана и в тени под водой, цветная тень дальнего каскада.
- Ветер и рябь от дождя — СДЕЛАНО. Исходное ТЗ: листвы/травы (вершинный шейдер террейна по типу блока), рябь от дождя.
- Объёмный дым пожаров и взрывов, принимающий свет и тени (сейчас дым — в
effects.wgsl, без теней). - Motion blur, DoF для кинокамеры — СДЕЛАНО в коде (см. ниже), проверено частично.
- RT-пресет:
wgpuEXPERIMENTAL_RAY_QUERY(Vulkan/DX12/Metal M3+), BLAS по секциям, ray-query тени и отражения, затем GI. Отдельный пресет, HD остаётся.
Вода (патч 19): что знать преемнику
- Тайминги проходов:
hd water(геометрия воды; на Metal его метка часто 0 — работа учитывается в соседних проходах) иhd water composite. Замеры 2560×1440, Hills, река на ~40% экрана, M5 (шумно: у меня GPU делят браузеры): композит 0.8–2 мс,hd lightingна 2–3 мс выше, чем без воды (теперь освещается дно под водой — раньше эти пиксели были ванильными); всего вода ≈ +3 мс при большой воде на экране, около 0.5–1 мс при малой. Цель «≤ 2 мс» при полной реке не достигнута. Идеи: отражения в полразрешения (с учётом маски воды на границе), SSR только при F>0.04 уже есть, ужать освещение дна (без точечных источников на глубине > N блоков). - Вода находится по слоям спрайтов. LOD-тайлы воды (
lod/mesh.rs) идут тем же путём, если их слой — water_still; вода из resource pack с другим именем спрайта не распознается (останется ванильной).water_overlay(боковые грани у стекла) не трогаем. - Стекло/частицы/лёд перед водой: проверка
d - wdвозвращает ванильный вид пикселя (вода под стеклом без эффекта). - Известные ограничения: отражается только то, что есть на экране (и только непрозрачное: листва — да, вода/стекло — нет); дождь на воде, пена у берега, подводная каустика не сделаны; ночью вода слегка ярче неба (нужно привязать к модели неба — п. 4 плана «Небо»); отражение солнечного диска — только блик GGX (без диска в SSR). Проверка мерцания на реке: разница двух скриншотов ≈1.6% пикселей >20/255 (норма ~2%).
Небо и свет (патч 22): что знать преемнику
- Тайминги (M5, 2560×1440, Hills, река в кадре):
hd sky≈ 2.4–4.7 мс по таймстемпам, но кадр не вырос (GPU avg 18.4 мс против 18.1) — на Metal метка крошечного прохода ловит ожидание соседних; с кэшем по высоте солнца в покое проход не выполняется. Освещение 4.0–5.1 мс (PCSS-lite и небо почти не добавили), композит воды 0.6–2.4, TAA 2.6–2.9. - Яркость: небо — физическая радиация → сжатие по максимальному каналу (
SKY_EXPOSURE0.85) → гамма 1/1.9 → насыщенность 1.2 (+0.45 у низкого солнца). Весь конвейер в гамма-пространстве, поэтому цвета неба и подкраски света — «показанные» значения; подкраска света неба имеет яркость 1 и не меняет экспозицию (яркость задаёт ванильный лайтмап ×ambient.x= 0.62 днём, 0.95 ночью). - Цвет солнца (
light_of): пропускание атмосферы вдоль солнца (hd_sky::sun_transmittance), нормировано на 1, степень 0.6, × (1, 0.97, 0.88). Луна — холодная, как раньше. - Дождь:
sky_extra.zсмешивает небо/дымку с ванильным градиентом (серым от ванили); Нижний мир/Энд/подводное —sky_sun.w = 0, всё как раньше (ванильное небо и туман). - Облака — по-прежнему ванильные блоки; объёмные облака — п. 4 плана. Окраска облаков цветом закатного солнца — эвристика по маске (глубина есть, G-буфера нет, высота и дальность), может задеть далёкие частицы.
- Идеи дальше: многократное рассеяние по Hillaire вместо плоского члена, тени облаков, диск луны с фазой, звёзды/галактика по Photon (
sky.glsl), розовый оттенок заката (get_sun_tintу Photon).
Правила
- Код Photon (MIT) переносить можно только с атрибуцией (см. «Что это»); код других шейдерпаков — нельзя.
- Ничего не ломать в
off/on; HD выключен — код HD не работает вовсе (hd = None). - Без потери качества ради скорости без договорённости (шаги, разрешение, число эффектов).
- Каждый новый WGSL — в naga-тест. Каждый новый проход — в тайминги (
PassTiming). - Комментарии — короткие предложения о том «зачем»; в
gfx.rsпрефиксAirDef fork:. - Коммиты: в форке, затем патчи; подпись
Co-Authored-By: Claude <модель> <noreply@anthropic.com>. - Пуш в Gitea с машины без DNS:
ssh://git@100.92.6.40:2222/chexit/airdef.git(ключ~/Documents/claude-keys/deploy). - Обнови этот документ (статус + что изменилось в конвейере) в том же коммите, что и код.
Тюнинг после проверки на полигоне (патч 22)
Голубая заливка неба по умолчанию делала белый бетон «снегом»: оттенок неба в заливке ослаблен до 0.4
(sky_light_tint, в fs_light), заливка днём ambient.x = 0.5 (+0.45 ночью), солнце 0.7 и теплее
(light_of: множитель (1, 0.93, 0.78)). Проверять баланс на Polygon в полдень (time set 6000) — белое должно
оставаться белым с тёплым оттенком.
Закат, облака и туман (патчи 23–25): что знать преемнику
- Закат (23). Небо марширует в Rec. 2020 (коэффициенты Photon сдвинуты матрицей
rec709_to_rec2020, результат возвращается в 709): это убрало жёлто-зелёный горизонт. Добавлены фаза Клейна-Нишины для Ми и деполяризованная Рэлея,sun_light_tint(розоватый тинт и подъём яркости на закате, фиолетовый на синем часе, изget_sun_tint), сильнее насыщенность у низкого солнца (sky_show). Цвет прямого света (light_of) умножен на тот же тинт. Сверка: 12000/12500/13000 (запад) и 23000/23500 (восток) — оранжевый/розовый/пурпурный; полдень Polygon (6000) прежний. - Облака (24). Слой 192.33…264.33 блока (
CLOUD_BASE/CLOUD_THICKNESS), ветер 3 блока/с (CLOUD_WIND), шум — текстуры, сгенерированные на CPU при создании HD (hd_noise.rs: 2D 256² покрытие/форма, 3D 64³ Worley). Плотность — поclouds_cumulus_densityPhoton (порог покрытия × форма, «яйцо» по высоте, эрозия Worley, резкий верх), рассеяние —clouds_cumulus_scattering(октавы, пудра, фазы). Цвет света —cloud_light(Rust): пропускание атмосферы на высоте солнца; солнце светит облакам до −0.2 по высоте, дальше луна. Воздушная перспективаexp(−d/3600)к цвету дымки. История сбрасывается при resize, телепорте и без TAA. Опция ванильных облаков «выкл» →frame.cloudsпуст → нет ни облаков, ни теней. Тень:cloud_shadowвfs_light(карта оптической толщины; гаснет при солнце ниже ~0.1–0.25 по высоте). - Туман (25).
fs_fog: 18 шагов, плотностьfog_sigma(высотная дымка от y=62, масштаб 42; утренний туман — пока солнце на «утренней» стороне и низко; пятна из 3D-шума; дождь; затухание ниже y≈28–58, чтобы не светить в пещерах), свет — Photon-смесь двух HG, «вечернее свечение» ×1…3. Старые 14 шагов лучей остаются приHORIZON_HD_VOLFOG=0; ореолы вокруг источников мода — всегда. - Тайминги. Метки Metal (
HORIZON_SHADER_PACK_TIMING=1, Hills, 2560×1440):hd clouds≈ 6 мс (завышено: метка ловит ожидание соседних проходов),hd cloud shadows0.2–3 (шум),hd fog≈ 1.3. По среднему кадру GPU «оба выкл → оба вкл»: +0.2…3.7 мс (замеры шумные: рядом шла чужая игра на том же GPU). Как удешевить: у облаков — длина шага (14) и потолок шагов (26) вfs_cloud, 3 шага света, 4 октавы, облака в четверть разрешения; у тумана —FOG_STEPS. - Разработка шейдеров.
HORIZON_HD_SHADER_DIR=<папка>: каждый HD-шейдер читается из<папка>/<метка>.wgsl(записывается из встроенного, если файла нет; это тело без прелюдии, она добавляется всегда) — правки видны после перезапуска игры без пересборки; готовое переносится вgfx/hd/*.wgslвручную. - Слабые места (после доработки облаков и тумана). Облака не закрывают далёкую землю выше слоя (только небо); карта теней облаков теперь центрируется на проекции камеры вдоль солнца (работает и при низком солнце, гаснет плавно при высоте солнца 0.03..0.2), но текстель карты ~9 блоков; перистые облака и панорама — без тени на землю; туман знает рельеф только в 512 блоках вокруг камеры (карта 8-блоковых клеток, деревья считаются минимумом из 4 проб); история облаков на быстром повороте держится сильнее (порог расхождения шире), но на краю экрана свежие пиксели остаются шумными; тайминги Metal в этой работе не сняты чисто (на машине шли другие игры, load average 65).
Облака, перистые, туман по рельефу, дальний дым (доработка)
- Облака в отражениях. Проход
hd cloud sky(fs_cloud_skyвcloud.wgsl): панорама облаков 256×64 (Rgba16Float, азимут от +x по кругу × v = √(высота/(π/2))), марш кучевых «lite» (14 шагов, 2 шага света, без тоннелей дыма) + перистые, смешивается с прошлым кадром (0.8, без репроекции: история в мировых направлениях). Пинг-понг идёт тем жеflip, что у облаков. Читает еёsky_with_clouds(pano_funcs.wgsl, маркер//PANO<n>): привязка 21 вfs_light(отражение неба на мокром/металле) и 12 в композите воды (sky_along). Групп воды теперь две (поflip). - Перистые.
cirrus()вcloud.wgsl: плоский слой на 900 блоков (CIRRUS_ABOVEнад основанием кучевых), 2D-шум (каналыnoise2, вытянут вдоль ветра, масштабы делят 65536), без марша света: прямой свет = цвет солнца облаков × (0.5 + 3·HG(0.7)) плюс небо; на закате цвет берётся изcloud_sun; дымка по расстоянию, гаснет у горизонта; дождь повышает покрытие (cloud_cam.z) и затемняет небесную долю. Складывается за кучевыми (behind), только на небо. У кучевых при дожде темнее небесный свет. - Туман по рельефу.
gfx/hd/ground.rs: карта 64×64 клетки по 8 блоков (Rg16Float: высота земли, глубина котловины = среднее в ±48 блоков минус высота), тороидальная (индекс = клетка mod 64, sampler Repeat, uv = мир/512). CPU обновляет по 256 клеток за кадр (4 пробыworld_surface, минимум), при телепорте — всю карту; размытие и загрузка — раз в 4 кадра при изменениях. Данные:Frame::hd_ground(замыкание,game.rs).fog_sigma: утренний туман льнёт к земле и заполняет котловину до 75% глубины, немного дневной дымки в долинах, затухание ниже земли (g−14..g−3) вместо абсолютного y 28–58. Привязка 9 тумана. - Лучи.
FOG_SUN1.1→1.35 (у дыма осталось 1.1) и плотность ×(1…1.9) при низком солнце (low_sun); в лесу Hills утром лучи заметны. - Дальний дым (#54). Второй каскад сетки дыма: те же 128×64×128 клеток по 12 блоков (
FAR_CELL): 768 блоков по горизонтали (192 вниз, 576 вверх), трубки не тоньше 7.2 блока, до 6144 сегментов (FAR_MAX_SEGMENTS); ближний — как раньше (192 блока, 3072). Проходhd smoke grid far; привязка 10 тумана;HdFrame.smoke_far,smoke.w= «дальняя сетка не пуста». Вfog.wgslдва марша (march_smoke(..., far)): ближний до 200 блоков, дальний (шаг 12, до 72 шагов) до 800; вес ближнего —smoke_near_cover(плавно 1→0 на последних 8 клетках), дальнего — остаток: сумма 1, шва нет; дальний гаснет на последних 6 клетках. Игра (mod_models.rs: mod_smoke) шлёт сегменты до 900 блоков и до 8192. Не проверено глазами: полёт дронов на 700 блоков (/airdef wave shahed 8 700за время ожидания трасс не показал).
Заметки по исправлениям #35–#41 (преемнику)
- Цели света.
fs_lightпишет две цели:litиair(Rgba16Float). Альфаlit: 1 — обычный пиксель, 0.5 — вода (вlitдно без воздуха, вairрассеяние воздуха до поверхности и пропускание в альфе), 0.6 — прозрачное перед водой (вlitсцена как нарисована, вair.rgbосвещённое дно). Привязка 11 композита воды —air. - Ванильная вода в HD. Пока
hd.water_active(),fs_translucentне рисует цвет воды (альфа 0), но пишет её глубину; флаг —Globals.hd_flags.x(поле добавлено в Rust-Globals,terrain.wgsl,entity.wgsl; пишется послеhd.writeи вhd.main_globals). Вода без дна (g = 0) считается водой толщиной 48. - Не сделано. Полупрозрачное перед водой берёт вес 0.7 на «вклад воды» (приближение: настоящий порядок «вода → прозрачное» потребовал бы разделить проход сцены). Частицы перед водой,
HORIZON_HD_SCALE=0.67, поиск мерцания на рябь после этих правок не прогонялись. HORIZON_HD_TAA=0. На Polygon в полдень (tp @s 40 76 40 135 15) HD с выключенным TAA выглядит так же (дымка, небо, тени на месте), GPU 16.5 мс против 18.1 мс с TAA (минус ≈ проход TAA); потери вида и падения до 4.8 мс не воспроизвелись. В кодеHORIZON_HD_TAAчитают толькоtaa_enabled()(джиттер, проходtaa, история), остальные проходы от него не зависят. Если проявится снова — проверь, что до шота прошёл/airdef shader hd(при неготовой команде кадр без HD ~5 мс).
Материалы, мокрое, ветер: что знать преемнику
- Класс спрайта.
gfx/hd/materials.rsпо имени спрайта (minecraft:block/<имя>) даёт класс 0..8 (обычный, листва, растение, нижняя/верхняя половина двойного растения, металл, гладкое, глянец, дерево). Таблица — по 4 бита на слой, первые 2048 слоёв (Globals.hd_materials, 64 ×vec4<u32>, считается вgame.rsи кладётся вRenderer); спрайты модов дальше 2048 — обычные. ЧислаMAT_*вterrain.wgslдержать в синхроне. Добавить блок в класс — список имён вclassify. - G-буфер.
levels.zw(было не занято) = шероховатость и металличность (material_paramsвterrain.wgsl; для сущностей — по оверлею вfs_entity_gbuffer: обычные 0.9/0; модели мода41— крашеный металл 0.42/0.3 (по умолчанию для всех моделей мода,game/mod_models.rs),42— голый металл 0.28/1 (мод пока не умеет выбирать),40— свечение 0.5/0). Вода своим проходом, без материала. - Блик. В
shade_surface(light.wgsl): GGX (α = rough², не ниже 0.03) × Смит × Френель Шлика, у шероховатых Френель почти не растёт (max(gloss, F0)); цвет — солнце × тень ×sky_reach; потолок 6. Отражение неба:atmosphere_sky(reflect)× Френель × gloss² × доступ к небесному свету × AO, без SSR на террейне. Металл сохраняет 35% диффузного (карты окружения нет). Дно под водой блеск не получает. - Мокро.
HdFrame.surface.x— влажность 0..1 (CPU,Hd::wetness): растёт за ~15 с дождя к уровню дождя мира, сохнет ~150 с. Под открытым небом (lm.x> 0.8) поверхности темнеют до ×0.7 (боковые грани слабее) и их шероховатость падает к 0.12; лужи — на плоских гранях вверх, по 2-октавному value-шуму в мировых координатах (wet_noise, период 256 блоков), с влажности ~0.3, альбедо ×0.6 и шероховатость 0.03, отражают небо. - Ветер.
terrain_vertexсмещает вершины листвы (±0.04 блока, непрерывно по мировой позиции, чтобы блоки не расходились) и растений (до 0.14 блока вверху спрайта; низ — поuv.y; двойные растения стыкуются на середине). Волны — целые циклы на 256 блоков и 600 секунд (как у воды). Данные ветра — вGlobals.hd_wind(сила 1 + 1.2·дождь, секунды, камера mod 256); они же в глобалах каскадов теней, поэтому смещение одно и то же во всех проходах (ванильный, G-буфер, тени, глубина совпадают). Вне HD сила 0 — вершинный шейдер не меняется. TAA не знает о движении листвы (малые амплитуды). - Переключатели.
HORIZON_HD_WIND=0,HORIZON_HD_SPECULAR=0(блик, отражения и мокрое).
Проверка материалов/мокрого/ветра (что видно и что нет)
- Увидено в игре (Hills,
run-home-bench/out/mat1,out/rain): ветер работает, листва и трава двигаются, артефактов швов нет; в дождь сцена заметно темнее (мокрое), небо серое. Не проверено глазами: блики на технике (в Polygon сцена бенча сейчас без техники, не сопоставима с базойbefore-mat), отдельные лужи (кадр дождя смотрит в небо), высыхание (out/rain/drying_30s.pngснят, не разобран), ночь. - Мерцание:
hills_river_noon9% без ветра (out/nowind) против 31% с ветром — это движение листвы вблизи камеры, не шум; амплитуда листвы снижена с 0.04 до 0.025 после этого замера (повторно не мерилось). - Тайминги не сняты: параллельно шли игры других агентов (числа 25–155 мс недействительны). Перед замером брать
run-home/.gpu-lock. - Известно:
shaders_translate_to_hlsl_for_directx_12падает наcloud.wgsl(не мой файл).
Камеры мода: полный вид и мониторы (#56, #51)
- Главный вид (
render.set-main-view, API 15): мир экрана рисуется из камеры мода (set-views) полным конвейером — HD, небо, облака, туман, объёмный дым, TAA, пост-проход мода, разрешение окна. Руку, контур блока и ванильный HUD не рисуем (game.rs:mod_main_view,show_hud). Так показываются повторы Full/Reel/моменты; чанки остаются вокруг игрока, слушатель — игрок. - Малые виды (мониторы,
gfx/mod_views.rs) остаются экономными: небо, твёрдая земля, сущности и вторым проходом пост-эффект мода с камерой этого вида (ModPost::write_to/run_view, режимparams[0].w = 0: пост сам освещает землю и рисует дым). Бюджет по площади картинки (post_budget: <4096 px без пост-прохода, до 150k px — 4 взрыва/12 огней, до 600k — 12/32, больше — всё). HD-освещения, облаков и TAA у малых видов нет. - Мод в повторе берёт взрывы из записи (
record::replay_blasts, чистая функция времени), время идёт по часам повтора (params[255].x— доля частичного тика, которую эффекты не используют при паузе/замедлении). - #51: на перехвате слои звука (
cinema.rs), свет-«удар» (белая вспышка в HD-освещении, радиус до 256), пик экспозиции/bloom (set-lighting), вeffects.wgslширокое гало, подсветка неба/облаков и яркое кольцо ударной волны на экране.
Прожекторы: направленный свет и объёмный луч (#60)
- API 16
render.set-spot-lights(list<spot-light>) -> bool: апекс, направление, цвет, интенсивность,range(до 1024), внутренний/внешний полуугол (рад),beam(видимость луча 0..1). До 32 от мода, рисуются ближайшие 16 (MAX_SPOTS). Возвращает, рисует ли HD (HORIZON_HD_SPOTS=0выключает), иначе мод держит свой запасной луч. Игра интерполирует по id между двумя вызовами (mod_models.rs: mod_spots) и переводит вHdSpot(gfx/hd/frame.rs): координаты камеры, косинусы углов. - Данные в шейдере:
HdFrame.spots(4 vec4 на источник: апекс+range, ось+cos внешнего, цвет+интенсивность, cos внутреннего+beam), число вvfog.w. Новых привязок нет. - Тайлы: заголовок тайла 5 слов (
TILE_WORDS), пятое: маска конусов, бит j стоит, если выпуклая оболочка конуса (апекс и квадрат вокруг дальнего круга,tiles.rs: spot_hull,cover_points) на экране накрывает тайл. Маска служит и поверхностям, и лучу. Индекс тайла в WGSL:* 5u. - Поверхности и цели:
spot_light(spot.wgsl, вставляется маркером//SPOTвassemble): конус с мягкой кромкой (smoothstep по косинусам),I/(d²+1), окно в последние 3 блока доrange, угол падения как у точечных; вshade_surface(земля, стены, дроны: они в G-буфере). Теней нет: свет обрывается поrange, который мод ставит по броску оси на землю (ground.world.probe, раз в 4 тика). - Луч:
spot_beams(spot.wgsl): пересечение луча взгляда с конусом (spot_span: квадратное уравнение конуса, обрезка по оси и range); внутри 8 шагов с джиттером IGN; яркое ядро, мягкая периферия; затухание1/(1+d/70); плотность воздухаmax(spot_air, 0.0009)(в чистом воздухе слабый луч, в дымке/тумане ярче и короче); фаза Henyey-Greenstein вперёд (на лампу ярче). Складывается (прозрачный), ограничен глубиной сцены: за стеной не продолжается; камера внутри конуса работает. Туман вкл: считается вfs_fog(spot_air = fog_sigma, длина до 1100 блоков, в небо тоже, не 192 как у тумана); туман выкл или нет атмосферы: тот же код вfs_lightс постоянной плотностьюambient.z. - Мод:
sim/lights.rs: голова прожектора шлёт одинSpotLight(Sam::spot_beam,lib.rs: spots; конус 0.03/0.075 рад, range = свободная длина оси + 3.5). Цель «освещена» (lit) только в угле 0.05 от оси и если она не дальше свободной длины (за горой бонуса нет). Старыеairdef:beamрисуются, только когда HD не рисует споты и эффекты включены (тоньше), при эффектах off луча нет. Точечный «шар» на цели остаётся лишь без спотов. В реплеях споты не шлются (реплей не пишет голову прожектора; #56/#58 отдельно). - Ограничения: тень рельефа вне оси (боковая часть конуса за гребнем светит), запасной луч непрозрачен (текстура модели без альфы), GPU-цена на массовой сцене не снята.
Кинорежиссёр и экспорт MP4 (#58, API 17)
- Качество фильма (
render.set-film-quality, включается и на время захвата): мир HD в полном размере окна (hd::scale_override→ 1.0), шаги облаков ×2 (шаг 7 блоков, 16–52 шага), шаги тумана ×2; флаг идёт вHdFrame.surface.z. Сменаset-main-viewid (склейка) сбрасывает историю TAA и облаков (Renderer::cut). - Захват (
render.capture-start/stop/status,game/film.rs,Renderer::capture_*): часы игры идут ровно1/fpsсекунды за кадр (тики, интерполяция,tickмода), кадр копируется с GPU через очередь из ≤3 буферов и уходит в поток-писатель →ffmpeg(libx264, иначе h264_videotoolbox; иначе PNG-последовательность). Звук: микшер OpenAL-подобный (audio.rs) работает офлайн (film_begin/film_mix), слышны только звуки мода, ориентация — по камере; дорожка вмуксивается AAC. После склейки рисуютсяwarmupкадров без записи, чтобы сошлись TAA и объёмы. Размер: offscreen-игра ресайзится, окно просится (request_inner_size). - Мод-HUD (
set-hud) теперь рисуется и приset-main-view(титры, леттербокс фильма); ванильный HUD по-прежнему скрыт. - Режиссура:
airdef-mod/src/film.rs(план по записи, варианты ракурсов, проверка рельефа по высотамGround::land_state, ускорение/замедление времени).
Проверенный жизненный цикл MP4 (4 ноября, pin 57f8b2853, очистка ошибки энкодера — 73b9d3070, API 29)
Реальный короткий экспорт на scratch HORIZON_HOME/мире (не на сохранениях игрока), Apple M5, Metal,
ffmpeg 9.0.1. /airdef replay export с записанного налёта (4 цели, 4 перехвата), 640×360:
- готовый файл: h264 + AAC 48 кГц stereo, длительность видео = длительность звука; звук не пустой
(mean −29.4 dB, max −18.0 dB); 2 с при 30 fps — 62 кадра,
sec=1— 32,noaudio— 33 кадра без звуковой дорожки,sec=0.01— 3 кадра; 1920×1080sec=1— 32 кадра. Число кадров в отчёте мода всегда совпадает сffprobe nb_frames: очередь не теряет кадры на остановке. - 2–3 кадра сверх
sec— это кадры, уже находившиеся в полёте на момент остановки (движок их дописывает); это не потеря хвоста. Отчёт и файл сходятся. - отмена во время записи (
/airdef replay stop): сообщение «Экспорт отменён», файла и процессов ffmpeg не остаётся; сразу после отмены новый экспорт проходит. - отказ SDK (
size=10000x10000): сообщение об ошибке, захват не остаётся, следующий экспорт проходит. - ошибка энкодера (подставной
HORIZON_FFMPEG, отвечающий на-encoders, но падающий при записи): «Экспорт не удался: … Broken pipe …: fake encoder failed: no such codec»; временных файлов и процесса нет, а последние строки лога теперь в самом тексте ошибки (сам лог удалён). Готовыйkeep.mp4прежнего экспорта с тем же именем остался байт-в-байт (sha256), как и беззвучныйkeepnone.mp4после неудачногоnoaudio-экспорта с тем же именем. Движок73b9d3070всегда пишет<имя>.video.mp4;<имя>.mp4появляется только после успешного mux/переименования. Контрольныйctrlна итоговой сборке73b9d3070— h264 + AAC, 33 кадра, временных файлов нет;keep(h264 + AAC, 32 кадра) иkeepnone(только h264, 32 кадра) проверялись на той же правке. - акустика в MP4: пары on/off одного повтора (кнопка мода) со статусами
Акустика … readyиЗахват … ready; в открытом поле send реверба нулевой по построению, в каменном дворе ON тише и глуше, а остаток после вычитания масштабированного сухого сигнала низкочастотный и декоррелированный (wet-путь). Цифры, статусы и ограничения — ACOUSTICS.md. - выход в заголовок во время экспорта (до исправления): игра перестаёт тикать на экране
заголовка, но продолжает кадры, поэтому захват писал заголовок; запрос
sec=10дал 3898 кадров (129.9 с), временныеvideo.mp4/audio.rawи ffmpeg оставались, а после возврата в тот же мир экспорт продолжался. Мод получает кадры и вне мира (Mod::frame), поэтомуSession::world_leftзавершает показ сExitCause::WorldChangeи однимCancelCapture: после исправления при выходе в заголовок ffmpeg и временные файлы исчезают, повторный вход и следующий экспорт проходят. Пропуск мира виден поgame.world-id = none; тик мода вне мира не вызывается.
Цветные тени, рябь, кино-пост: что знать преемнику
- Цветные тени. Ближний каскад (c = 0) рисуется
fs_shadow_near(листваMAT_LEAVESотброшена), потом проходhd shadow colour(Hd::shadow_tint_pass, цельShadows::tint, очистка в белое) рисует листву (shadow_tint_leaves, твёрдые квады,SHADOW_SOLID) и полупрозрачные квады секций (shadow_tint_glass, по отсортированным индексам; вода не рисуется) тем жеfs_shadow_tint: rgb умножаются (blendDst*src), альфа =(расстояние от света − 320)/160с blend Min — ближайший такой квад. Приёмник вshadow_tint(light.wgsl, привязка 22) окрашивается, если квад ближе к свету, чем он; 8 точек Vogel радиусом 0.4 блока размывают листву в «пятна». Дальний каскад по-прежнему рисует листву полностью (на границе 36 блоков шов: ближе листва светлее). Цвет не доходит до лучей тумана, воды и дальнего каскада. Цена:hd shadow colour0.25–0.35 мс (Mac, малая сцена). - Рябь.
ripple_gradientберёт часы воды (water.y, часы фильма): частоты — целые циклы на 600 с, сетка 160 клеток на 256 блоков, поэтому рисунок не прыгает на обороте. Гаснет по размеру пикселя, только под открытым небом (nt.a). Амплитуда 0.012 не подбиралась. - Кино-пост.
Hd::filmмеждуtaaиfinish(HdSource::Film), только приfilm_quality(); цели создаются при первом фильме. Motion blur — только от движения камеры (глубина +prev_view_proj, затвор 180°, мёртвая зона 1–2.5 px из-за джиттера; движущиеся объекты не размываются; нет после склейки/телепорта:taa.w). DoF — диск 24 отсчёта, фокус авто (гармоническое среднее 9 глубин у центра) илиHORIZON_HD_DOF_FOCUS. Детерминизм: единственное время в шейдере —water.y(часы фильма) для шума. Проверять на реальном фильме/airdef replay film;HORIZON_HD_FILM_QUALITY=1принудительно включает качество фильма для проб. DoF в игре был слишком сильным и дорогим; параметры уменьшены без повторного снимка. Motion blur на снимке (поворот 40° за 1.5 с) не разобран. - Из Photon (MIT) ничего нового не переносилось: THIRD_PARTY_PHOTON.md не менялся.