plain text 69 lines
shader_type spatial;
render_mode specular_disabled;
// The jungle short-grass ground. A flat plane on its own reads as a dead slab, so this
// breaks the surface into flat patches of two grass greens with a toon-quantised value
// noise — reading as clumped short grass from the close follow-camera — and bands the key
// light into the same flat tones the units wear (cel.gdshader), so a unit and the ground
// it stands on share one lighting treatment. World-space, so the pattern tiles evenly
// across the whole arena regardless of where the plane sits.
// The two grass tones the patches blend between — a deep shade green and a brighter one.
uniform vec3 grass_low : source_color = vec3(0.13, 0.30, 0.12);
uniform vec3 grass_high : source_color = vec3(0.22, 0.45, 0.18);
// How tight the grass clumps are: the broad-patch frequency and the finer speckle on top,
// per world unit, and how many flat steps the blend is quantised into for the toon look.
uniform float patch_scale = 0.004;
uniform float speckle_scale = 0.02;
uniform float patch_steps = 4.0;
// The two light levels the key light steps through — the same three-tone ramp the units
// wear, so the ground tone sits in the same family as the bodies on it.
const float MID_TONE = 0.5;
const float LOW_CUT = 0.25;
const float HIGH_CUT = 0.6;
varying vec3 world_pos;
float hash(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
// Smoothed value noise in [0, 1] — bilinear blend of four lattice hashes, the building
// block the grass dapple is layered from.
float value_noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
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));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
void vertex() {
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
}
void fragment() {
float broad = value_noise(world_pos.xz * patch_scale);
float fine = value_noise(world_pos.xz * speckle_scale);
float t = clamp(broad * 0.7 + fine * 0.3, 0.0, 1.0);
t = floor(t * patch_steps) / (patch_steps - 1.0);
ALBEDO = mix(grass_low, grass_high, t);
ROUGHNESS = 1.0;
METALLIC = 0.0;
}
// Bands the key light into three flat tones, the shadow side left to the ambient fill —
// the same step the units take, so the field lights as one surface.
void light() {
float ndl = max(dot(NORMAL, LIGHT), 0.0);
float tone = step(LOW_CUT, ndl) * MID_TONE + step(HIGH_CUT, ndl) * (1.0 - MID_TONE);
DIFFUSE_LIGHT += ALBEDO * LIGHT_COLOR * ATTENUATION * tone;
}