perf(sky): ~10x cheaper night-sky lattice — one-round cell hash, 2x2x2 block, bit-sliced stars
The owner measured 63% GPU at night vs 28% by day: the starfield lattice was the whole difference. Three lossless cuts: - one packed-multiply hash round per cell instead of ~9 chained PCG rounds (position/presence bit-sliced from one result, brightness/tint from a second); - the 3x3x3 neighbourhood sweep becomes the 2x2x2 block around the sample — a star's visible footprint (a few px) is far smaller than a lattice cell on screen, so any star outside that block is over a full cell (>=15 px) away and contributes nothing; - background mottle drops its invisible third octave. Same densities, sizes, colours, and look; star positions reshuffle (procedural layout, not authored). sky.frag.spv re-pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9fcc3f4870
commit
9cf15e1f13
4 changed files with 43 additions and 31 deletions
|
|
@ -88,22 +88,33 @@ vec3 nsTint(float r)
|
|||
return vec3(0.70, 0.82, 1.00); // hot blue
|
||||
}
|
||||
|
||||
// One-round packed 3D lattice hash (three odd-constant multiplies + a
|
||||
// single PCG finalise). The 2026-08-23 night GPU measurement (63% night vs
|
||||
// 28% day at the owner's card) showed the first version's ~9 chained PCG
|
||||
// rounds per cell were the whole bill; one round is statistically ample for
|
||||
// star placement.
|
||||
uint nsCellHash(ivec3 c, uint seed)
|
||||
{
|
||||
uint h = uint(c.x + 1024) * 0x8DA6B343u
|
||||
^ uint(c.y + 1024) * 0xD8163841u
|
||||
^ uint(c.z + 1024) * 0xCB1AB31Fu
|
||||
^ seed;
|
||||
return nsPcg(h);
|
||||
}
|
||||
|
||||
float nsVnoise(vec3 p, uint seed)
|
||||
{
|
||||
vec3 i = floor(p);
|
||||
vec3 f = fract(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
ivec3 ii = ivec3(i);
|
||||
float acc = 0.0;
|
||||
for (int c = 0; c < 8; ++c)
|
||||
{
|
||||
vec3 o = vec3(float(c & 1), float((c >> 1) & 1), float((c >> 2) & 1));
|
||||
vec3 q = i + o;
|
||||
uint h = nsPcg(seed
|
||||
^ nsPcg(uint(int(q.x) + 512)
|
||||
^ nsPcg(uint(int(q.y) + 512)
|
||||
^ uint(int(q.z) + 512))));
|
||||
vec3 w = mix(1.0 - f, f, o);
|
||||
acc += nsRand(h) * w.x * w.y * w.z;
|
||||
ivec3 o = ivec3(c & 1, (c >> 1) & 1, (c >> 2) & 1);
|
||||
float v = nsRand(nsCellHash(ii + o, seed));
|
||||
vec3 wf = mix(1.0 - f, f, vec3(o)); // trilinear weights
|
||||
acc += v * wf.x * wf.y * wf.z;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
|
@ -116,7 +127,6 @@ vec3 nsStarTier(vec3 dir, vec3 ex, vec3 ey, uint seed, float cells,
|
|||
// 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
|
||||
|
|
@ -127,32 +137,35 @@ vec3 nsStarTier(vec3 dir, vec3 ex, vec3 ey, uint seed, float cells,
|
|||
float c = dot(ey, ey);
|
||||
float det = max(a * c - b * b, 1e-14);
|
||||
|
||||
// A star's visible footprint (a few px) is far smaller than a lattice
|
||||
// cell on screen, so only the 2x2x2 block around the sample can hold a
|
||||
// star that contributes — the 3x3x3 sweep was pure waste (GPU bill,
|
||||
// 2026-08-23): any star outside this block is over a full cell away.
|
||||
ivec3 base = ivec3(floor(g - 0.5));
|
||||
|
||||
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)
|
||||
for (int ci = 0; ci < 8; ++ci)
|
||||
{
|
||||
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);
|
||||
uint h6 = nsPcg(h5);
|
||||
vec3 sdir = normalize(cc + vec3(nsRand(h2), nsRand(h3), nsRand(h4)));
|
||||
ivec3 cc = base + ivec3(ci & 1, (ci >> 1) & 1, (ci >> 2) & 1);
|
||||
uint h = nsCellHash(cc, seed ^ uint(cells));
|
||||
// Bit-slice ONE hash for presence + position; one more round for
|
||||
// brightness + tint. (Was five chained rounds per star.)
|
||||
if (float(h & 0xFFu) > density * 255.0) continue;
|
||||
vec3 jitter = vec3(
|
||||
float((h >> 8) & 0xFFu),
|
||||
float((h >> 16) & 0xFFu),
|
||||
float((h >> 24) & 0xFFu)) * (1.0 / 255.0);
|
||||
vec3 sdir = normalize(vec3(cc) + jitter);
|
||||
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);
|
||||
uint h2 = nsPcg(h);
|
||||
float t = float(h2 & 0xFFFFu) * (1.0 / 65535.0);
|
||||
float br = mix(bMin, bMax, t * t * t);
|
||||
vec3 tint = nsTint(nsRand(h6));
|
||||
vec3 tint = nsTint(float((h2 >> 16) & 0xFFu) * (1.0 / 255.0));
|
||||
// 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.
|
||||
|
|
@ -169,9 +182,8 @@ vec3 nightSky(vec3 dir, uint seed)
|
|||
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)
|
||||
+ 0.30 * nsVnoise(dir * 7.0, seed ^ 0x85EBCA6Bu)
|
||||
+ 0.15 * nsVnoise(dir * 15.0, seed ^ 0xC2B2AE35u);
|
||||
float n = 0.62 * nsVnoise(dir * 3.0, seed ^ 0x9E3779B9u)
|
||||
+ 0.38 * nsVnoise(dir * 7.0, seed ^ 0x85EBCA6Bu);
|
||||
vec3 rgb = (0.004 + 0.009 * n) * vec3(0.85, 0.92, 1.10);
|
||||
|
||||
// The star carpet: three density tiers plus sparse bright standouts,
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@
|
|||
},
|
||||
{
|
||||
"stage": "frag",
|
||||
"sourceSha256": "e7ccdb4d5fece2ceeb48eb43aaa827424df2b68614987eeb3dfb7880dff8ea83",
|
||||
"sourceSha256": "5785633bc936fad68c97247f6ef8a22e5bcde0fe6d74a2f4676b844a2d7600c5",
|
||||
"compiled": true
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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"] = "e307a7f3cf84aada86db7c2859c55841a650b46732f12149986562cef0a90ca8",
|
||||
["sky.frag.spv"] = "1c4ae77056837cbdc188f8cfcc4b0e8851647cdfaf398f25d8c8ff489ef84d57",
|
||||
["sky.vert.spv"] = "3b51945fa4ff1be1604144df92866bdd47aade22f9dd90267591ef36adb28cde",
|
||||
["terrain_modern.frag.spv"] = "7b3cdb01b837ed77ee20559a81c1ce5c9d5395300efcc072560ab0be3c5a1af9",
|
||||
["terrain_modern.vert.spv"] = "9f4cb221ea6aed94a8d23af6cb8e3f3ed96c3cce6e50d135a72d3b55667b1557",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue