Files

137 lines
5.8 KiB
Plaintext

// Opaque hull for the arena: the deck, the fillets easing it into the walls,
// and the bulkheads surrounding each goal mouth — the lit half of
// ArenaBoundary's visual shell.
//
// Everything is drawn procedurally from the vertex's position in the
// boundary's local space, so field markings are resolution-independent and
// need no decals, UVs or texture authoring. All dimensions arrive as uniforms
// set from ArenaBoundary's constants (see _make_deck_material), so the
// markings cannot drift away from the collision geometry they describe.
shader_type spatial;
render_mode cull_back, diffuse_burley, specular_schlick_ggx;
uniform vec3 deck_color : source_color = vec3(0.085, 0.095, 0.122);
// Kept close to deck_color on purpose: per-plate variation is meant to break
// up a 24x36 m plane, not to reintroduce visible tiling.
uniform vec3 panel_color : source_color = vec3(0.104, 0.116, 0.146);
uniform float panel_variation : hint_range(0.0, 1.0) = 0.4;
uniform vec3 line_color : source_color = vec3(0.62, 0.78, 1.0);
// Must track TeamColors.TEAM_COLORS in scripts/team_colors.gd — shaders can't
// import GDScript consts, and arena_boundary.gd always overrides these at
// runtime, but keep the literal defaults in sync for anyone inspecting the
// shader/material directly.
uniform vec3 team0_color : source_color = vec3(0.48, 0.18, 0.88);
uniform vec3 team1_color : source_color = vec3(0.24, 0.86, 0.42);
uniform vec3 seam_color : source_color = vec3(0.35, 0.75, 1.0);
// Play-volume dimensions, from ArenaBoundary's constants.
uniform float half_x = 12.0;
uniform float half_z = 18.0;
uniform float goal_line_z = 18.0;
uniform float base_radius = 2.0;
uniform float centre_circle_radius = 4.0;
uniform float goal_area_depth = 6.0;
uniform float goal_area_half_width = 6.0;
uniform float line_width = 0.14;
// Each arena's Environment runs glow at hdr_threshold ~1.0 with hdr_scale 2.0,
// so emission much above 1 blooms into a white smear. These stay under it.
uniform float line_emission : hint_range(0.0, 8.0) = 0.65;
uniform float seam_emission : hint_range(0.0, 8.0) = 0.8;
uniform float seam_width = 0.22;
uniform float end_zone_strength : hint_range(0.0, 1.0) = 0.12;
uniform float panel_size = 4.0;
uniform float panel_line_width : hint_range(0.0, 0.5) = 0.05;
// Deliberately not a high metallic value: the local pitch lights and SDFGI
// need enough diffuse response to reveal the plating instead of turning it
// into a dark mirror at grazing angles.
uniform float deck_metallic : hint_range(0.0, 1.0) = 0.3;
uniform float deck_roughness : hint_range(0.0, 1.0) = 0.34;
varying vec3 v_local;
varying vec3 v_local_normal;
void vertex() {
v_local = VERTEX;
v_local_normal = NORMAL;
}
float hash21(vec2 p) {
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}
// 1 inside a band of width `w` centred on the zero of `d`, screen-space
// antialiased so lines stay crisp at grazing angles and don't shimmer.
float band(float d, float w) {
float aa = fwidth(d) + 0.001;
return 1.0 - smoothstep(w * 0.5, w * 0.5 + aa, abs(d));
}
// Antialiased outline of the axis-aligned box |p| <= half_size.
float rect_outline(vec2 p, vec2 half_size, float w) {
vec2 d = abs(p) - half_size;
return band(max(d.x, d.y), w);
}
// Plate seam mask for one projection plane.
float plate_seam(vec2 p) {
vec2 d = abs(fract(p / panel_size) - 0.5) * panel_size;
float m = min(d.x, d.y);
return 1.0 - smoothstep(panel_line_width, panel_line_width + fwidth(m) + 0.01, m);
}
void fragment() {
vec2 xz = v_local.xz;
vec3 n = normalize(v_local_normal);
float up_facing = clamp(n.y, 0.0, 1.0);
// --- hull plating ----------------------------------------------------
// Projected onto whichever plane each facet most faces, blended by
// |normal|, so plating doesn't smear up the fillets or stripe the vertical
// goal bulkheads the way a flat XZ projection did.
vec3 w = abs(n);
w /= max(w.x + w.y + w.z, 0.001);
float seams = w.x * plate_seam(v_local.zy)
+ w.y * plate_seam(v_local.xz)
+ w.z * plate_seam(v_local.xy);
vec3 albedo = mix(deck_color, panel_color, hash21(floor(xz / panel_size)) * panel_variation);
albedo = mix(albedo, deck_color * 0.55, seams);
float rough = deck_roughness + seams * 0.25;
// --- team-tinted end zones -------------------------------------------
// Team 0 defends +z (see the arenas' GoalTeam0 transforms), team 1 -z.
float t0 = smoothstep(0.0, half_z, xz.y);
float t1 = smoothstep(0.0, half_z, -xz.y);
vec3 zone = team0_color * t0 + team1_color * t1;
// Markings belong on the flat deck only: fade them out as the fillet lifts
// away from the floor, and drop them entirely on anything not facing up so
// they never wrap onto a goal bulkhead.
float flat_deck = (1.0 - smoothstep(0.0, base_radius * 0.4, v_local.y)) * up_facing;
albedo = mix(albedo, albedo + zone * 0.18, end_zone_strength * flat_deck);
// --- field markings ---------------------------------------------------
float marks = band(length(xz) - centre_circle_radius, line_width);
marks = max(marks, band(xz.y, line_width));
for (int i = 0; i < 2; i++) {
float s = i == 0 ? 1.0 : -1.0;
vec2 c = vec2(0.0, s * (goal_line_z - goal_area_depth * 0.5));
marks = max(marks, rect_outline(
xz - c, vec2(goal_area_half_width, goal_area_depth * 0.5), line_width));
}
marks *= flat_deck;
// --- deck/field handover ---------------------------------------------
// A band, not a step: it has to light the top edge of the fillets and the
// matching edge of the goal bulkheads without setting the whole of an
// elevated bulkhead (which sits far above base_radius) glowing.
float seam = 1.0 - smoothstep(0.0, seam_width, abs(v_local.y - base_radius));
ALBEDO = mix(albedo, line_color, marks);
METALLIC = deck_metallic * (1.0 - marks);
ROUGHNESS = clamp(mix(rough, 0.5, marks), 0.05, 1.0);
EMISSION = line_color * marks * line_emission
+ seam_color * seam * seam_emission
+ zone * 0.015 * flat_deck;
}