shader_type canvas_item; // #125: the dimensional breach, as a screen-space lens. // // The prologue's warp used to be a handful of lines turning around a point. // This bends the ACTUAL SCENE around the breach instead - the ships, the // rocks and the starfield all get dragged into it - which is most of what // makes a hole in space read as a hole rather than as a decal drawn on top // of one. // // Two effects, both radial and both fading to nothing at the edge of their // reach, so the shader is invisible outside its own radius and costs a // clamped texture fetch everywhere else: // // swirl angle offset that grows toward the centre, so the scene winds // around the hole rather than merely sliding toward it // pull inward displacement, strongest just outside the horizon, which // is the lensing itself // // Inside the horizon nothing is sampled at all: the core is drawn as empty, // the same decision BlackHoleVisual makes for the black-hole planet, and // for the same reason - a "black hole" with something visible in the middle // stops reading as one. uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear; // Where the breach is, in SCREEN pixels, and how big it has grown. uniform vec2 breach_center = vec2(360.0, 640.0); uniform vec2 viewport_size = vec2(720.0, 1280.0); // The event horizon: everything inside is void. uniform float horizon_radius = 0.0; // How far the lens reaches past the horizon. Beyond this the scene is // untouched. uniform float lens_radius = 1.0; // 0 while the breach is opening, 1 at full strength. Scales both effects // together so one knob fades the whole thing in. uniform float strength = 0.0; uniform float swirl_turns = 2.4; uniform vec4 rim_color : source_color = vec4(0.72, 0.45, 1.0, 1.0); void fragment() { vec2 pixel = UV * viewport_size; vec2 offset = pixel - breach_center; float distance_to_centre = length(offset); if (distance_to_centre >= lens_radius || strength <= 0.001) { COLOR = texture(screen_texture, SCREEN_UV); } else if (distance_to_centre <= horizon_radius) { // The core. Not black-with-alpha: genuinely nothing, so whatever is // behind the breach is gone rather than dimmed. COLOR = vec4(0.0, 0.0, 0.0, 1.0); } else { // 1 at the horizon, 0 at the edge of the lens. Squared so the bend // is concentrated near the hole instead of smeared evenly outward. float falloff = 1.0 - clamp( (distance_to_centre - horizon_radius) / max(lens_radius - horizon_radius, 1.0), 0.0, 1.0); float bend = falloff * falloff * strength; float angle = atan(offset.y, offset.x) + bend * swirl_turns; float pulled = distance_to_centre - bend * (lens_radius - horizon_radius) * 0.55; pulled = max(pulled, horizon_radius); vec2 sampled = breach_center + vec2(cos(angle), sin(angle)) * pulled; COLOR = texture(screen_texture, sampled / viewport_size); // A hot rim where the lensing is fiercest, which is what gives the // horizon an edge rather than a fade. float rim = smoothstep(0.72, 1.0, falloff) * strength; COLOR.rgb = mix(COLOR.rgb, rim_color.rgb, rim * 0.65); } }