From 23fbc3114a90b5475b5a6b178ff8bb93484cbae9 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Wed, 27 May 2026 15:42:11 -0700 Subject: [PATCH] feat(render): fade railroad overlay near min zoom Replace the hard zoom cutoff in RailroadPass with a linear alpha fade controlled by a new `railFadeRange` setting. Rails (and bridge pixels) ramp from invisible at `railMinZoom - railFadeRange` to fully opaque at `railMinZoom`, instead of popping in. Adds a uRailFade shader uniform and a debug slider. --- src/client/render/gl/RenderSettings.ts | 1 + src/client/render/gl/debug/Layout.ts | 9 +++++++++ src/client/render/gl/passes/RailroadPass.ts | 15 +++++++++++++-- src/client/render/gl/render-settings.json | 1 + .../render/gl/shaders/railroad/railroad.frag.glsl | 9 +++++---- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/client/render/gl/RenderSettings.ts b/src/client/render/gl/RenderSettings.ts index 9d848a992..628f0ea32 100644 --- a/src/client/render/gl/RenderSettings.ts +++ b/src/client/render/gl/RenderSettings.ts @@ -81,6 +81,7 @@ export interface RenderSettings { }; railroad: { railMinZoom: number; + railFadeRange: number; railDetailZoom: number; railAlpha: number; }; diff --git a/src/client/render/gl/debug/Layout.ts b/src/client/render/gl/debug/Layout.ts index a727d4127..831f4465a 100644 --- a/src/client/render/gl/debug/Layout.ts +++ b/src/client/render/gl/debug/Layout.ts @@ -162,6 +162,15 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] { folder("Railroad", [ slider(s.railroad, "railMinZoom", d.railroad, 0, 10, 0.1, "Min Zoom"), + slider( + s.railroad, + "railFadeRange", + d.railroad, + 0, + 5, + 0.1, + "Fade Range", + ), slider( s.railroad, "railDetailZoom", diff --git a/src/client/render/gl/passes/RailroadPass.ts b/src/client/render/gl/passes/RailroadPass.ts index ec1970c48..517c7f47c 100644 --- a/src/client/render/gl/passes/RailroadPass.ts +++ b/src/client/render/gl/passes/RailroadPass.ts @@ -98,6 +98,7 @@ export class RailroadPass { private uZoom: WebGLUniformLocation; private uRailDetailZoom: WebGLUniformLocation; private uRailAlpha: WebGLUniformLocation; + private uRailFade: WebGLUniformLocation; private uGhostOwnerID: WebGLUniformLocation; private mapW: number; @@ -145,6 +146,7 @@ export class RailroadPass { "uRailDetailZoom", )!; this.uRailAlpha = gl.getUniformLocation(this.program, "uRailAlpha")!; + this.uRailFade = gl.getUniformLocation(this.program, "uRailFade")!; this.uGhostOwnerID = gl.getUniformLocation(this.program, "uGhostOwnerID")!; // Texture unit bindings + ghost defaults @@ -265,8 +267,16 @@ export class RailroadPass { const gl = this.gl; const rs = this.settings.railroad; - // Skip entirely when below minimum zoom - if (zoom < rs.railMinZoom) return; + // Fade out as zoom drops below railMinZoom; fully invisible at railMinZoom - railFadeRange + const fadeRange = Math.max(rs.railFadeRange, 0); + const fadeStart = rs.railMinZoom - fadeRange; + const fade = + fadeRange <= 0 + ? zoom >= rs.railMinZoom + ? 1 + : 0 + : Math.min(1, Math.max(0, (zoom - fadeStart) / fadeRange)); + if (fade <= 0) return; // Flush CPU railroad state → GPU if (this.railroadDirty) { @@ -310,6 +320,7 @@ export class RailroadPass { gl.uniform1f(this.uZoom, zoom); gl.uniform1f(this.uRailDetailZoom, rs.railDetailZoom); gl.uniform1f(this.uRailAlpha, rs.railAlpha); + gl.uniform1f(this.uRailFade, fade); gl.uniform1f(this.uGhostOwnerID, this.ghostOwnerID); // Bind textures: 0=railroad, 1=tile, 2=palette, 3=terrain, 4=ghostRail diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index 6efae7d03..cbf3d7273 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -79,6 +79,7 @@ }, "railroad": { "railMinZoom": 4, + "railFadeRange": 2, "railDetailZoom": 6, "railAlpha": 1 }, diff --git a/src/client/render/gl/shaders/railroad/railroad.frag.glsl b/src/client/render/gl/shaders/railroad/railroad.frag.glsl index 0b363625d..e89a06cd1 100644 --- a/src/client/render/gl/shaders/railroad/railroad.frag.glsl +++ b/src/client/render/gl/shaders/railroad/railroad.frag.glsl @@ -12,6 +12,7 @@ uniform vec2 uMapSize; uniform float uZoom; uniform float uRailDetailZoom; uniform float uRailAlpha; +uniform float uRailFade; // Zoom-based fade multiplier (0..1) uniform float uGhostOwnerID; // Player smallID for ghost rail color in vec2 vWorldPos; @@ -139,17 +140,17 @@ void main() { // Overlapping railroad highlight — green tint if (highlighted) railColor = vec3(0.2, 0.85, 0.3); if (hitBridge) { - fragColor = vec4(mix(bridgeColor, railColor, railAlpha), 1.0); + fragColor = vec4(mix(bridgeColor, railColor, railAlpha), uRailFade); } else { - fragColor = vec4(railColor, railAlpha); + fragColor = vec4(railColor, railAlpha * uRailFade); } } else if (hitGhost) { float ghostAlpha = uRailAlpha * ghostCov * 0.5; vec3 ghostColor = uGhostOwnerID > 0.0 ? texture(uPalette, vec2((uGhostOwnerID + 0.5) / float(PALETTE_SIZE), 0.75)).rgb : vec3(0.75); - fragColor = vec4(ghostColor, ghostAlpha); + fragColor = vec4(ghostColor, ghostAlpha * uRailFade); } else { - fragColor = vec4(bridgeColor, 1.0); + fragColor = vec4(bridgeColor, uRailFade); } }