Files
airdef/docs/HD-RENDERER.md
T

48 KiB
Raw Blame History

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-конвейер: цели, проходы, WGSL-шейдеры (строками), тайлы света, AO, тени, bloom, tonemap, TAA horizon/crates/horizon-client/src/gfx/hd.rs
Объёмные облака (рэймарч слоя, тени облаков на землю) и объёмный туман (лучи света через тени каскадов), их цели и проходы; CPU-шум (2D, 3D Worley) horizon/crates/horizon-client/src/gfx/hd_volumes.rs, hd_noise.rs
Атмосфера: сборка текстуры неба (WGSL), чтение неба/дымки/тона света в шейдерах, цвет солнца (sun_transmittance) horizon/crates/horizon-client/src/gfx/hd_sky.rs (+ 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
Пост-проход мода (взрывы, дым, вспышки, свечение) и его прелюдия 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) 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.

  1. Небо (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. Каскады теней (2 шт., CASCADES = [40, 176] блоков полуразмера, SHADOW_SIZE = 2048, массив глубины из 2 слоёв). Источник — солнце днём, луна ночью (light_of). Матрицы cascades() привязаны к текселям (тени не «ползут»). Во время прохода буфер глобалов мира подменяется на глобалы каскада (copy-трюк) и потом возвращается. Рисуются секции террейна в коробке дальнего каскада (hd_shadow_visible) и сущности-тенеотбрасыватели (shadow_casters: без оверлеев 4/9/10/40 — 40 это светящиеся модели мода).
  3. G-буфер (свой depth): albedo Rgba8Unorm (a = эмиссия), нормаль Rgba16Float (мировая, из dpdx/dpdy), уровни света Rgba8Unorm (x = небо, y = блоки — сырые координаты лайтмапа /240). Террейн + дальние LOD-тайлы + сущности. Полупрозрачное в G-буфер не попадает (идёт ванильным путём); вода — отдельный путь, см. п. 3 и «Вода».
  4. Вода (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 шагов).
  5. Освещение 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 как раньше.
  6. Пост-проход мода lit → fx (effects.wgsl: взрывы, дым, вспышки, свечение; при HD его собственная «подсветка огнём» выключена — свет даёт HD).
  7. TAA — после пост-прохода мода (fx, либо lit без него), до bloom: история пинг-понгом, в альфе истории — дистанция для отбраковки дизокклюзии; телепорт > 64 блоков сбрасывает историю. Джиттер добавляется к 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.
  8. 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 объёмные облака, карта теней облаков

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.

Как проверять

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-ошибки в hd.rs всплывают только в рантайме — поэтому каждый новый шейдер добавляй в naga-тест lighting_shader_parses.

Игра офскрин (только одна копия за раз; данные в 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 и кадрами.

Отладка: временно выводи промежуточные величины цветом в 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 не проверялись) в рабочем дереве форка
#39: веер восхода vanilla_sunrise_weight отсекается до цикла по 16 треугольникам выше горизонта (знак x стороны солнца и наклон ≤ α/3; проверено перебором 200 тыс. лучей без пропусков); hd lighting на рассвете 5.3 → 3.3 мс (M-серия, 2560×1440) готово в рабочем дереве форка

План (по порядку) и ТЗ

Каждый пункт — самостоятельная задача. Перед началом прочитай hd.rs целиком.

  1. 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 заметно выше. Альтернатива на Mac — MetalFX через wgpu-hal (нативный код, только Metal) — отдельной опцией потом. DLSS нереален для нашей цели (только NVIDIA через Streamline, нет в wgpu).
  2. Вода — СДЕЛАНО (патч 19, см. «Вода» ниже). Исходное ТЗ: отдельный G-буфер-путь для воды (маска + нормаль с анимированными волнами), SSR (трассировка по глубине, откат на небо/туман), преломление (смещение по нормали), затухание по глубине воды (цвет биома), блики солнца (GGX-подобный), френель. Подводный вид не ломать (frame.underwater).
  3. Небо и атмосфера — СДЕЛАНО (патч 22, см. «Небо и свет» ниже). Исходное ТЗ: рассеяние Рэлея/Ми (предвычисленная LUT пропускания и небесного цвета в маленькой текстуре), солнце/луна дисками, звёзды из ванили; туман и лучи берут цвет из той же модели. Учесть дождь (weather), закат, Незер/Энд (там — ванильное небо).
  4. Объёмные облака — СДЕЛАНО (патч 24, см. «Закат, облака и туман»). Осталось: перистые/высококучевые слои Photon, облака в отражении воды.
  5. Блики и материалы: specular солнца для металла техники (маска по оверлею/альбедо или атрибут модели из мода), мокрые поверхности и лужи в дождь.
  6. PCSS (мягкость тени по расстоянию до тенеотбрасывателя) и цветные тени через стекло/листву.
  7. Ветер листвы/травы (вершинный шейдер террейна по типу блока), рябь от дождя.
  8. Объёмный дым пожаров и взрывов, принимающий свет и тени (сейчас дым — в effects.wgsl, без теней).
  9. Опционально: motion blur, DoF (для кинокамеры повторов).
  10. RT-пресет: wgpu EXPERIMENTAL_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_EXPOSURE 0.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_density Photon (порог покрытия × форма, «яйцо» по высоте, эрозия 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 shadows 0.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 (записывается из встроенного, если файла нет) — правки видны после перезапуска игры без пересборки; готовое переносится в константы вручную.
  • Слабые места. Облака не отражаются в воде (SSR берёт небо без облаков); не закрывают далёкую землю выше слоя (только небо); карта теней облаков ±1200 блоков и почти не работает при солнце ниже ~10°; туман не знает рельефа (только высоту над y=62); нет перистых облаков; на быстром повороте камеры история облаков отбрасывается (кратковременный шум).

Заметки по исправлениям #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 мс).