
A 2D screen-space wormhole / black hole effect. It distorts and warps background elements (using screen_texture) to simulate gravitational lensing, combined with a glowing, swirling procedural plasma nebula core.
How to set up:
shader_type canvas_item;
render_mode unshaded;
/*
============================================================================
2D COSMIC PORTAL & GRAVITATIONAL LENS SHADER
============================================================================
For Godot 4.x (CanvasItem / Screen-space post-process)
*/
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
// ============================================================================
// PORTAL & GRAVITY WELL
// ============================================================================
group_uniforms PortalStructure;
uniform vec2 portal_center = vec2(0.5, 0.5);
uniform float portal_radius : hint_range(0.0, 1.0) = 0.35;
uniform float core_radius : hint_range(0.0, 0.5) = 0.08;
uniform float core_softness : hint_range(0.0, 0.1) = 0.02;
uniform float lensing_strength : hint_range(-0.2, 0.2) = 0.06;
// ============================================================================
// ACCRETION SWIRL & PLASMA
// ============================================================================
group_uniforms NebulaPlasma;
uniform float swirl_strength : hint_range(-10.0, 10.0) = 4.5;
uniform float swirl_speed : hint_range(-5.0, 5.0) = 1.2;
uniform float plasma_scale : hint_range(1.0, 15.0) = 6.0;
uniform float plasma_detail : hint_range(1.0, 4.0, 1.0) = 3.0;
uniform float plasma_brightness : hint_range(0.0, 3.0) = 1.4;
// ============================================================================
// COLORS & GLOW
// ============================================================================
group_uniforms Visuals;
uniform vec4 core_glow_color : source_color = vec4(0.0, 0.9, 1.0, 1.0);
uniform vec4 nebula_color_1 : source_color = vec4(0.65, 0.0, 0.9, 1.0);
uniform vec4 nebula_color_2 : source_color = vec4(0.05, 0.0, 0.35, 1.0);
uniform float ring_glow_intensity : hint_range(0.0, 5.0) = 2.0;
uniform float portal_glow_falloff : hint_range(0.1, 2.0) = 0.65;
// ============================================================================
// PROCEDURAL NOISE
// ============================================================================
float hash12(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise2d(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash12(i);
float b = hash12(i + vec2(1.0, 0.0));
float c = hash12(i + vec2(0.0, 1.0));
float d = hash12(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float fbm(vec2 p, int octaves) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < octaves; i++) {
value += amplitude * noise2d(p * frequency);
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
vec2 rotate2d(vec2 p, float angle) {
float s = sin(angle);
float c = cos(angle);
return vec2(p.x * c - p.y * s, p.x * s + p.y * c);
}
void fragment() {
vec2 base_uv = SCREEN_UV;
vec2 uv = base_uv;
float t = TIME;
vec2 px_size = SCREEN_PIXEL_SIZE;
float aspect = px_size.y / px_size.x;
vec2 center_uv = uv - portal_center;
center_uv.y /= aspect;
float dist = length(center_uv);
// 1. Gravitational Lensing (Background Distortion)
vec2 distorted_uv = base_uv;
if (dist < portal_radius) {
float lens_mask = smoothstep(portal_radius, core_radius, dist);
float gravity_well = lensing_strength * lens_mask;
distorted_uv = base_uv - normalize(center_uv) * gravity_well;
}
vec3 background_color = textureLod(screen_texture, distorted_uv, 0.0).rgb;
vec3 portal_color = vec3(0.0);
float portal_mask = 0.0;
if (dist < portal_radius) {
float swirl_angle = (swirl_strength / (dist + 0.02)) + (t * swirl_speed);
vec2 swirl_coord = rotate2d(center_uv, swirl_angle);
vec2 noise_coord = swirl_coord * plasma_scale + vec2(t * 0.15);
float plasma_noise = fbm(noise_coord, int(plasma_detail));
portal_mask = 1.0 - smoothstep(core_radius, portal_radius, dist);
vec3 plasma_color = mix(nebula_color_2.rgb, nebula_color_1.rgb, plasma_noise);
portal_color = plasma_color * plasma_noise * plasma_brightness * portal_mask;
}
float ring_dist = abs(dist - core_radius);
float ring_glow = exp(-pow(ring_dist, 2.0) / (0.0004 * portal_glow_falloff));
vec3 accretion_glow = core_glow_color.rgb * ring_glow * ring_glow_intensity;
float singularity_mask = smoothstep(core_radius - core_softness, core_radius, dist);
vec3 final_color = background_color;
if (dist < portal_radius) {
final_color = mix(final_color, final_color + portal_color, portal_mask * 0.85);
}
final_color += accretion_glow;
final_color = mix(vec3(0.0), final_color, singularity_mask);
COLOR = vec4(clamp(final_color, 0.0, 1.0), 1.0);
}Join the discussion! Log in to share your thoughts.
Log In to Comment