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);
}


