// Containment-field surface for the arena walls, ceiling, corner curves and // ceiling fillets — the translucent half of ArenaBoundary's visual shell. // // Additive and unshaded on purpose. The StandardMaterial3D this replaces used // alpha blending plus rim lighting, which had two problems: // - alpha blending darkens whatever is behind it, so wherever two boundary // surfaces overlapped the tint composited twice and the overlap read as a // hard-edged patch that re-sorted as the camera moved; // - rim is a *lit* effect, so each arena's two DirectionalLight3Ds and SDFGI // drove visible gradients across a single 28x40 m panel. // Additive cannot double-darken and composites order-independently, so the // field reads uniformly regardless of how the geometry happens to sort. shader_type spatial; render_mode blend_add, depth_draw_never, cull_back, unshaded; uniform vec3 field_color : source_color = vec3(0.45, 0.65, 1.0); uniform float field_intensity : hint_range(0.0, 1.0) = 0.09; // Face-on brightness floor, before fresnel and cell detail. uniform float base_level : hint_range(0.0, 2.0) = 0.5; // Grazing-angle glow — the "glass" read the old rim param was reaching for. // Kept gentle: a steep exponent makes a head-on end wall read as a hard-edged // dark rectangle against the corner curves flanking it. uniform float fresnel_power : hint_range(0.5, 8.0) = 2.2; uniform float fresnel_boost : hint_range(0.0, 8.0) = 2.6; uniform float cell_size = 1.6; uniform float cell_width : hint_range(0.0, 0.5) = 0.06; uniform float cell_strength : hint_range(0.0, 4.0) = 1.1; // Camera position in ArenaBoundary's local space, pushed each frame by // arena_boundary.gd. Facets the camera has crossed to the outside of fade out // rather than popping, so looking into the arena from outside stays clear. // Per-pixel, which is what lets one merged shell replace the old per-face // MeshInstance3D visibility toggling. uniform vec3 camera_local_pos = vec3(0.0, 6.0, 0.0); uniform float cull_fade_distance : hint_range(0.01, 8.0) = 1.25; varying vec3 v_local_pos; varying vec3 v_local_normal; void vertex() { v_local_pos = VERTEX; v_local_normal = NORMAL; } // Offset-grid nearest-cell offset, used to build a hex lattice. vec2 hex_cell(vec2 p) { vec2 r = vec2(1.0, 1.7320508); vec2 h = r * 0.5; vec2 a = mod(p, r) - h; vec2 b = mod(p - h, r) - h; return dot(a, a) < dot(b, b) ? a : b; } float hex_edge(vec2 p) { vec2 g = abs(hex_cell(p / cell_size)); float d = max(dot(g, normalize(vec2(1.0, 1.7320508))), g.x); return smoothstep(0.5 - cell_width, 0.5, d); } void fragment() { vec3 n = normalize(v_local_normal); // Project the hex lattice onto whichever plane the facet most faces, // blended by |normal| so the curved corner and fillet surfaces cross // between projections without a seam. vec3 w = abs(n); w /= max(w.x + w.y + w.z, 0.001); float cells = w.x * hex_edge(v_local_pos.zy) + w.y * hex_edge(v_local_pos.xz) + w.z * hex_edge(v_local_pos.xy); float facing = clamp(dot(normalize(NORMAL), normalize(VIEW)), 0.0, 1.0); float fresnel = pow(1.0 - facing, fresnel_power); float level = base_level + fresnel * fresnel_boost + cells * cell_strength; ALBEDO = field_color * level * field_intensity; // Signed distance from the camera to this facet's plane along its inward // normal: positive while the camera is still inside the enclosure, so the // panel fades out just before back-face culling would drop it. float side = dot(camera_local_pos - v_local_pos, n); ALPHA = smoothstep(0.0, cull_fade_distance, side); }