Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f43fa5ee66 |
@@ -118,6 +118,7 @@ Bind groups кэшируются и пересоздаются только пр
|
||||
| 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, ночь),
|
||||
@@ -227,6 +228,9 @@ HORIZON_HD_TAA=0 --out run-home-bench/out/taa-off` — A/B; `--baseline run-home
|
||||
|
||||
| Материалы и блики (план 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` (следы дронов и ракет, колонны над пожарами, дым взрывов) | готово, в рабочем дереве форка; вид не настраивался (см. «Дым и следы») | не закоммичено |
|
||||
|
||||
## План (по порядку) и ТЗ
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 3de7a6875140a107e28563cc44489b55f5794517 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Wed, 28 Oct 2026 12:37:45 +0300
|
||||
Subject: [PATCH 01/28] Mod API (AirDef fork): world models, client particles,
|
||||
Subject: [PATCH 01/29] Mod API (AirDef fork): world models, client particles,
|
||||
sounds, ground heights
|
||||
|
||||
render.set-models draws block models with an interpolated pose per id;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From a6d64e6415bf618539af464e521091e72b72d72f Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Wed, 28 Oct 2026 12:39:45 +0300
|
||||
Subject: [PATCH 02/28] cargo-horizon-mod: report particles and sounds
|
||||
Subject: [PATCH 02/29] cargo-horizon-mod: report particles and sounds
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From ee79b3562feebe51959a01f1585a9e85bd53ccb5 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Wed, 28 Oct 2026 13:33:10 +0300
|
||||
Subject: [PATCH 03/28] Mod API (AirDef fork): full-screen post pass for mods
|
||||
Subject: [PATCH 03/29] Mod API (AirDef fork): full-screen post pass for mods
|
||||
|
||||
render.set-post compiles a mod's WGSL after a prelude (camera matrices,
|
||||
time, 256 vec4 of params, the world's colour and depth) and runs it after
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 2b137dbbedd540daeebc7f5e387847aeb56a1355 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Wed, 28 Oct 2026 22:09:30 +0300
|
||||
Subject: [PATCH 04/28] Mod models: draw far models as scaled impostors past
|
||||
Subject: [PATCH 04/29] Mod models: draw far models as scaled impostors past
|
||||
the far plane, cull at 4096
|
||||
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From c8ebca6e0af68630e65211844b34d25c5b985c03 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Wed, 28 Oct 2026 22:31:00 +0300
|
||||
Subject: [PATCH 05/28] Mod cameras: render.set-views draws the world from
|
||||
Subject: [PATCH 05/29] Mod cameras: render.set-views draws the world from
|
||||
other points into pictures that HUD view shapes show (API level 9)
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 8d2261fc4f8072c32e454139cfea7a8c0cd9d664 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Wed, 28 Oct 2026 23:08:21 +0300
|
||||
Subject: [PATCH 06/28] Mods: more fuel per tick and per event (AirDef plans
|
||||
Subject: [PATCH 06/29] Mods: more fuel per tick and per event (AirDef plans
|
||||
raids)
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 6c548de34def8e4a624d734243418090798bd16a Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Wed, 28 Oct 2026 23:25:29 +0300
|
||||
Subject: [PATCH 07/28] Mod cameras up to 1920x1080 (full-screen replays)
|
||||
Subject: [PATCH 07/29] Mod cameras up to 1920x1080 (full-screen replays)
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 9b9e93269d67401cc620567a884d5ce97f68d76e Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 11:15:11 +0300
|
||||
Subject: [PATCH 08/28] Mod cameras: distant terrain tiles, fog measured from
|
||||
Subject: [PATCH 08/29] Mod cameras: distant terrain tiles, fog measured from
|
||||
the camera, no impostors while one is shown
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 065d9fa02966238a5bd9ff460392a1a809db0b11 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 11:25:45 +0300
|
||||
Subject: [PATCH 09/28] Mods: game.world-id, so mods keep state per world (API
|
||||
Subject: [PATCH 09/29] Mods: game.world-id, so mods keep state per world (API
|
||||
level 9)
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 1bf28135fd9a31f837323dc3a8d81ed73343d816 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 12:01:08 +0300
|
||||
Subject: [PATCH 10/28] cargo-horizon-mod: trial runs behind feature full, so a
|
||||
Subject: [PATCH 10/29] cargo-horizon-mod: trial runs behind feature full, so a
|
||||
packing-only tool skips wasmtime
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 0a56f5616df4699588cbcc529d4720661baa810f Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 13:18:43 +0300
|
||||
Subject: [PATCH 11/28] HD lighting: G-buffer pass, per-pixel mod point lights
|
||||
Subject: [PATCH 11/29] HD lighting: G-buffer pass, per-pixel mod point lights
|
||||
in HDR, bloom and tone curve; render.set-lights and render.set-lighting (API
|
||||
level 10)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 9ebf0031fe9ba6b7f02afb26e4fd3a1ba4ddb0d0 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 13:59:43 +0300
|
||||
Subject: [PATCH 12/28] HD lighting stage 2: sun and moon shadow cascades, the
|
||||
Subject: [PATCH 12/29] HD lighting stage 2: sun and moon shadow cascades, the
|
||||
world relit (sun by face angle and shadow, sky and block light through the
|
||||
lightmap), light shafts and halos, fog over it again, a light colour grade
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 7a5a38f68923aec62f10bfa0df50b436474ed612 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 16:19:53 +0300
|
||||
Subject: [PATCH 13/28] HD: bind groups cached until a resize or a new source,
|
||||
Subject: [PATCH 13/29] HD: bind groups cached until a resize or a new source,
|
||||
shadow passes draw only the entities that cast shadows
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 8020811e57d4b5a9ea1cd72d30ce859dc5095d35 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 16:25:44 +0300
|
||||
Subject: [PATCH 14/28] HD: lights culled per 16x16 tile (surface and halo
|
||||
Subject: [PATCH 14/29] HD: lights culled per 16x16 tile (surface and halo
|
||||
lists built on the CPU), a naga test for the lighting shaders
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 72d3fddfea329e578162656151350f0aa2efd323 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 16:43:36 +0300
|
||||
Subject: [PATCH 15/28] HD: GPU time per pass in the F3 timing list,
|
||||
Subject: [PATCH 15/29] HD: GPU time per pass in the F3 timing list,
|
||||
half-resolution GTAO with a depth-aware blur
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 1fd83a821bf270879d8ece5aa583f200ed920d89 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 17:02:02 +0300
|
||||
Subject: [PATCH 16/28] HD: shadows draw only sun-facing quads and skip buried
|
||||
Subject: [PATCH 16/29] HD: shadows draw only sun-facing quads and skip buried
|
||||
sections, the far cascade every second frame; TAA (Halton jitter,
|
||||
reprojection by depth, YCoCg variance clipping) before bloom
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 1502d359d599228fe0f44ce6b99a3fded2ac5ea0 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 17:02:02 +0300
|
||||
Subject: [PATCH 17/28] Mods: model handles (render.model-id, model-generation,
|
||||
Subject: [PATCH 17/29] Mods: model handles (render.model-id, model-generation,
|
||||
set-models-by-id; API level 11) so mods send no model names each tick;
|
||||
clear-post and set-lighting documented
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 0f9d99b8ad3e4b72e621d84c2396cf1d3d2b7a33 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 17:19:57 +0300
|
||||
Subject: [PATCH 18/28] HD: temporal upscaling (HORIZON_HD_SCALE: world,
|
||||
Subject: [PATCH 18/29] HD: temporal upscaling (HORIZON_HD_SCALE: world,
|
||||
G-buffer and lighting at a lower size, rebuilt to the window in the TAA
|
||||
pass); fix flickering shadows near the player (the near cascade lost its
|
||||
sections on frames the far one was held, the held far matrix ignored the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 38250c4392e27d5700842ca6ee47291dd1831b72 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 17:47:52 +0300
|
||||
Subject: [PATCH 19/28] HD water: its own pass (colour, normal, depth), the
|
||||
Subject: [PATCH 19/29] HD water: its own pass (colour, normal, depth), the
|
||||
floor under it lit, a composite with world-space waves, refraction with depth
|
||||
absorption in the biome colour, screen-space reflections, Fresnel and a
|
||||
shadowed sun glint (HORIZON_HD_WATER=0 to switch off)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From dbc6f8a6e9c2eeabe1de15f1794013c90be7faa5 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 17:56:04 +0300
|
||||
Subject: [PATCH 20/28] HD water: no vanilla texture on the surface (the biome
|
||||
Subject: [PATCH 20/29] HD water: no vanilla texture on the surface (the biome
|
||||
colour only), a darker, deeper colour
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 13e74537dd6e63482e3613e0e416de1158944dfe Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 18:38:19 +0300
|
||||
Subject: [PATCH 21/28] HD sky and light: a physical atmosphere (Rayleigh, Mie,
|
||||
Subject: [PATCH 21/29] HD sky and light: a physical atmosphere (Rayleigh, Mie,
|
||||
ozone; constants and transmittance after Photon, MIT) for the sky, haze, fog
|
||||
and water reflections; warmer sunlight from the atmosphere's transmittance, a
|
||||
gentle sky-coloured ambient, soft PCSS-lite shadows near the player, a haze
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 6277e051a89c2537570982f6ef275887193c05cf Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 19:02:16 +0300
|
||||
Subject: [PATCH 22/28] HD water: a darker blue, a little clearer, ripples a
|
||||
Subject: [PATCH 22/29] HD water: a darker blue, a little clearer, ripples a
|
||||
little higher
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 982265d21bb9d74ec0040db03feb5a55762286ad Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 21:41:48 +0300
|
||||
Subject: [PATCH 23/28] HD: Photon-style sunsets (Rec. 2020 atmosphere, sun
|
||||
Subject: [PATCH 23/29] HD: Photon-style sunsets (Rec. 2020 atmosphere, sun
|
||||
tint, blue hour; MIT), volumetric clouds with ground shadows instead of
|
||||
vanilla's in HD, volumetric fog with sun shafts and morning mist
|
||||
(HORIZON_HD_CLOUDS=0, HORIZON_HD_VOLFOG=0)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 24971b3c67d8c87673eb2e0d204cedf8a2f46d1c Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 21:43:22 +0300
|
||||
Subject: [PATCH 24/28] Mods: world queries for simulations (render.query-rays,
|
||||
Subject: [PATCH 24/29] Mods: world queries for simulations (render.query-rays,
|
||||
query-sections, world-changes; API level 12): block-exact rays and section
|
||||
occupancy by collision, sight or sound, answered next tick within budgets,
|
||||
and a log of changed sections
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From fdec4960983bdff82f3793460f9a5234f0cd3f49 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 22:56:15 +0300
|
||||
Subject: [PATCH 25/28] HD fixes: rain level from the world not from rain
|
||||
Subject: [PATCH 25/29] HD fixes: rain level from the world not from rain
|
||||
geometry (#41), TAA history on the jittered grid (#38), vanilla cloud tint
|
||||
without a depth heuristic (#35), air fog once over water (#37), translucent
|
||||
things in front of HD water (#36), cheaper sunrise fan (#39), halos in thick
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 21391afb26f49505d6cdaf454a6355cd4e747503 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 23:19:53 +0300
|
||||
Subject: [PATCH 26/28] HD split into modules (gfx/hd/: one Rust file and one
|
||||
Subject: [PATCH 26/29] HD split into modules (gfx/hd/: one Rust file and one
|
||||
WGSL file per pass, a shared prelude), contracts for colour, depth, alpha,
|
||||
time and coordinates written down, tests that the frame layout and bindings
|
||||
match between Rust and WGSL (#45)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 8d4cadbf8534309ac847d34e229f6d764770475d Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Thu, 29 Oct 2026 23:22:19 +0300
|
||||
Subject: [PATCH 27/28] HD clouds: noise scales that divide the camera's
|
||||
Subject: [PATCH 27/29] HD clouds: noise scales that divide the camera's
|
||||
65536-block wrap, so the clouds and their shadows no longer jump at x or z =
|
||||
0
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 0fb59c782abf269fe555faa3241b31acf41e26d7 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Fri, 30 Oct 2026 03:17:11 +0300
|
||||
Subject: [PATCH 28/28] HD: volumetric smoke and missile trails with clear
|
||||
Subject: [PATCH 28/29] HD: volumetric smoke and missile trails with clear
|
||||
tunnels through fog and cloud (render.set-smoke, API level 14; #54); sun
|
||||
speculars by material, wet surfaces and puddles in rain, wind in foliage
|
||||
(HORIZON_HD_SPECULAR=0, HORIZON_HD_WIND=0, HORIZON_HD_SMOKE=0); HD shaders
|
||||
|
||||
@@ -0,0 +1,603 @@
|
||||
From e4bc29da45d0d38244c6eb2ba3ffb9ab853540c0 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Fri, 30 Oct 2026 03:26:09 +0300
|
||||
Subject: [PATCH 29/29] HD clouds composited by depth so terrain and vehicles
|
||||
inside or behind the layer are hidden and those in front stay in front (#67);
|
||||
world-anchored noise everywhere (cloud detail by height above the layer, fog,
|
||||
smoke and puddle scales dividing the wrap) (#68); the replay's time is visual
|
||||
only, gameplay sky darkening uses the world's time (#70)
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
---
|
||||
crates/horizon-client/src/game.rs | 37 +++--
|
||||
crates/horizon-client/src/game/render_tick.rs | 2 +-
|
||||
crates/horizon-client/src/gfx/hd/cloud.wgsl | 54 ++++++-
|
||||
.../src/gfx/hd/cloud_common.wgsl | 3 +-
|
||||
crates/horizon-client/src/gfx/hd/fog.wgsl | 6 +-
|
||||
crates/horizon-client/src/gfx/hd/light.wgsl | 42 +++++-
|
||||
crates/horizon-client/src/gfx/hd/lighting.rs | 4 +-
|
||||
crates/horizon-client/src/gfx/hd/volumes.rs | 135 +++++++++++++++---
|
||||
8 files changed, 239 insertions(+), 44 deletions(-)
|
||||
|
||||
diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs
|
||||
index 2130d8d88..f21b3dcdd 100644
|
||||
--- a/crates/horizon-client/src/game.rs
|
||||
+++ b/crates/horizon-client/src/game.rs
|
||||
@@ -1838,6 +1838,8 @@ struct Game<'a> {
|
||||
events: events::Events,
|
||||
/// The overworld's last `sky_darken` and its inputs (time, rain, thunder).
|
||||
sky_darken_memo: std::cell::Cell<Option<((i64, u32, u32), i32)>>,
|
||||
+ /// The same for the time a mod shows (a replay's): render only.
|
||||
+ shown_darken_memo: std::cell::Cell<Option<((i64, u32, u32), i32)>>,
|
||||
persist: persist::EntityMemory,
|
||||
/// Connected to a server: the world comes from the network (game/remote.rs).
|
||||
remote: Option<remote::Remote>,
|
||||
@@ -8020,7 +8022,7 @@ impl Game<'_> {
|
||||
.flatten();
|
||||
if let Some(sprite) = shadow_sprite {
|
||||
let ambient = if self.dim == Dim::Nether { 0.1 } else { 0.0 };
|
||||
- let darken = self.sky_darken();
|
||||
+ let darken = self.shown_sky_darken();
|
||||
let a = alpha;
|
||||
let lerp = |p: Vec3, q: Vec3| {
|
||||
glam::DVec3::new(
|
||||
@@ -8378,33 +8380,43 @@ impl Game<'_> {
|
||||
.map_or(self.world.day_time, |t| t as i64)
|
||||
}
|
||||
|
||||
+ /// The sky's darkening for the game's rules (mob AI, spawning, block behaviour, stats): always
|
||||
+ /// the world's real time, never a mod's render-only override.
|
||||
fn sky_darken(&self) -> i32 {
|
||||
+ self.sky_darken_at(self.world.day_time, &self.sky_darken_memo)
|
||||
+ }
|
||||
+
|
||||
+ /// The sky's darkening as the player sees it (entity shadows, particle light): follows the mod's
|
||||
+ /// time override, so a replay's night looks dark without touching the rules.
|
||||
+ fn shown_sky_darken(&self) -> i32 {
|
||||
+ self.sky_darken_at(self.shown_day_time(), &self.shown_darken_memo)
|
||||
+ }
|
||||
+
|
||||
+ fn sky_darken_at(
|
||||
+ &self,
|
||||
+ time: i64,
|
||||
+ memo: &std::cell::Cell<Option<((i64, u32, u32), i32)>>,
|
||||
+ ) -> i32 {
|
||||
if self.dim == Dim::Nether {
|
||||
return 11;
|
||||
}
|
||||
if self.dim == Dim::End {
|
||||
- let sky = self
|
||||
- .res
|
||||
- .end_environment()
|
||||
- .sample(self.shown_day_time(), None, None);
|
||||
+ let sky = self.res.end_environment().sample(time, None, None);
|
||||
return (15.0 - 15.0 * sky.sky_light_level).round() as i32;
|
||||
}
|
||||
let (rain, thunder) = self.world.weather.levels(1.0);
|
||||
// Sampled once per (time, weather): every mob asks each tick.
|
||||
- let key = (self.shown_day_time(), rain.to_bits(), thunder.to_bits());
|
||||
- if let Some((k, darken)) = self.sky_darken_memo.get()
|
||||
+ let key = (time, rain.to_bits(), thunder.to_bits());
|
||||
+ if let Some((k, darken)) = memo.get()
|
||||
&& k == key
|
||||
{
|
||||
return darken;
|
||||
}
|
||||
- let mut sky = self
|
||||
- .res
|
||||
- .environment()
|
||||
- .sample(self.shown_day_time(), None, None);
|
||||
+ let mut sky = self.res.environment().sample(time, None, None);
|
||||
horizon_client::environment::apply_weather(&mut sky, rain, thunder);
|
||||
// Level.updateSkyBrightness: darkening from the sky light level.
|
||||
let darken = (15.0 - 15.0 * sky.sky_light_level).round() as i32;
|
||||
- self.sky_darken_memo.set(Some((key, darken)));
|
||||
+ memo.set(Some((key, darken)));
|
||||
darken
|
||||
}
|
||||
fn mob_hit(&self) -> Option<(usize, f64)> {
|
||||
@@ -13954,6 +13966,7 @@ impl<'a> Game<'a> {
|
||||
mods: Default::default(),
|
||||
native_shaders: Default::default(),
|
||||
sky_darken_memo: Default::default(),
|
||||
+ shown_darken_memo: Default::default(),
|
||||
persist: Default::default(),
|
||||
remote: None,
|
||||
lan: None,
|
||||
diff --git a/crates/horizon-client/src/game/render_tick.rs b/crates/horizon-client/src/game/render_tick.rs
|
||||
index 6807d7718..9912970a5 100644
|
||||
--- a/crates/horizon-client/src/game/render_tick.rs
|
||||
+++ b/crates/horizon-client/src/game/render_tick.rs
|
||||
@@ -241,7 +241,7 @@ impl Game<'_> {
|
||||
eye[2].floor() as i32,
|
||||
];
|
||||
let packed = self.world.light(b[0], b[1], b[2]);
|
||||
- let sky = i32::from(packed >> 4) - self.sky_darken();
|
||||
+ let sky = i32::from(packed >> 4) - self.shown_sky_darken();
|
||||
let raw = sky.max(i32::from(packed & 15)).clamp(0, 15) as u8;
|
||||
lightmap_brightness(if self.dim == Dim::Nether { 0.1 } else { 0.0 }, raw)
|
||||
}
|
||||
diff --git a/crates/horizon-client/src/gfx/hd/cloud.wgsl b/crates/horizon-client/src/gfx/hd/cloud.wgsl
|
||||
index fad234a46..39a239f4d 100644
|
||||
--- a/crates/horizon-client/src/gfx/hd/cloud.wgsl
|
||||
+++ b/crates/horizon-client/src/gfx/hd/cloud.wgsl
|
||||
@@ -1,6 +1,17 @@
|
||||
|
||||
@group(0) @binding(6) var history: texture_2d<f32>;
|
||||
@group(0) @binding(7) var smoke_tex: texture_2d<f32>;
|
||||
+@group(0) @binding(8) var scene_depth: texture_depth_2d;
|
||||
+@group(0) @binding(9) var history_dist: texture_2d<f32>;
|
||||
+
|
||||
+// What the sky pixels report as the length of their ray (nothing stops them).
|
||||
+const SKY_LEN: f32 = 20000.0;
|
||||
+
|
||||
+struct CloudOut {
|
||||
+ @location(0) cloud: vec4<f32>,
|
||||
+ // How far the ray was marched: the surface behind this texel, or SKY_LEN for sky.
|
||||
+ @location(1) dist: vec4<f32>,
|
||||
+}
|
||||
|
||||
fn ign(px: vec2<f32>) -> f32 {
|
||||
let p = px + vec2<f32>(5.588238 * frame.params.w);
|
||||
@@ -65,7 +76,26 @@ fn cloud_scatter(
|
||||
}
|
||||
|
||||
@fragment
|
||||
-fn fs_cloud(in: FullOut) -> @location(0) vec4<f32> {
|
||||
+fn fs_cloud(in: FullOut) -> CloudOut {
|
||||
+ // The nearest of the four world pixels this texel stands for: the march ends at the surface
|
||||
+ // there (the sky counts as infinitely far), so what stands in front of a cloud stays in front
|
||||
+ // and what stands behind it is hidden by it.
|
||||
+ let size = vec2<i32>(frame.resolution.xy);
|
||||
+ let base = vec2<i32>(in.position.xy) * 2;
|
||||
+ var zmax = 0.0;
|
||||
+ var best = base;
|
||||
+ for (var k = 0; k < 4; k++) {
|
||||
+ let q = min(base + vec2<i32>(k & 1, k >> 1), size - 1);
|
||||
+ let z = textureLoad(scene_depth, q, 0);
|
||||
+ if z > zmax {
|
||||
+ zmax = z;
|
||||
+ best = q;
|
||||
+ }
|
||||
+ }
|
||||
+ let suv = (vec2<f32>(best) + 0.5) * frame.resolution.zw;
|
||||
+ let sndc = vec4<f32>(suv.x * 2.0 - 1.0, 1.0 - suv.y * 2.0, max(zmax, 0.000001), 1.0);
|
||||
+ let sh = frame.inverse_view_proj * sndc;
|
||||
+ let scene_len = select(SKY_LEN, length(sh.xyz / sh.w), zmax > 0.0);
|
||||
let ndc = vec4<f32>(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0, 0.5, 1.0);
|
||||
let hh = frame.inverse_view_proj * ndc;
|
||||
let dir = normalize(hh.xyz / hh.w);
|
||||
@@ -73,11 +103,13 @@ fn fs_cloud(in: FullOut) -> @location(0) vec4<f32> {
|
||||
var hit_t = 900.0;
|
||||
let y0 = frame.clouds.x;
|
||||
let y1 = y0 + frame.clouds.y;
|
||||
- if abs(dir.y) > 0.004 {
|
||||
- let ta = y0 / dir.y;
|
||||
- let tb = y1 / dir.y;
|
||||
+ // A level ray stays in the slab if the camera is in it (it never leaves), else misses it.
|
||||
+ let dy = select(dir.y, select(-1.0e-4, 1.0e-4, dir.y >= 0.0), abs(dir.y) < 1.0e-4);
|
||||
+ {
|
||||
+ let ta = y0 / dy;
|
||||
+ let tb = y1 / dy;
|
||||
let lo = max(min(ta, tb), 0.0);
|
||||
- let hi = min(max(ta, tb), 4200.0);
|
||||
+ let hi = min(max(ta, tb), min(4200.0, scene_len));
|
||||
if hi > lo {
|
||||
let dither = ign(in.position.xy);
|
||||
let n = clamp(i32(ceil((hi - lo) / 14.0)), 8, 26);
|
||||
@@ -131,6 +163,8 @@ fn fs_cloud(in: FullOut) -> @location(0) vec4<f32> {
|
||||
cur = vec4<f32>(colour, trans);
|
||||
}
|
||||
}
|
||||
+ var out: CloudOut;
|
||||
+ out.dist = vec4<f32>(scene_len, 0.0, 0.0, 0.0);
|
||||
// Reproject the history by where the clouds are (rotation and the camera's step) and blend.
|
||||
if frame.cloud_cam.w > 0.5 {
|
||||
let hp = frame.prev_view_proj * vec4<f32>(dir * hit_t + frame.taa.xyz, 1.0);
|
||||
@@ -138,12 +172,18 @@ fn fs_cloud(in: FullOut) -> @location(0) vec4<f32> {
|
||||
let huv = vec2<f32>(hp.x / hp.w * 0.5 + 0.5, 0.5 - hp.y / hp.w * 0.5);
|
||||
if all(huv > vec2<f32>(0.0)) && all(huv < vec2<f32>(1.0)) {
|
||||
let old = textureSampleLevel(history, linear, huv, 0.0);
|
||||
+ // A history that was marched to another surface (an edge, something that moved in
|
||||
+ // front) does not describe this ray.
|
||||
+ let hs = vec2<f32>(textureDimensions(history_dist));
|
||||
+ let old_len = textureLoad(history_dist, vec2<i32>(huv * hs), 0).x;
|
||||
+ let same_len = 1.0 - smoothstep(0.5, 1.0, abs(old_len - scene_len) / (0.1 * min(scene_len, old_len) + 2.0));
|
||||
// Not too much of a history that disagrees by a lot (a gust, a fast turn).
|
||||
let diff = length(old.rgb - cur.rgb) + abs(old.a - cur.a);
|
||||
- let keep = 0.9 * (1.0 - smoothstep(0.35, 1.2, diff));
|
||||
+ let keep = 0.9 * (1.0 - smoothstep(0.35, 1.2, diff)) * same_len;
|
||||
cur = mix(cur, old, keep);
|
||||
}
|
||||
}
|
||||
}
|
||||
- return cur;
|
||||
+ out.cloud = cur;
|
||||
+ return out;
|
||||
}
|
||||
diff --git a/crates/horizon-client/src/gfx/hd/cloud_common.wgsl b/crates/horizon-client/src/gfx/hd/cloud_common.wgsl
|
||||
index e2352f008..8ee014420 100644
|
||||
--- a/crates/horizon-client/src/gfx/hd/cloud_common.wgsl
|
||||
+++ b/crates/horizon-client/src/gfx/hd/cloud_common.wgsl
|
||||
@@ -44,7 +44,8 @@ fn cloud_density(pos: vec3<f32>, detail: f32) -> f32 {
|
||||
d *= smoothstep(0.0, 0.2, h);
|
||||
if d < 0.002 { return 0.0; }
|
||||
if detail > 0.0 {
|
||||
- let p3 = vec3<f32>(w.x, pos.y, w.y);
|
||||
+ // Height above the layer's base, not the camera's height: the noise stays put in the world.
|
||||
+ let p3 = vec3<f32>(w.x, pos.y - frame.clouds.x, w.y);
|
||||
let warp = (textureSampleLevel(noise3, repeat_s, p3 / 1024.0, 0.0).b - 0.5) * 0.4;
|
||||
let w0 = 1.0 - textureSampleLevel(noise3, repeat_s, p3 / 512.0 + vec3<f32>(warp), 0.0).r;
|
||||
let w1 = 1.0 - textureSampleLevel(noise3, repeat_s, p3 / 64.0 + vec3<f32>(warp * 3.0), 0.0).g;
|
||||
diff --git a/crates/horizon-client/src/gfx/hd/fog.wgsl b/crates/horizon-client/src/gfx/hd/fog.wgsl
|
||||
index 38b9098e3..d035bbf0d 100644
|
||||
--- a/crates/horizon-client/src/gfx/hd/fog.wgsl
|
||||
+++ b/crates/horizon-client/src/gfx/hd/fog.wgsl
|
||||
@@ -40,7 +40,7 @@ fn sun_visible(p: vec3<f32>) -> f32 {
|
||||
}
|
||||
|
||||
// The air's extinction per block at a camera-relative point: a haze that thins with height above
|
||||
-// the sea, a morning mist hugging the ground, rain, patches from a 3D noise that drifts; none
|
||||
+// the sea, a morning mist hugging the ground, rain, patches from a 3D noise that drifts (its scale divides 65536, the wrap of the camera's x, z, so it does not jump at x or z = 0); none
|
||||
// deep underground.
|
||||
fn fog_sigma(p: vec3<f32>) -> f32 {
|
||||
let wy = p.y + frame.vfog.y;
|
||||
@@ -50,7 +50,7 @@ fn fog_sigma(p: vec3<f32>) -> f32 {
|
||||
s += frame.sky_extra.z * 0.003;
|
||||
let w = vec3<f32>(p.x + frame.cloud_cam.x, wy, p.z + frame.cloud_cam.y)
|
||||
+ vec3<f32>(0.5, 0.0, 0.35) * frame.clouds.w;
|
||||
- let n = textureSampleLevel(noise3, repeat_s, w / 170.0, 0.0).b;
|
||||
+ let n = textureSampleLevel(noise3, repeat_s, w / 128.0, 0.0).b;
|
||||
let patchy = 0.35 + 1.3 * smoothstep(0.25, 0.75, n);
|
||||
// Fade out far below the sea's height: caves keep their own dark air.
|
||||
return s * patchy * smoothstep(28.0, 58.0, wy);
|
||||
@@ -70,7 +70,7 @@ fn smoke_detail(pos: vec3<f32>, core: f32) -> f32 {
|
||||
let w = vec3<f32>(pos.x + frame.cloud_cam.x, pos.y + frame.vfog.y, pos.z + frame.cloud_cam.y)
|
||||
- vec3<f32>(SMOKE_WIND.x, 0.0, SMOKE_WIND.y) * frame.smoke.z;
|
||||
let n1 = textureSampleLevel(noise3, repeat_s, w / 64.0, 0.0).r;
|
||||
- let n2 = textureSampleLevel(noise3, repeat_s, w / 22.0, 0.0).g;
|
||||
+ let n2 = textureSampleLevel(noise3, repeat_s, w / 32.0, 0.0).g;
|
||||
let n = 0.55 * n1 + 0.45 * n2;
|
||||
// Thin smoke breaks up into wisps, thick smoke stays whole.
|
||||
let keep = smoothstep(0.18, 0.62, n + 0.45 * min(core, 1.5) - 0.12);
|
||||
diff --git a/crates/horizon-client/src/gfx/hd/light.wgsl b/crates/horizon-client/src/gfx/hd/light.wgsl
|
||||
index 2534a3db8..870ae0d7d 100644
|
||||
--- a/crates/horizon-client/src/gfx/hd/light.wgsl
|
||||
+++ b/crates/horizon-client/src/gfx/hd/light.wgsl
|
||||
@@ -30,6 +30,9 @@ struct Light {
|
||||
@group(0) @binding(17) var fog_dist_tex: texture_2d<f32>;
|
||||
@group(0) @binding(18) var cloud_tex: texture_2d<f32>;
|
||||
@group(0) @binding(19) var cloud_shadow_tex: texture_2d<f32>;
|
||||
+// How far each cloud texel's ray was marched (the surface behind it, or SKY_RAY for sky).
|
||||
+@group(0) @binding(20) var cloud_dist_tex: texture_2d<f32>;
|
||||
+const SKY_RAY: f32 = 20000.0;
|
||||
const CLOUD_SHADOW_R: f32 = 1200.0;
|
||||
// The pixel being lit, for the shadow filter's noise.
|
||||
var<private> frag_px: vec2<f32>;
|
||||
@@ -194,6 +197,34 @@ fn fog_at(px: vec2<i32>, dist: f32) -> vec4<f32> {
|
||||
return sum / total;
|
||||
}
|
||||
|
||||
+// The volumetric clouds between the camera and a surface `dist` away (SKY_RAY for sky): the four
|
||||
+// nearest half-resolution texels weighted by closeness and by how near the length they were
|
||||
+// marched to is, so a cloud in front of a mountain does not bleed onto the sky beside it.
|
||||
+fn cloud_at(px: vec2<i32>, dist: f32) -> vec4<f32> {
|
||||
+ let size = vec2<i32>(textureDimensions(cloud_tex));
|
||||
+ let f = vec2<f32>(px) * 0.5 - vec2<f32>(0.25);
|
||||
+ let base = vec2<i32>(floor(f));
|
||||
+ let t = f - floor(f);
|
||||
+ var sum = vec4<f32>(0.0);
|
||||
+ var total = 0.0;
|
||||
+ for (var j = 0; j < 2; j++) {
|
||||
+ for (var i = 0; i < 2; i++) {
|
||||
+ let q = clamp(base + vec2<i32>(i, j), vec2<i32>(0), size - 1);
|
||||
+ let s = textureLoad(cloud_tex, q, 0);
|
||||
+ let sd = textureLoad(cloud_dist_tex, q, 0).x;
|
||||
+ let bw = select(1.0 - t.x, t.x, i == 1) * select(1.0 - t.y, t.y, j == 1);
|
||||
+ let dz = (sd - dist) / (0.06 * min(sd, dist) + 0.4);
|
||||
+ let w = bw / (1.0 + dz * dz * dz * dz);
|
||||
+ sum += s * w;
|
||||
+ total += w;
|
||||
+ }
|
||||
+ }
|
||||
+ if total < 0.0001 {
|
||||
+ return textureLoad(cloud_tex, clamp(px / 2, vec2<i32>(0), size - 1), 0);
|
||||
+ }
|
||||
+ return sum / total;
|
||||
+}
|
||||
+
|
||||
struct LightOut {
|
||||
// The lit colour; alpha 0.5 marks water (the floor under it, lit without any air), 0.6 an
|
||||
// object in front of the water (the scene as drawn).
|
||||
@@ -246,7 +277,7 @@ fn shade_surface(px: vec2<i32>, p: vec3<f32>, water: bool) -> vec3<f32> {
|
||||
let flat_up = smoothstep(0.9, 0.98, n.y);
|
||||
let soak = wet * open_sky * (0.35 + 0.65 * flat_up);
|
||||
let wp = frame.cloud_cam.xy + p.xz;
|
||||
- let nz = 0.6 * wet_noise(wp * 0.45) + 0.4 * wet_noise(wp * 1.3 + vec2<f32>(17.0, 5.0));
|
||||
+ let nz = 0.6 * wet_noise(wp * 0.5) + 0.4 * wet_noise(wp * 1.25 + vec2<f32>(17.0, 5.0));
|
||||
puddle = smoothstep(0.66 - 0.16 * wet, 0.74 - 0.16 * wet, nz) * smoothstep(0.3, 0.7, wet) * open_sky * flat_up;
|
||||
surface_rgb *= mix(1.0, 0.7, soak) * mix(1.0, 0.6, puddle);
|
||||
rough = mix(rough, 0.12, soak * (1.0 - 0.5 * metal));
|
||||
@@ -377,7 +408,7 @@ fn fs_light(in: FullOut) -> LightOut {
|
||||
c = sky + kept;
|
||||
// The volumetric clouds over the sky and the sun.
|
||||
if frame.clouds.z > 0.5 {
|
||||
- let cl = textureSampleLevel(cloud_tex, linear, in.uv, 0.0);
|
||||
+ let cl = cloud_at(px, SKY_RAY);
|
||||
c = c * cl.a + cl.rgb;
|
||||
}
|
||||
}
|
||||
@@ -421,6 +452,13 @@ fn fs_light(in: FullOut) -> LightOut {
|
||||
air_t = fg.a;
|
||||
air_s = fg.rgb;
|
||||
}
|
||||
+ // The clouds between the camera and the surface (inside the layer, or in front of a mountain
|
||||
+ // or a flying thing): laid over the surface and under the fog, as the sky's are.
|
||||
+ if d > 0.0 && atmosphere && frame.clouds.z > 0.5 {
|
||||
+ let cl = cloud_at(px, length(ps));
|
||||
+ air_s += air_t * cl.rgb;
|
||||
+ air_t *= cl.a;
|
||||
+ }
|
||||
if !volumetric && sun_on > 0.0 && density > 0.0 {
|
||||
let steps = 14;
|
||||
let jitter = ign(in.position.xy);
|
||||
diff --git a/crates/horizon-client/src/gfx/hd/lighting.rs b/crates/horizon-client/src/gfx/hd/lighting.rs
|
||||
index da56d00ba..d26f1e513 100644
|
||||
--- a/crates/horizon-client/src/gfx/hd/lighting.rs
|
||||
+++ b/crates/horizon-client/src/gfx/hd/lighting.rs
|
||||
@@ -34,7 +34,7 @@ pub const MAX_LIGHTS: usize = 512;
|
||||
|
||||
pub(super) const LIGHT: &str = include_str!("light.wgsl");
|
||||
/// The lighting's group 0, binding by binding (see the contract above).
|
||||
-pub(super) const LIGHT_BINDINGS: [wgpu::BindingType; 20] = [
|
||||
+pub(super) const LIGHT_BINDINGS: [wgpu::BindingType; 21] = [
|
||||
UNIFORM,
|
||||
UNFILTERED,
|
||||
DEPTH,
|
||||
@@ -55,6 +55,7 @@ pub(super) const LIGHT_BINDINGS: [wgpu::BindingType; 20] = [
|
||||
UNFILTERED,
|
||||
FLOAT,
|
||||
FLOAT,
|
||||
+ UNFILTERED,
|
||||
];
|
||||
|
||||
/// The outside views and sampler the lighting group reads.
|
||||
@@ -219,6 +220,7 @@ impl Hd {
|
||||
wgpu::BindingResource::TextureView(&self.volumes.fog_dist),
|
||||
wgpu::BindingResource::TextureView(self.volumes.cloud_view()),
|
||||
wgpu::BindingResource::TextureView(&self.volumes.cloud_shadow),
|
||||
+ wgpu::BindingResource::TextureView(self.volumes.cloud_dist_view()),
|
||||
];
|
||||
let entries: Vec<_> = resources
|
||||
.into_iter()
|
||||
diff --git a/crates/horizon-client/src/gfx/hd/volumes.rs b/crates/horizon-client/src/gfx/hd/volumes.rs
|
||||
index a338d756c..d3b55e2f2 100644
|
||||
--- a/crates/horizon-client/src/gfx/hd/volumes.rs
|
||||
+++ b/crates/horizon-client/src/gfx/hd/volumes.rs
|
||||
@@ -26,8 +26,8 @@
|
||||
|
||||
use super::noise as hd_noise;
|
||||
use super::{
|
||||
- DEPTH, FLOAT, Hd, SAMPLER, SHADOW_ARRAY, SHADOW_SAMPLER, UNIFORM, entries, fullscreen, module,
|
||||
- stamp, stamped_pass, target,
|
||||
+ DEPTH, FLOAT, Hd, SAMPLER, SHADOW_ARRAY, SHADOW_SAMPLER, UNFILTERED, UNIFORM, entries,
|
||||
+ fullscreen, module, stamp, stamped_pass, target,
|
||||
};
|
||||
|
||||
pub const CLOUD_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
|
||||
@@ -86,6 +86,9 @@ pub struct HdVolumes {
|
||||
shadow_sampler: wgpu::Sampler,
|
||||
/// The clouds' half-resolution target and its history (read one, write the other).
|
||||
pub clouds: [wgpu::TextureView; 2],
|
||||
+ /// How far each cloud texel's ray was marched (ping-pong with `clouds`).
|
||||
+ cloud_dist: [wgpu::TextureView; 2],
|
||||
+ cloud_depth: Option<wgpu::TextureView>,
|
||||
flip: usize,
|
||||
history_ready: bool,
|
||||
pub cloud_shadow: wgpu::TextureView,
|
||||
@@ -111,9 +114,10 @@ const D3: wgpu::BindingType = wgpu::BindingType::Texture {
|
||||
};
|
||||
|
||||
/// `fs_cloud`'s group 0: frame, 3D noise, 2D noise, clamped and repeating samplers, the sky
|
||||
-/// texture, the previous cloud frame.
|
||||
-pub(super) const CLOUD_BINDINGS: [wgpu::BindingType; 8] =
|
||||
- [UNIFORM, D3, FLOAT, SAMPLER, SAMPLER, FLOAT, FLOAT, FLOAT];
|
||||
+/// texture, the previous cloud frame, the smoke atlas, the world's depth, the previous march lengths.
|
||||
+pub(super) const CLOUD_BINDINGS: [wgpu::BindingType; 10] = [
|
||||
+ UNIFORM, D3, FLOAT, SAMPLER, SAMPLER, FLOAT, FLOAT, FLOAT, DEPTH, UNFILTERED,
|
||||
+];
|
||||
/// `fs_cloud_shadow`'s group 0: as the clouds' without the history.
|
||||
pub(super) const CLOUD_SHADOW_BINDINGS: [wgpu::BindingType; 6] =
|
||||
[UNIFORM, D3, FLOAT, SAMPLER, SAMPLER, FLOAT];
|
||||
@@ -218,15 +222,40 @@ impl HdVolumes {
|
||||
let cloud_module = module(device, "HD clouds", &cloud_src);
|
||||
let shadow_module = module(device, "HD cloud shadows", &shadow_src);
|
||||
let fog_module = module(device, "HD fog", &fog_body());
|
||||
- let cloud_pipeline = fullscreen(
|
||||
- device,
|
||||
- "HD clouds",
|
||||
- &cloud_layout,
|
||||
- &cloud_module,
|
||||
- "fs_cloud",
|
||||
- CLOUD_FORMAT,
|
||||
- None,
|
||||
- );
|
||||
+ let cloud_pipeline = {
|
||||
+ let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
+ label: Some("HD clouds"),
|
||||
+ bind_group_layouts: &[Some(&cloud_layout)],
|
||||
+ ..Default::default()
|
||||
+ });
|
||||
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
+ label: Some("HD clouds"),
|
||||
+ layout: Some(&layout),
|
||||
+ vertex: wgpu::VertexState {
|
||||
+ module: &cloud_module,
|
||||
+ entry_point: Some("vs_full"),
|
||||
+ compilation_options: Default::default(),
|
||||
+ buffers: &[],
|
||||
+ },
|
||||
+ fragment: Some(wgpu::FragmentState {
|
||||
+ module: &cloud_module,
|
||||
+ entry_point: Some("fs_cloud"),
|
||||
+ compilation_options: Default::default(),
|
||||
+ targets: &[CLOUD_FORMAT, FOG_DIST_FORMAT].map(|format| {
|
||||
+ Some(wgpu::ColorTargetState {
|
||||
+ format,
|
||||
+ blend: None,
|
||||
+ write_mask: wgpu::ColorWrites::ALL,
|
||||
+ })
|
||||
+ }),
|
||||
+ }),
|
||||
+ primitive: Default::default(),
|
||||
+ depth_stencil: None,
|
||||
+ multisample: Default::default(),
|
||||
+ multiview_mask: None,
|
||||
+ cache: None,
|
||||
+ })
|
||||
+ };
|
||||
let shadow_pipeline = fullscreen(
|
||||
device,
|
||||
"HD cloud shadows",
|
||||
@@ -286,6 +315,11 @@ impl HdVolumes {
|
||||
one("HD clouds", [1, 1], CLOUD_FORMAT),
|
||||
one("HD clouds", [1, 1], CLOUD_FORMAT),
|
||||
],
|
||||
+ cloud_dist: [
|
||||
+ one("HD cloud distance", [1, 1], FOG_DIST_FORMAT),
|
||||
+ one("HD cloud distance", [1, 1], FOG_DIST_FORMAT),
|
||||
+ ],
|
||||
+ cloud_depth: None,
|
||||
flip: 0,
|
||||
history_ready: false,
|
||||
cloud_shadow: one(
|
||||
@@ -316,6 +350,10 @@ impl HdVolumes {
|
||||
one("HD clouds", CLOUD_FORMAT),
|
||||
one("HD clouds", CLOUD_FORMAT),
|
||||
];
|
||||
+ self.cloud_dist = [
|
||||
+ one("HD cloud distance", FOG_DIST_FORMAT),
|
||||
+ one("HD cloud distance", FOG_DIST_FORMAT),
|
||||
+ ];
|
||||
self.fog = one("HD fog", FOG_FORMAT);
|
||||
self.fog_dist = one("HD fog distance", FOG_DIST_FORMAT);
|
||||
self.history_ready = false;
|
||||
@@ -342,6 +380,11 @@ impl HdVolumes {
|
||||
&self.clouds[self.flip]
|
||||
}
|
||||
|
||||
+ /// The march lengths that go with `cloud_view`.
|
||||
+ pub fn cloud_dist_view(&self) -> &wgpu::TextureView {
|
||||
+ &self.cloud_dist[self.flip]
|
||||
+ }
|
||||
+
|
||||
fn group(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
@@ -372,7 +415,12 @@ impl HdVolumes {
|
||||
frame: &wgpu::Buffer,
|
||||
sky_lut: &wgpu::TextureView,
|
||||
atlas: &wgpu::TextureView,
|
||||
+ depth: &wgpu::TextureView,
|
||||
) {
|
||||
+ if self.cloud_depth.as_ref() != Some(depth) {
|
||||
+ self.cloud_groups = None;
|
||||
+ self.cloud_depth = Some(depth.clone());
|
||||
+ }
|
||||
if self.shadow_group.is_none() {
|
||||
let layout = self.shadow_layout.clone();
|
||||
let (n3, n2, clamp, repeat) = (
|
||||
@@ -405,6 +453,7 @@ impl HdVolumes {
|
||||
self.repeat.clone(),
|
||||
);
|
||||
let hist = self.clouds.clone();
|
||||
+ let hist_dist = self.cloud_dist.clone();
|
||||
let groups = [0, 1].map(|read| {
|
||||
self.group(
|
||||
device,
|
||||
@@ -419,6 +468,8 @@ impl HdVolumes {
|
||||
wgpu::BindingResource::TextureView(sky_lut),
|
||||
wgpu::BindingResource::TextureView(&hist[read]),
|
||||
wgpu::BindingResource::TextureView(atlas),
|
||||
+ wgpu::BindingResource::TextureView(depth),
|
||||
+ wgpu::BindingResource::TextureView(&hist_dist[read]),
|
||||
],
|
||||
)
|
||||
});
|
||||
@@ -455,7 +506,7 @@ impl HdVolumes {
|
||||
stamped_pass(
|
||||
encoder,
|
||||
"HD clouds",
|
||||
- &[&self.clouds[write]],
|
||||
+ &[&self.clouds[write], &self.cloud_dist[write]],
|
||||
&self.cloud_pipeline,
|
||||
&groups[read],
|
||||
stamp,
|
||||
@@ -525,8 +576,13 @@ impl Hd {
|
||||
depth: &wgpu::TextureView,
|
||||
) {
|
||||
if self.clouds_active {
|
||||
- self.volumes
|
||||
- .prepare_clouds(device, &self.frame, &self.sky.lut, &self.smoke.atlas);
|
||||
+ self.volumes.prepare_clouds(
|
||||
+ device,
|
||||
+ &self.frame,
|
||||
+ &self.sky.lut,
|
||||
+ &self.smoke.atlas,
|
||||
+ depth,
|
||||
+ );
|
||||
self.volumes
|
||||
.draw_cloud_shadow(encoder, stamp(&mut self.timing, "hd cloud shadows"));
|
||||
self.volumes
|
||||
@@ -570,3 +626,48 @@ pub fn shader_sources() -> Vec<(&'static str, String, &'static [wgpu::BindingTyp
|
||||
("fog", fog_body(), &FOG_BINDINGS),
|
||||
]
|
||||
}
|
||||
+
|
||||
+#[cfg(test)]
|
||||
+mod tests {
|
||||
+ use super::*;
|
||||
+
|
||||
+ /// Every world-anchored noise lookup divides the world coordinate (the camera's x, z wrapped to
|
||||
+ /// 65536) by a scale that divides 65536, or the pattern jumps where the wrap happens (x or z = 0).
|
||||
+ #[test]
|
||||
+ fn noise_scales_divide_the_camera_wrap() {
|
||||
+ let mut found = 0;
|
||||
+ for (name, src) in [("cloud_common", CLOUD_COMMON), ("fog", FOG)] {
|
||||
+ for key in ["w / ", "p3 / "] {
|
||||
+ for part in src.split(key).skip(1) {
|
||||
+ let num: String = part
|
||||
+ .chars()
|
||||
+ .take_while(|c| c.is_ascii_digit() || *c == '.')
|
||||
+ .collect();
|
||||
+ let Ok(scale) = num.parse::<f64>() else {
|
||||
+ continue;
|
||||
+ };
|
||||
+ found += 1;
|
||||
+ assert!(
|
||||
+ scale >= 1.0 && scale.fract() == 0.0 && 65536 % (scale as u64) == 0,
|
||||
+ "{name}: the scale {scale} does not divide 65536"
|
||||
+ );
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ assert!(found >= 8, "found only {found} noise lookups");
|
||||
+ }
|
||||
+
|
||||
+ /// The cloud noise's height is measured from the layer's base, not from the camera: the same
|
||||
+ /// world point gives the same noise coordinate at any camera height.
|
||||
+ #[test]
|
||||
+ fn cloud_noise_height_is_anchored_to_the_layer() {
|
||||
+ assert!(CLOUD_COMMON.contains("pos.y - frame.clouds.x"));
|
||||
+ // frame.clouds.x = CLOUD_BASE - camera height: y_world - CLOUD_BASE however high the camera.
|
||||
+ for cam in [64.0f32, 210.0, 1000.0] {
|
||||
+ let world_y = 230.0f32;
|
||||
+ let rel = world_y - cam;
|
||||
+ let base_rel = CLOUD_BASE - cam;
|
||||
+ assert!((rel - base_rel - (world_y - CLOUD_BASE)).abs() < 1e-3);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
--
|
||||
2.54.0 (Apple Git-157)
|
||||
|
||||
Reference in New Issue
Block a user