Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
26 KiB
HD-рендер AirDef: устройство, статус, план
Документ для передачи работы (человеку или агенту). Ведётся по ходу разработки: каждый этап, попавший в
main, отмечается здесь вместе с коммитом. Если что-то в коде расходится с этим текстом — прав код, а текст
надо поправить.
Что это
HD — третий пресет графики мода (/airdef shader off|on|hd): отложенное освещение в стиле шейдерпаков
(BSL/Complementary-класс), но написанное с нуля внутри нашего форка Horizon (Minecraft 26.3 на Rust/wgpu).
Код шейдерпаков (BSL, Complementary, Photon, Iris/OptiFine) не копируем: их лицензии запрещают перенос, а в
Horizon действует правило clean-room. Пишем по знанию техник (статьи, общеизвестные алгоритмы).
Пресеты должны оставаться: off (ваниль), on (пост-эффекты мода), hd (этот рендер), позже rt (аппаратная
трассировка, отдельно).
Где код
| Что | Где |
|---|---|
| Весь HD-конвейер: цели, проходы, WGSL-шейдеры (строками), тайлы света, AO, тени, bloom, tonemap, TAA | horizon/crates/horizon-client/src/gfx/hd.rs |
Встраивание в кадр: 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.
- Каскады теней (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). Террейн + дальние LOD-тайлы + сущности. Полупрозрачное в 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 = выкл). - Освещение
fs_light→lit(HDR): пиксель — вода, если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, тусклее под навесом). Френель Шлика 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 блоков сбрасывает историю. Джиттер добавляется к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) |
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: есть небо).
Композит воды (WATER, group 0): 0 HdFrame, 1 lit, 2 глубина G-буфера, 3 цвет воды, 4 нормаль воды, 5 глубина воды, 6–7 тени, 8 лайтмап, 9 сэмплер.
Как проверять
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, чтобы найти виновника.
Отладка: временно выводи промежуточные величины цветом в 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 |
API моделей по хэндлам (#30, уровень API 11: model-id, model-generation, set-models-by-id) |
готово | патч 17 |
План (по порядку) и ТЗ
Каждый пункт — самостоятельная задача. Перед началом прочитай hd.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 заметно выше. Альтернатива на Mac — MetalFX через wgpu-hal (нативный код, только Metal) — отдельной опцией потом. DLSS нереален для нашей цели (только NVIDIA через Streamline, нет в wgpu). - Вода — СДЕЛАНО (патч 19, см. «Вода» ниже). Исходное ТЗ: отдельный G-буфер-путь для воды (маска + нормаль с анимированными волнами), SSR (трассировка по
глубине, откат на небо/туман), преломление (смещение по нормали), затухание по глубине воды (цвет биома),
блики солнца (GGX-подобный), френель. Подводный вид не ломать (
frame.underwater). - Небо и атмосфера: рассеяние Рэлея/Ми (предвычисленная LUT пропускания и небесного цвета в маленькой
текстуре), солнце/луна дисками, звёзды из ванили; туман и лучи берут цвет из той же модели. Учесть дождь
(
weather), закат, Незер/Энд (там — ванильное небо). - Объёмные облака (рэймарч слоя с шумом) + их тени на земле (в карту/маску солнца).
- Блики и материалы: 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%).
Правила
- Ничего не ломать в
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). - Обнови этот документ (статус + что изменилось в конвейере) в том же коммите, что и код.