shader_type sky; // Cel sky: a painted gradient with rounded, flat-toned anime clouds. // // ProceduralSkyMaterial gives a smooth two-colour ramp, and a smooth ramp over // 40% of the frame is exactly what tools/levels.py was reporting as dead%: // somewhere between a quarter and a third of every shot was a single flat // shade with nothing in it. Sky is the cheapest real estate in the picture and // it was empty. // // The clouds are drawn the way an anime background artist draws them — a // silhouette filled with two or three flat tones and a crisp edge, NOT a soft // volumetric puff. That is both the correct style and the cheap option. uniform vec3 top_color : source_color = vec3(0.18, 0.40, 0.85); uniform vec3 horizon_color : source_color = vec3(0.72, 0.88, 0.98); uniform vec3 ground_color : source_color = vec3(0.24, 0.22, 0.30); uniform vec3 cloud_color : source_color = vec3(1.0, 0.99, 0.97); uniform vec3 cloud_shadow : source_color = vec3(0.72, 0.75, 0.88); // Raise to thin the clouds out. This is a threshold on the noise, so it is the // knob for "how much sky is covered", not a density. uniform float cloud_cover : hint_range(0.0, 1.0) = 0.52; uniform float cloud_scale : hint_range(0.2, 8.0) = 1.6; uniform float cloud_softness : hint_range(0.001, 0.2) = 0.035; // Slow. This is a competitive shooter and anything the eye can track in the // sky is a distraction; it only needs to move enough that the sky is not a // photograph. uniform float drift : hint_range(0.0, 0.02) = 0.0022; uniform float horizon_falloff : hint_range(0.1, 2.0) = 0.45; uniform float sun_halo : hint_range(0.0, 2.0) = 0.35; #include "res://assets/shaders/anime_clouds.gdshaderinc" void sky() { float up = clamp(EYEDIR.y, 0.0, 1.0); float down = clamp(-EYEDIR.y, 0.0, 1.0); vec3 col = EYEDIR.y >= 0.0 ? mix(horizon_color, top_color, pow(up, horizon_falloff)) : mix(horizon_color, ground_color, pow(down, 0.62)); // Soft halo around the sun. The procedural sky drew a disk here; a broad // glow reads better under the filmic shoulder and does not clip. if (LIGHT0_ENABLED) { float d = clamp(dot(EYEDIR, LIGHT0_DIRECTION), 0.0, 1.0); col += LIGHT0_COLOR * pow(d, 24.0) * sun_halo; } if (EYEDIR.y > 0.005) { // Project the dome onto a plane overhead, so clouds compress toward the // horizon the way real ones do instead of smearing across the zenith. vec2 uv = EYEDIR.xz / (EYEDIR.y + 0.18) * cloud_scale + vec2(TIME * drift, 0.0); float n = cloud_field(uv); // The edge remains clean enough for the anime style, but it follows a // layered organic field instead of revealing primitive circles. float edge = max(cloud_softness, fwidth(n) * 1.35); float shape = smoothstep(cloud_cover - edge, cloud_cover + edge, n); vec3 sun_direction = LIGHT0_ENABLED ? normalize(LIGHT0_DIRECTION) : normalize(vec3(0.45, 0.55, -0.70)); vec3 sun_color = LIGHT0_ENABLED ? LIGHT0_COLOR : vec3(1.0, 0.91, 0.72); float facing = cloud_sun_response(uv, sun_direction); float core = smoothstep( cloud_cover + 0.045 - edge, cloud_cover + 0.155 + edge, n); float carving = smoothstep( 0.38, 0.64, cloud_detail(uv + vec2(2.4, 9.7))); // Three painted tones respond to the actual DirectionalLight: a colored // lee side, a broad lit face, and a narrow sunward silver lining. float lit_face = clamp( 0.12 + core * 0.25 + facing * 0.58 + carving * 0.05, 0.0, 1.0); vec3 lit_color = mix(cloud_color, sun_color, 0.18); vec3 cloud = mix(cloud_shadow, lit_color, lit_face); float boundary = 1.0 - smoothstep( cloud_cover + edge, cloud_cover + 0.105 + edge, n); float silver_lining = boundary * facing * facing * mix(0.10, 0.24, clamp(sun_direction.y, 0.0, 1.0)); cloud += sun_color * silver_lining; // Fade out near the horizon, where the projection stretches a cloud // into an infinitely long streak. col = mix(col, cloud, shape * smoothstep(0.0, 0.22, EYEDIR.y)); } COLOR = col; }