Files
Papay-Shooter/assets/shaders/anime_sky.gdshader
T
2026-08-02 02:20:02 -04:00

91 lines
3.9 KiB
Plaintext

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;
}