From b8cfee6d08a9ac27131be8950277700f86fd4d0d Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 23 Aug 2026 18:00:28 +0200 Subject: [PATCH] =?UTF-8?q?fix(sky):=20seamless=20night-sky=20star=20latti?= =?UTF-8?q?ce=20=E2=80=94=20no=20face=20seams,=20no=20flare,=20round=20pix?= =?UTF-8?q?el-exact=20stars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first night-sky gate (2026-08-23 screenshot) showed three defects: glowing beams along the cube-face boundaries (fwidth blowup where adjacent pixels land on different faces lights every neighbourhood star solid), diffraction-spiked standouts the user rejects ('that is in a photo only, not in real sky'), and ellipse-stretched stars from scalar length(fwidth) sizing at oblique view angles. One rewrite removes all three: stars now live on a seamless 3D lattice over the unit sphere (no faces, so no seams by construction), each star resolved through an exact tangent-plane -> screen-pixel 2x2 solve of the direction derivatives (perfectly round, true pixel sizing at every view angle, sharper cores), spikes deleted in favour of a soft round halo on the bright tiers. Guard test updated to pin the new anchors and forbid both fwidth-face grids and spikes; sky.frag.spv re-pinned. Co-Authored-By: Claude Fable 5 --- src/AcDream.App/Rendering/Shaders/sky.frag | 90 ++++++++++-------- .../Shaders/spv/shaders.manifest.json | 2 +- .../Rendering/Shaders/spv/sky.frag.spv | Bin 23624 -> 26316 bytes .../Gpu/Vk/VulkanShaderManifestTests.cs | 2 +- .../Sky/EnhancedNightSkyRuleTests.cs | 10 +- 5 files changed, 58 insertions(+), 46 deletions(-) diff --git a/src/AcDream.App/Rendering/Shaders/sky.frag b/src/AcDream.App/Rendering/Shaders/sky.frag index b84dda24..db25c71e 100644 --- a/src/AcDream.App/Rendering/Shaders/sky.frag +++ b/src/AcDream.App/Rendering/Shaders/sky.frag @@ -108,60 +108,65 @@ float nsVnoise(vec3 p, uint seed) return acc; } -vec3 nsStars(vec2 fuv, uint faceSeed, float cells, float density, - float bMin, float bMax, float sizePx, bool spikes) +vec3 nsStarTier(vec3 dir, vec3 ex, vec3 ey, uint seed, float cells, + float density, float bMin, float bMax, float sizePx, + float haloAmp) { - vec2 g = fuv * cells; - vec2 cellF = floor(g); - // Screen pixels per cell-space unit: the crispness anchor. - float pxPerCell = 1.0 / max(length(fwidth(g)), 1e-6); + // Seamless 3D lattice on the unit sphere: no face boundaries, so no + // seam artefacts by construction (the 2026-08-23 gate screenshot's + // glowing "Y" was cube-face fwidth blowup — do not bring faces back). + vec3 g = dir * cells; + vec3 cellF = floor(g); + + // Exact tangent-plane -> screen-pixel solve: 2x2 normal equations of + // the direction derivatives. Stars come out perfectly ROUND and sized + // in true screen pixels at every view angle — a length(fwidth(...)) + // scalar stretches them into ellipses at oblique angles. + float a = dot(ex, ex); + float b = dot(ex, ey); + float c = dot(ey, ey); + float det = max(a * c - b * b, 1e-14); + vec3 acc = vec3(0.0); + for (int dz = -1; dz <= 1; ++dz) for (int dy = -1; dy <= 1; ++dy) for (int dx = -1; dx <= 1; ++dx) { - vec2 c = cellF + vec2(float(dx), float(dy)); - uint h = nsPcg(faceSeed - ^ nsPcg(uint(int(c.x) + 512) - ^ nsPcg(uint(int(c.y) + 512) - ^ uint(cells)))); + vec3 cc = cellF + vec3(float(dx), float(dy), float(dz)); + uint h = nsPcg(seed + ^ nsPcg(uint(int(cc.x) + 512) + ^ nsPcg(uint(int(cc.y) + 512) + ^ nsPcg(uint(int(cc.z) + 512) ^ uint(cells))))); if (nsRand(h) > density) continue; uint h2 = nsPcg(h); uint h3 = nsPcg(h2); uint h4 = nsPcg(h3); uint h5 = nsPcg(h4); - vec2 pos = c + vec2(nsRand(h2), nsRand(h3)); - float t = nsRand(h4); - float b = mix(bMin, bMax, t * t * t); - vec3 tint = nsTint(nsRand(h5)); - vec2 dPx = (g - pos) * pxPerCell; - float d2 = dot(dPx, dPx); + uint h6 = nsPcg(h5); + vec3 sdir = normalize(cc + vec3(nsRand(h2), nsRand(h3), nsRand(h4))); + vec3 v = sdir - dir * dot(sdir, dir); // tangent-plane offset + float bx = dot(v, ex); + float by = dot(v, ey); + vec2 sPx = vec2(bx * c - by * b, by * a - bx * b) / det; + float d2 = dot(sPx, sPx); + if (d2 > 400.0) continue; // > 20 px: contributes nothing + float t = nsRand(h5); + float br = mix(bMin, bMax, t * t * t); + vec3 tint = nsTint(nsRand(h6)); + // Round gaussian core; optional soft round halo on the bright tiers. + // NO diffraction spikes / lens flare — user-directed 2026-08-23: + // spikes are a photographic artefact, not a naked-eye sky. float star = exp(-d2 / (2.0 * sizePx * sizePx)); - if (spikes) - { - // Axis-aligned diffraction cross + soft halo for the standouts. - float dist = sqrt(d2) + 1e-4; - float fall = exp(-dist / (14.0 * sizePx)); - float arm = exp(-dPx.y * dPx.y * 0.8) + exp(-dPx.x * dPx.x * 0.8); - star += 0.35 * fall * arm; - star += 0.10 * exp(-d2 / (18.0 * sizePx * sizePx)); - } - acc += b * tint * star; + star += haloAmp * exp(-d2 / (24.0 * sizePx * sizePx)); + acc += br * tint * star; } return acc; } vec3 nightSky(vec3 dir, uint seed) { - // Cube-face parameterisation: uniform angular density, no pole pinch. - // Face-edge star continuity is not exact (adjacent faces hash their own - // grids); the sub-pixel population makes any seam visually negligible. - vec3 ad = abs(dir); - uint face; vec2 fuv; float ma; - if (ad.z >= ad.x && ad.z >= ad.y) { face = dir.z > 0.0 ? 4u : 5u; ma = ad.z; fuv = dir.xy; } - else if (ad.x >= ad.y) { face = dir.x > 0.0 ? 0u : 1u; ma = ad.x; fuv = dir.yz; } - else { face = dir.y > 0.0 ? 2u : 3u; ma = ad.y; fuv = dir.xz; } - fuv = fuv / ma * 0.5 + 0.5; - uint fs = nsPcg(seed ^ (face * 0x9E3779B9u)); + vec3 ex = dFdx(dir); + vec3 ey = dFdy(dir); // Faint cool background mottle (0.4%..1.3% - the user-approved level). float n = 0.55 * nsVnoise(dir * 3.0, seed ^ 0x9E3779B9u) @@ -169,11 +174,12 @@ vec3 nightSky(vec3 dir, uint seed) + 0.15 * nsVnoise(dir * 15.0, seed ^ 0xC2B2AE35u); vec3 rgb = (0.004 + 0.009 * n) * vec3(0.85, 0.92, 1.10); - // The star carpet: three density tiers plus sparse spiked standouts. - rgb += nsStars(fuv, fs, 160.0, 0.90, 0.05, 0.35, 0.55, false); - rgb += nsStars(fuv, fs, 64.0, 0.50, 0.20, 0.70, 0.75, false); - rgb += nsStars(fuv, fs, 24.0, 0.35, 0.50, 1.40, 1.05, false); - rgb += nsStars(fuv, fs, 6.0, 0.10, 2.00, 4.00, 1.80, true); + // The star carpet: three density tiers plus sparse bright standouts, + // all crisp round points (see nsStarTier). + rgb += nsStarTier(dir, ex, ey, seed ^ 0x1B873593u, 110.0, 0.85, 0.05, 0.35, 0.50, 0.0); + rgb += nsStarTier(dir, ex, ey, seed ^ 0xCC9E2D51u, 48.0, 0.45, 0.20, 0.70, 0.65, 0.0); + rgb += nsStarTier(dir, ex, ey, seed ^ 0x27D4EB2Fu, 18.0, 0.30, 0.50, 1.40, 0.90, 0.05); + rgb += nsStarTier(dir, ex, ey, seed ^ 0x165667B1u, 6.0, 0.08, 2.00, 4.00, 1.40, 0.10); return rgb; } // ============================================================================ diff --git a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json index 7067f074..1029e342 100644 --- a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json +++ b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json @@ -316,7 +316,7 @@ }, { "stage": "frag", - "sourceSha256": "bf5bc353959636b1c80a9f72dc918cffdd33fb6b7b655149de40422030b4f51a", + "sourceSha256": "a6efd09f396b17ae6a19ffc4be400ac9b035056cbad26aee3447459cd98341f0", "compiled": true } ] diff --git a/src/AcDream.App/Rendering/Shaders/spv/sky.frag.spv b/src/AcDream.App/Rendering/Shaders/spv/sky.frag.spv index df2533c57dd54426bc2df225cd14e181535fb212..54f0a5690a91c24962d27f36fd695dafe14fd2ff 100644 GIT binary patch literal 26316 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 btVbUp>~Fk&Ysz}0udYX~OP_HC*P{JDfKQJm literal 23624 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 diff --git a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs index 11d417a6..4a1a467a 100644 --- a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs @@ -52,7 +52,7 @@ public sealed class VulkanShaderManifestTests // override, retail's GameSky::Draw @0x00506FF0 rule (see // SkyFogRuleTests). A deliberate default-path change, reviewed // with the world-fog-range fix in the same commit. - ["sky.frag.spv"] = "8105984072fc1b9075b5efdffd75d959c4087710d354d50a0e00ee0500462ca7", + ["sky.frag.spv"] = "2d2de4080c7f4b0885aee4441e37a1725d3f30661b45773bcb39383674b1718b", ["sky.vert.spv"] = "3b51945fa4ff1be1604144df92866bdd47aade22f9dd90267591ef36adb28cde", ["terrain_modern.frag.spv"] = "7b3cdb01b837ed77ee20559a81c1ce5c9d5395300efcc072560ab0be3c5a1af9", ["terrain_modern.vert.spv"] = "9f4cb221ea6aed94a8d23af6cb8e3f3ed96c3cce6e50d135a72d3b55667b1557", diff --git a/tests/AcDream.App.Tests/Rendering/Sky/EnhancedNightSkyRuleTests.cs b/tests/AcDream.App.Tests/Rendering/Sky/EnhancedNightSkyRuleTests.cs index 0909720c..48ad7a82 100644 --- a/tests/AcDream.App.Tests/Rendering/Sky/EnhancedNightSkyRuleTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Sky/EnhancedNightSkyRuleTests.cs @@ -23,8 +23,14 @@ public sealed class EnhancedNightSkyRuleTests Assert.Contains("if (uParamA > 0.5)", code, StringComparison.Ordinal); Assert.Contains("nightSky(normalize(vDir), uint(uParamB))", code, StringComparison.Ordinal); // Screen-pixel star sizing is the reason this exists — the stretched - // texture flaw must not creep back in via a fixed-size grid. - Assert.Contains("fwidth(g)", code, StringComparison.Ordinal); + // texture flaw must not creep back in via a fixed-size grid, and the + // cube-face fwidth seams must not return (the 2026-08-23 gate's + // glowing "Y"): the crispness anchor is the seamless 3D-lattice + // tangent-plane solve on the direction derivatives. + Assert.Contains("dFdx(dir)", code, StringComparison.Ordinal); + Assert.DoesNotContain("fwidth(g)", code, StringComparison.Ordinal); + // No diffraction spikes — user-directed: flare is photographic. + Assert.DoesNotContain("spike", code, StringComparison.OrdinalIgnoreCase); } [Fact]