Portal shader inspired by Portal 1

[created in Godot 4.6.2]

 

A fully procedural, unlit portal shader for Godot 4.x. No external textures are required!

This shader is highly customizable.

How to use:

  • Create a MeshInstance3D in your scene.
  • Assign a QuadMesh to it.
  • Create a new ShaderMaterial and assign this shader.
  • Tweak the uniform parameters in the inspector to create your unique portal!

Compatible with Godot 4.x.

 

Can easily be converted to a canvas_item shader for 2D games by changing shader_type spatial; to shader_type canvas_item; and replacing ALBEDO with COLOR.rgb and ALPHA with COLOR.a.

Shader code
shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_disabled, unshaded, shadows_disabled, specular_disabled;

uniform vec3  portal_color      : source_color = vec3(0.15, 0.6, 1.0);
uniform vec3  ring_color_bottom : source_color = vec3(1.0, 1.0, 1.0);
uniform vec3  ring_color_top    : source_color = vec3(0.15, 0.6, 1.0);
uniform vec3  halo_color        : source_color = vec3(0.15, 0.6, 1.0);
uniform vec3  cloud_color_dark  : source_color = vec3(0.0, 0.1, 0.3);
uniform vec3  cloud_color_light : source_color = vec3(0.15, 0.6, 1.0);

uniform float brightness         : hint_range(0.5, 10.0) = 3.0;
uniform float ring_brightness    : hint_range(0.5, 10.0) = 2.5;
uniform float halo_brightness    : hint_range(0.0, 5.0)  = 0.7;
uniform float cloud_brightness   : hint_range(0.0, 5.0)  = 1.5;

uniform float oval_ratio         : hint_range(0.1, 1.0)  = 0.6;
uniform float body_edge_inner    : hint_range(0.5, 1.0)  = 0.92;
uniform float body_edge_outer    : hint_range(0.5, 1.0)  = 1.0;

uniform float ring_inner_start   : hint_range(0.5, 1.0)  = 0.83;
uniform float ring_inner_end     : hint_range(0.5, 1.0)  = 0.95;
uniform float ring_outer_start   : hint_range(0.8, 1.2)  = 0.97;
uniform float ring_outer_end     : hint_range(0.8, 1.5)  = 1.02;
uniform float ring_top_brightness: hint_range(0.0, 5.0)  = 1.2;

uniform float vertical_split_top : hint_range(-0.5, 0.5) = 0.15;
uniform float vertical_split_bot : hint_range(-0.5, 0.5) = -0.15;

uniform float halo_outer         : hint_range(1.0, 2.0)  = 1.12;
uniform float halo_inner         : hint_range(0.5, 1.2)  = 0.96;
uniform float halo_alpha_mult    : hint_range(0.0, 1.0)  = 0.7;

uniform float speed              : hint_range(0.0, 3.0)  = 1.0;
uniform float turbulence         : hint_range(0.5, 3.0)  = 1.0;

uniform float noise1_scale       : hint_range(1.0, 20.0) = 6.0;
uniform float noise1_speed_x     : hint_range(-1.0, 1.0) = 0.07;
uniform float noise1_speed_y     : hint_range(-1.0, 1.0) = 0.13;
uniform float noise1_weight      : hint_range(0.0, 1.0)  = 0.6;

uniform float noise2_scale       : hint_range(1.0, 20.0) = 12.0;
uniform float noise2_speed_x     : hint_range(-1.0, 1.0) = -0.11;
uniform float noise2_speed_y     : hint_range(-1.0, 1.0) = 0.09;
uniform float noise2_weight      : hint_range(0.0, 1.0)  = 0.4;

uniform float distort_strength   : hint_range(0.0, 0.5)  = 0.1;

uniform float cloud_threshold_low  : hint_range(0.0, 0.5)  = 0.15;
uniform float cloud_threshold_high : hint_range(0.5, 1.0)  = 0.9;
uniform float cloud_dark_level     : hint_range(0.0, 1.0)  = 0.05;

uniform int   fbm_octaves        : hint_range(1, 8)      = 5;

float hash(vec2 p){
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}

float vnoise(vec2 p){
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    return mix(mix(hash(i),             hash(i + vec2(1,0)), f.x),
               mix(hash(i + vec2(0,1)), hash(i + vec2(1,1)), f.x), f.y);
}

float fbm(vec2 p){
    float v = 0.0;
    float a = 0.5;
    for (int i = 0; i < 8; i++){
        if (i >= fbm_octaves) break;
        v += a * vnoise(p);
        p *= 2.0;
        a *= 0.5;
    }
    return v;
}

void fragment(){
    vec2 uv = UV - 0.5;

    vec2 e = uv * vec2(1.0 / oval_ratio, 1.0);
    float r = length(e) * 2.0;

    vec2 t1 = vec2(TIME * noise1_speed_x, TIME * noise1_speed_y) * speed;
    vec2 t2 = vec2(TIME * noise2_speed_x, TIME * noise2_speed_y) * speed;
    float n  = fbm(uv * noise1_scale * turbulence + t1) * noise1_weight
             + fbm(uv * noise2_scale * turbulence + t2) * noise2_weight;

    float dr = r + (n - 0.5) * distort_strength;

    float body   = 1.0 - smoothstep(body_edge_inner, body_edge_outer, dr);

    float ring   = smoothstep(ring_inner_start, ring_inner_end, dr)
                 - smoothstep(ring_outer_start, ring_outer_end, dr);

    float clouds = smoothstep(cloud_threshold_low, cloud_threshold_high, n);

    float bottom_mask = smoothstep(vertical_split_top, vertical_split_bot, uv.y);

    vec3 r_bottom = ring_color_bottom * brightness * ring_brightness;
    vec3 r_top    = ring_color_top    * brightness * ring_top_brightness;
    vec3 ring_col = mix(r_top, r_bottom, bottom_mask);

    vec3 inside_col = mix(cloud_color_dark * brightness,
                          cloud_color_light * cloud_brightness * brightness,
                          clouds);

    vec3 col = inside_col * body + ring_col * ring;

    float halo = smoothstep(halo_outer, halo_inner, r) * (1.0 - body) * clouds;
    col += halo_color * halo * brightness * halo_brightness;

    ALBEDO = col;
    ALPHA  = clamp(body + ring + halo * halo_alpha_mult, 0.0, 1.0);
}
Live Preview
Tags
Magic, portal, source, space, valve
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from stryck33

Related shaders

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments