// 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); // Stand-in for bounce light. The arena is lit by one dim directional plus low // ambient, so hull facing away from it — the goal bulkheads, the fillet caps // either side of each mouth — otherwise crushed to pure black and the whole // end zone read as a void with a goal floating in it. uniform float hull_fill : hint_range(0.0, 2.0) = 0.55; 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: this arena is lit by one dim // directional plus low ambient, and a near-metal deck has almost no diffuse // response, which crushed the plating to black. 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 = albedo * hull_fill + line_color * marks * line_emission + seam_color * seam * seam_emission + zone * 0.015 * flat_deck; }