Starfield with Parallax Scrolling Effect

Starfield with parallax scrolling effect. See the comments in the shader for more details.

 

10th April 2023: I have updated the shader to version 3 which adds:

An extra layer of large stars.

Variable star size and color based on their distance.

Different star rendering options: hard and soft edged circles, hard and soft edged crosses, squares, and diamonds.

Guidance on replacing the TIME constant with a time uniform that you can update from your game’s _physics_process function to control the scrolling direction and pause scrolling.

 

Shader code
// Starfield shader v3 by Brian Smith (steampunkdemon.itch.io)
// MIT licence

shader_type canvas_item;

// Comment out the following line if you are not applying the shader to a ColorRect:
uniform vec2 dimensions = vec2(1024.0, 600.0); // Resolution of ColorRect in pixels

uniform float small_stars = 50.0; // Number of small stars. Rows for horizontally scrolling stars or columns for vertically scrolling stars.
uniform float small_star_far_size : hint_range(0.1, 1.0, 0.1) = 0.5;
uniform float small_star_near_size : hint_range(0.1, 1.0, 0.1) = 1.0;
uniform float large_stars = 8.0; // Number of large stars. Rows for horizontally scrolling stars or columns for vertically scrolling stars.
uniform float large_star_far_size : hint_range(0.1, 1.0, 0.1) = 0.5;
uniform float large_star_near_size : hint_range(0.1, 1.0, 0.1) = 1.0;
uniform vec4 far_star_color : hint_color = vec4(0.50, 0.0, 1.0, 1.0);
uniform vec4 near_star_color : hint_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform float base_scroll_speed : hint_range(0.0, 1.0, 0.01) = 0.05;
uniform float additional_scroll_speed : hint_range(0.01, 1.0, 0.01) = 0.05;

float greater_than(float x, float y) {
	return max(sign(x - y), 0.0);
}

void fragment() {
// The below line will scroll the stars from right to left or from bottom to top.
// To make the stars scroll in the opposite direction change the line to:
//	float time = 10000.0 - TIME;
// Alternativly you can comment out the below line and add a new uniform above as:
// uniform float time = 10000.0;
// You can then update the time uniform from your _physics_process function by adding or subtracting delta. You can also pause the scrolling by not changing the time uniform.
	float time = 10000.0 + TIME;

// Comment out the following two lines if you are not applying the shader to a TextureRect:
//	COLOR = texture(TEXTURE,UV);
//	vec2 dimensions = 1.0 / TEXTURE_PIXEL_SIZE;

// Horizontal scrolling:
	float small_stars_rn = fract(sin(floor(UV.y * small_stars)) * dimensions.y);
	float large_stars_rn = fract(sin(floor(UV.y * large_stars)) * dimensions.y);
	vec2 small_stars_uv = vec2(fract(UV.x + (base_scroll_speed + small_stars_rn * additional_scroll_speed) * time) * small_stars * dimensions.x / dimensions.y, fract(UV.y * small_stars)) * 2.0 - 1.0;
	vec2 large_stars_uv = vec2(fract(UV.x + (base_scroll_speed + large_stars_rn * additional_scroll_speed) * time) * large_stars * dimensions.x / dimensions.y, fract(UV.y * large_stars)) * 2.0 - 1.0;

// Vertical scrolling:
//	float small_stars_rn = fract(sin(floor(UV.x * small_stars)) * dimensions.x);
//	float large_stars_rn = fract(sin(floor(UV.x * large_stars)) * dimensions.x);
//	vec2 small_stars_uv = vec2(fract(UV.x * small_stars), fract(UV.y + (base_scroll_speed + small_stars_rn * additional_scroll_speed) * time) * small_stars * dimensions.y / dimensions.x) * 2.0 - 1.0;
//	vec2 large_stars_uv = vec2(fract(UV.x * large_stars), fract(UV.y + (base_scroll_speed + large_stars_rn * additional_scroll_speed) * time) * large_stars * dimensions.y / dimensions.x) * 2.0 - 1.0;

	vec4 star_color = mix(far_star_color, near_star_color, small_stars_rn);
	float star_size = small_star_far_size + (small_star_near_size - small_star_far_size) * small_stars_rn;

// Render small stars as circles with soft edges:
	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, max((star_size - length(small_stars_uv)) / star_size, 0.0) * star_color.a);

// Render small stars as circles with hard edges:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, greater_than(star_size, length(small_stars_uv)) * star_color.a);

// Render small stars as crosses with soft edges:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, max((star_size - length(small_stars_uv)) / star_size, 0.0) * (max(greater_than(star_size / 10.0, abs(small_stars_uv.x)), greater_than(star_size / 10.0, abs(small_stars_uv.y)))) * star_color.a);

// Render small stars as crosses with hard edges:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, max(greater_than(star_size / 5.0, abs(small_stars_uv.x)) * greater_than(star_size, abs(small_stars_uv.y)), greater_than(star_size / 5.0, abs(small_stars_uv.y)) * greater_than(star_size, abs(small_stars_uv.x))) * star_color.a);

// Render small stars as squares:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, greater_than(star_size, abs(small_stars_uv.x)) * greater_than(star_size, abs(small_stars_uv.y)) * star_color.a);

// Render small stars as diamonds:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, greater_than(star_size, abs(small_stars_uv.y) + abs(small_stars_uv.x)) * star_color.a);

	star_color = mix(far_star_color, near_star_color, large_stars_rn);
	star_size = large_star_far_size + (large_star_near_size - large_star_far_size) * large_stars_rn;

// Render large stars with circles and crosses with smooth edges:
	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, (max((star_size / 1.7 - length(large_stars_uv)) / star_size, 0.0) + max((star_size - length(large_stars_uv)) / star_size / 2.0, 0.0) * (max(greater_than(star_size / 10.0, abs(large_stars_uv.x)), greater_than(star_size / 10.0, abs(large_stars_uv.y))))) * star_color.a);

// Render large stars as circles with hard edges:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, greater_than(star_size, length(large_stars_uv)) * star_color.a);

// Render large stars as crosses with soft edges:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, max((star_size - length(large_stars_uv)) / star_size, 0.0) * (max(greater_than(star_size / 10.0, abs(large_stars_uv.x)), greater_than(star_size / 10.0, abs(large_stars_uv.y)))) * star_color.a);

// Render large stars as crosses with hard edges:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, max(greater_than(star_size / 5.0, abs(large_stars_uv.x)) * greater_than(star_size, abs(large_stars_uv.y)), greater_than(star_size / 5.0, abs(large_stars_uv.y)) * greater_than(star_size, abs(large_stars_uv.x))) * star_color.a);

// Render large stars as squares:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, greater_than(star_size, abs(large_stars_uv.x)) * greater_than(star_size, abs(large_stars_uv.y)) * star_color.a);

// Render large stars as diamonds:
//	COLOR.rgb = mix(COLOR.rgb, star_color.rgb, greater_than(star_size, abs(large_stars_uv.y) + abs(large_stars_uv.x)) * star_color.a);
}
Tags
parallax, scrolling, Starfield, Stars
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.

More from Steampunkdemon

Dial

Meter

Radar with trace blip

Related shaders

Parallax Ice | Depth effect

Starfield

Variable Blur – Works with parallax layers

guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jedah
Jedah
1 month ago

“hint_color” (lines 15 and 16) has been renamed to “source_color” in Godot 4