Enhanced sky atmosphere with DBC-driven colors, sun lighting, and zone weather

- Skybox now uses DBC sky colors (skyTop/skyMiddle/skyBand1/skyBand2) instead
  of hardcoded C++ color curves, with 3-band gradient and Rayleigh/Mie scattering
- Clouds receive sun direction for lit edges, self-shadowing, and silver lining
- Fixed sun quad box artifact with proper edge fade in celestial shader
- Lens flare attenuated by fog, cloud density, and weather intensity
- Replaced garish green/purple lens flare ghosts with warm natural palette
- Added zone-based weather system for single-player mode with per-zone rain/snow
  configuration, probability-based activation, and smooth intensity transitions
- Server SMSG_WEATHER remains authoritative when connected to a server
This commit is contained in:
Kelsi 2026-02-22 23:20:13 -08:00
parent 085fd09b9d
commit 6563eebb60
18 changed files with 434 additions and 252 deletions

View file

@ -26,9 +26,21 @@ float valueNoise(vec2 p) {
void main() {
vec2 uv = TexCoord - 0.5;
float dist = length(uv);
float disc = smoothstep(0.42, 0.38, dist);
float glow = exp(-dist * dist * 12.0) * 0.6;
// Hard disc with smooth edge
float disc = smoothstep(0.42, 0.35, dist);
// Soft glow that fades to zero well within quad bounds
// At dist=0.5 (quad edge), this should be negligible
float glow = exp(-dist * dist * 18.0) * 0.5;
// Combine disc and glow
float alpha = max(disc, glow) * push.intensity;
// Fade to zero at quad edges to prevent visible box
float edgeFade = 1.0 - smoothstep(0.4, 0.5, dist);
alpha *= edgeFade;
vec3 color = push.celestialColor.rgb;
// Animated haze/turbulence overlay for the sun disc

Binary file not shown.

View file

@ -1,35 +1,42 @@
#version 450
layout(push_constant) uniform Push {
vec4 cloudColor;
float density;
float windOffset;
vec4 cloudColor; // xyz = DBC-derived base cloud color, w = unused
vec4 sunDirDensity; // xyz = sun direction, w = density
vec4 windAndLight; // x = windOffset, y = sunIntensity, z = ambient, w = unused
} push;
layout(location = 0) in vec3 vWorldDir;
layout(location = 0) out vec4 outColor;
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
// --- Gradient noise (smoother than hash-based) ---
vec2 hash2(vec2 p) {
p = vec2(dot(p, vec2(127.1, 311.7)),
dot(p, vec2(269.5, 183.3)));
return fract(sin(p) * 43758.5453);
}
float noise(vec2 p) {
float gradientNoise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
// Quintic interpolation for smoother results
vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
float a = dot(hash2(i + vec2(0.0, 0.0)) * 2.0 - 1.0, f - vec2(0.0, 0.0));
float b = dot(hash2(i + vec2(1.0, 0.0)) * 2.0 - 1.0, f - vec2(1.0, 0.0));
float c = dot(hash2(i + vec2(0.0, 1.0)) * 2.0 - 1.0, f - vec2(0.0, 1.0));
float d = dot(hash2(i + vec2(1.0, 1.0)) * 2.0 - 1.0, f - vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y) * 0.5 + 0.5;
}
float fbm(vec2 p) {
float val = 0.0;
float amp = 0.5;
for (int i = 0; i < 4; i++) {
val += amp * noise(p);
for (int i = 0; i < 6; i++) {
val += amp * gradientNoise(p);
p *= 2.0;
amp *= 0.5;
}
@ -38,26 +45,60 @@ float fbm(vec2 p) {
void main() {
vec3 dir = normalize(vWorldDir);
float altitude = dir.z; // Z is up in the Z-up world coordinate system
float altitude = dir.z;
if (altitude < 0.0) discard;
vec2 uv = dir.xy / (altitude + 0.001); // XY is the horizontal plane
uv += push.windOffset;
vec3 sunDir = push.sunDirDensity.xyz;
float density = push.sunDirDensity.w;
float windOffset = push.windAndLight.x;
float sunIntensity = push.windAndLight.y;
float ambient = push.windAndLight.z;
vec2 uv = dir.xy / (altitude + 0.001);
uv += windOffset;
// --- 6-octave FBM for cloud shape ---
float cloud1 = fbm(uv * 0.8);
float cloud2 = fbm(uv * 1.6 + 5.0);
float cloud = cloud1 * 0.7 + cloud2 * 0.3;
cloud = smoothstep(0.35, 0.65, cloud) * push.density;
float edgeBreak = noise(uv * 4.0);
cloud *= smoothstep(0.2, 0.5, edgeBreak);
// Coverage control: base coverage with detail erosion
float baseCoverage = smoothstep(0.30, 0.55, cloud);
float detailErosion = gradientNoise(uv * 4.0);
cloud = baseCoverage * smoothstep(0.2, 0.5, detailErosion);
cloud *= density;
// Horizon fade
float horizonFade = smoothstep(0.0, 0.15, altitude);
cloud *= horizonFade;
if (cloud < 0.01) discard;
// --- Sun lighting on clouds ---
// Sun dot product for view-relative brightness
float sunDot = max(dot(vec3(0.0, 0.0, 1.0), sunDir), 0.0);
// Self-shadowing: sample noise offset toward sun direction, darken if occluded
float lightSample = fbm((uv + sunDir.xy * 0.05) * 0.8);
float shadow = smoothstep(0.3, 0.7, lightSample);
// Base lit color: mix dark (shadow) and bright (sunlit) based on shadow and sun
vec3 baseColor = push.cloudColor.rgb;
vec3 shadowColor = baseColor * (ambient * 0.8);
vec3 litColor = baseColor * (ambient + sunIntensity * 0.6);
vec3 cloudRgb = mix(shadowColor, litColor, shadow * sunDot);
// Add ambient fill so clouds aren't too dark
cloudRgb = mix(baseColor * ambient, cloudRgb, 0.7 + 0.3 * sunIntensity);
// --- Silver lining effect at cloud edges ---
float edgeLight = smoothstep(0.0, 0.3, cloud) * (1.0 - smoothstep(0.3, 0.8, cloud));
cloudRgb += vec3(1.0, 0.95, 0.9) * edgeLight * sunDot * sunIntensity * 0.4;
// --- Edge softness for alpha ---
float edgeSoftness = smoothstep(0.0, 0.3, cloud);
float alpha = cloud * edgeSoftness;
if (alpha < 0.01) discard;
outColor = vec4(push.cloudColor.rgb, alpha);
outColor = vec4(cloudRgb, alpha);
}

Binary file not shown.

View file

@ -14,9 +14,11 @@ layout(set = 0, binding = 0) uniform PerFrame {
};
layout(push_constant) uniform Push {
vec4 horizonColor;
vec4 zenithColor;
float timeOfDay;
vec4 zenithColor; // DBC skyTopColor
vec4 midColor; // DBC skyMiddleColor
vec4 horizonColor; // DBC skyBand1Color
vec4 fogColorPush; // DBC skyBand2Color
vec4 sunDirAndTime; // xyz = sun direction, w = timeOfDay
} push;
layout(location = 0) in vec2 TexCoord;
@ -25,27 +27,71 @@ layout(location = 0) out vec4 outColor;
void main() {
// Reconstruct world-space ray direction from screen position.
// TexCoord is [0,1]^2; convert to NDC [-1,1]^2.
float ndcX = TexCoord.x * 2.0 - 1.0;
float ndcY = -(TexCoord.y * 2.0 - 1.0); // flip Y: Vulkan NDC Y-down, but projection already flipped
float ndcY = -(TexCoord.y * 2.0 - 1.0);
// Unproject to view space using focal lengths from projection matrix.
// projection[0][0] = 2*near/(right-left) = 1/tan(fovX/2)
// projection[1][1] = 2*near/(top-bottom) (already negated for Vulkan Y-flip)
// We want the original magnitude, so take abs to get the focal length.
vec3 viewDir = vec3(ndcX / projection[0][0],
ndcY / abs(projection[1][1]),
-1.0);
// Rotate to world space: view = R*T, so R^-1 = R^T = transpose(mat3(view))
mat3 invViewRot = transpose(mat3(view));
vec3 worldDir = normalize(invViewRot * viewDir);
// worldDir.z = sin(elevation); +1 = zenith, 0 = horizon, -1 = nadir
float t = clamp(worldDir.z, 0.0, 1.0);
t = pow(t, 1.5);
vec3 sky = mix(push.horizonColor.rgb, push.zenithColor.rgb, t);
float scatter = max(0.0, 1.0 - t * 2.0) * 0.15;
sky += vec3(scatter * 0.8, scatter * 0.4, scatter * 0.1);
vec3 sunDir = push.sunDirAndTime.xyz;
float timeOfDay = push.sunDirAndTime.w;
// Elevation: +1 = zenith, 0 = horizon, -1 = nadir
float elev = worldDir.z;
float elevClamped = clamp(elev, 0.0, 1.0);
// --- 3-band sky gradient using DBC colors ---
// Zenith dominates upper sky, mid color fills the middle,
// horizon band at the bottom with a thin fog fringe.
vec3 sky;
if (elevClamped > 0.4) {
// Upper sky: mid -> zenith
float t = (elevClamped - 0.4) / 0.6;
sky = mix(push.midColor.rgb, push.zenithColor.rgb, t);
} else if (elevClamped > 0.05) {
// Lower sky: horizon -> mid (wide band)
float t = (elevClamped - 0.05) / 0.35;
sky = mix(push.horizonColor.rgb, push.midColor.rgb, t);
} else {
// Thin fog fringe right at horizon
float t = elevClamped / 0.05;
sky = mix(push.fogColorPush.rgb, push.horizonColor.rgb, t);
}
// --- Below-horizon darkening (nadir) ---
if (elev < 0.0) {
float nadirFade = clamp(-elev * 3.0, 0.0, 1.0);
vec3 nadirColor = push.fogColorPush.rgb * 0.3;
sky = mix(push.fogColorPush.rgb, nadirColor, nadirFade);
}
// --- Rayleigh-like scattering (subtle warm glow near sun) ---
float sunDot = max(dot(worldDir, sunDir), 0.0);
float sunAboveHorizon = clamp(sunDir.z, 0.0, 1.0);
float rayleighStrength = pow(1.0 - elevClamped, 3.0) * 0.15;
vec3 scatterColor = mix(vec3(0.8, 0.45, 0.15), vec3(0.3, 0.5, 1.0), elevClamped);
sky += scatterColor * rayleighStrength * sunDot * sunAboveHorizon;
// --- Mie-like forward scatter (sun disk glow) ---
float mieSharp = pow(sunDot, 64.0) * 0.4;
float mieSoft = pow(sunDot, 8.0) * 0.1;
vec3 sunGlowColor = mix(vec3(1.0, 0.85, 0.55), vec3(1.0, 1.0, 0.95), elevClamped);
sky += sunGlowColor * (mieSharp + mieSoft) * sunAboveHorizon;
// --- Subtle horizon haze ---
float hazeDensity = exp(-elevClamped * 12.0) * 0.06;
sky += push.horizonColor.rgb * hazeDensity * sunAboveHorizon;
// --- Night: slight moonlight tint ---
if (sunDir.z < 0.0) {
float moonlight = clamp(-sunDir.z * 0.5, 0.0, 0.15);
sky += vec3(0.02, 0.03, 0.08) * moonlight;
}
outColor = vec4(sky, 1.0);
}

Binary file not shown.