2D Swirling Vortex Portal

A 2D portal vortex shader with animated twist, pulsation, and scrolling effects. Customizable with edge softness, color tint, twist strength, breathing motion, scroll speed, and other controls. Tested on a TextureRect in Godot 4.1.1 and 4.4.1 — likely works in other 4.x versions too. Made for 2D; untested in 3D or on other node types. Works best with a seamless noise texture.

Shader code
shader_type canvas_item;

// The texture that will be distorted and scrolled for the portal effect
uniform sampler2D portal_texture : source_color, filter_linear_mipmap_anisotropic;

// --- Effect Configuration ---

// General color tint for the portal
uniform vec4 portal_tint : source_color = vec4(1.0, 1.0, 1.0, 1.0);

// Vortex Shape and Twist
// Defines the radius from the center where the vortex effects (like twist) are prominent.
uniform float vortex_effect_radius : hint_range(0.01, 1.0, 0.01) = 0.5;
// Strength of the twisting effect. Higher values mean more spiraling.
uniform float twist_strength : hint_range(0.0, 30.0, 0.1) = 8.0;

// Pulsation (Timing and Magnitude)
// Speed of the pulsation cycle. Affects how fast the portal twists and breathes.
uniform float pulsation_speed : hint_range(0.0, 5.0, 0.01) = 0.7;
// Magnitude of the shrink/expand "breathing" effect.
uniform float breath_magnitude : hint_range(-0.3, 0.3, 0.005) = 0.05;

// Overall Rotation
// Constant rotation speed for the entire portal.
uniform float overall_rotation_speed : hint_range(-2.0, 2.0, 0.01) = 0.25;

// Texture Scrolling
// Speed at which the portal_texture scrolls in the X direction.
uniform float texture_scroll_speed_x : hint_range(-1.0, 1.0, 0.01) = 0.08;
// Speed at which the portal_texture scrolls in the Y direction.
uniform float texture_scroll_speed_y : hint_range(-1.0, 1.0, 0.01) = 0.03;

// Appearance
// How soft the edge of the portal fades to transparency.
uniform float edge_softness : hint_range(0.01, 0.5, 0.005) = 0.1;


// --- Utility Functions ---

// Rotation matrix function
mat2 rotate(float angle) {
    float s = sin(angle);
    float c = cos(angle);
    return mat2(vec2(c, -s), vec2(s, c));
}

// --- Fragment Shader Logic ---

void fragment() {
    // --- 1. UV Preparation and Aspect Correction ---
    vec2 node_pixel_size = 1.0 / TEXTURE_PIXEL_SIZE;
    vec2 uv = UV - 0.5;

    if (node_pixel_size.x > node_pixel_size.y) {
        uv.x *= node_pixel_size.x / node_pixel_size.y;
    } else if (node_pixel_size.y > node_pixel_size.x) {
        uv.y *= node_pixel_size.y / node_pixel_size.x;
    }

    // --- 2. Time-based Progress for Pulsation ---
    float time_progress = sin(TIME * pulsation_speed);

    // --- 3. Overall Rotation ---
    if (overall_rotation_speed != 0.0) {
        uv = rotate(TIME * overall_rotation_speed) * uv;
    }

    // --- 4. Calculate Distance and Angle (Polar Coordinates) ---
    float distance_from_center = length(uv);

    // --- 5. Vortex/Twist Effect ---
    float twist_spatial_factor = smoothstep(0.0, vortex_effect_radius, vortex_effect_radius - distance_from_center);
    float current_animated_twist = twist_strength * time_progress;
    float twist_angle = twist_spatial_factor * current_animated_twist;

    if (distance_from_center < vortex_effect_radius) {
        uv = rotate(twist_angle) * uv;
    }

    // --- 6. Pulsating Shrink/Expand ("Breathing") Effect ---
    float breath_value = breath_magnitude * time_progress;
    if (distance_from_center > 0.0001) {
        float new_radial_distance = max(0.0, distance_from_center + breath_value);
        uv *= (new_radial_distance / distance_from_center);
    }

    // --- 7. Texture Scrolling ---
    // Combine the X and Y scroll speed uniforms into a vec2
    vec2 texture_scroll_speed = vec2(texture_scroll_speed_x, texture_scroll_speed_y);
    vec2 scroll_offset = TIME * texture_scroll_speed;
    vec2 final_scrolling_uv = uv + scroll_offset;

    // --- 8. Revert Aspect Correction and De-center for Texture Sampling ---
    if (node_pixel_size.x > node_pixel_size.y) {
        final_scrolling_uv.x /= (node_pixel_size.x / node_pixel_size.y);
    } else if (node_pixel_size.y > node_pixel_size.x) {
        final_scrolling_uv.y /= (node_pixel_size.y / node_pixel_size.x);
    }
    final_scrolling_uv += 0.5;

    // --- 9. Alpha Calculation for Portal Edge ---
    float alpha = smoothstep(vortex_effect_radius, vortex_effect_radius - edge_softness, distance_from_center);

    // --- 10. Texture Sampling and Final Color ---
    vec4 texture_color = texture(portal_texture, fract(final_scrolling_uv));
    
    COLOR = texture_color * portal_tint;
    COLOR.a *= alpha;

    if (UV.x < 0.0 || UV.x > 1.0 || UV.y < 0.0 || UV.y > 1.0) {
        COLOR.a = 0.0;
    }
}
Tags
2d, Customisable, customizable, portal, Swirl, Twisty, vortex
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

Vortex overlay

vortex and shrink

Portal Shader

guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
trackback

[…] is a shader ! I used and modified this shader for the death sequence : https://godotshaders.com/shader/2d-swirling-vortex-portal/. As with many shaders on godotshader, you can use, and play with these freely. The license for this […]