Stylized Sky

An open-source shader for creating stylized skies. Compatible with Godot game engine version 4.*

⚠️This page mirrors the Stylized Sky project on Github.
For more information and up to date version, you may want to check the github page.

 

Shader code
shader_type sky;
render_mode use_half_res_pass;

group_uniforms clouds;

uniform sampler2D cloud_shape_sampler : filter_linear_mipmap_anisotropic, repeat_enable;
uniform sampler2D cloud_noise_sampler : filter_linear_mipmap_anisotropic, repeat_enable;
uniform sampler2D cloud_curves;

uniform int clouds_samples : hint_range(8, 32, 8) = 16;
uniform int shadow_sample : hint_range(1, 4, 1) = 4;

uniform float clouds_density : hint_range(0.0, 1.0, 0.1) = 0.5;
uniform float clouds_scale : hint_range(0.5, 1.5, 0.1) = 1.0;
uniform float clouds_smoothness : hint_range(0.01, 0.1, 0.01) = 0.035;
uniform vec3 clouds_light_color : source_color;
uniform float clouds_shadow_intensity : hint_range(0.1, 10.0, 0.1) = 1.0;

group_uniforms high_clouds;

uniform sampler2D high_clouds_sampler;
uniform float high_clouds_density : hint_range(0.0, 1.0, 0.05) = 0.0;

group_uniforms sky;

uniform vec3 top_color : source_color = vec3(1.0);
uniform vec3 bottom_color : source_color = vec3(1.0);
uniform vec3 sun_scatter : source_color = vec3(1.0);

group_uniforms astro;

uniform vec3 astro_tint : source_color;
uniform sampler2D astro_sampler : repeat_disable, filter_linear_mipmap;
uniform float astro_scale : hint_range(0.1, 10.0, 0.1) = 1.0;
uniform float astro_intensity : hint_range(1.0, 3.0, 0.1) = 1.0;

group_uniforms stars;

uniform float stars_intensity : hint_range(0.0, 5.0, 0.1) = 0.0;

group_uniforms shooting_stars;

uniform float shooting_stars_intensity : hint_range(0.0, 10.0, 0.1) = 0.0;
uniform sampler2D shooting_star_sampler : filter_linear, repeat_disable;
uniform vec3 shooting_star_tint : source_color;

float rand(float n){return fract(sin(n) * 43758.5453123);}

// Voronoi method credit:
// The MIT License
// Copyright © 2013 Inigo Quilez
// https://www.shadertoy.com/view/ldl3Dl

vec3 hash( vec3 x ){
	x = vec3( dot(x,vec3(127.1,311.7, 74.7)),
			  dot(x,vec3(269.5,183.3,246.1)),
			  dot(x,vec3(113.5,271.9,124.6)));
	return fract(sin(x)*43758.5453123);
}

vec3 voronoi( in vec3 x ){
	vec3 p = floor( x );
	vec3 f = fract( x );
	
	float id = 0.0;
	vec2 res = vec2( 100.0 );
	for( int k=-1; k<=1; k++ )
	for( int j=-1; j<=1; j++ )
	for( int i=-1; i<=1; i++ ) {
		vec3 b = vec3( float(i), float(j), float(k) );
		vec3 r = vec3( b ) - f + hash( p + b );
		float d = dot( r, r );
		if( d < res.x ) {
			id = dot( p+b, vec3(1.0,57.0,113.0 ) );
			res = vec2( d, res.x );
		} else if( d < res.y ) {
			res.y = d;
		}
    }
    return vec3( sqrt( res ), abs(id) );
}

// https://stackoverflow.com/questions/18558910/direction-vector-to-rotation-matrix

mat3 direction_to_matrix(vec3 direction) {
	vec3 x_axis = normalize(cross(vec3(0.0, 1.0, 0.0), direction));
	vec3 y_axis = normalize(cross(direction, x_axis));
	return mat3(vec3(x_axis.x, y_axis.x, direction.x),
				vec3(x_axis.y, y_axis.y, direction.y),
				vec3(x_axis.z, y_axis.z, direction.z));
}

float cloud_density(vec3 p, float progress){
	float t_o = TIME * 0.001;
	float t_o_small = TIME * -0.005;
	float noise = texture(cloud_noise_sampler, p.xy * 4.0 + t_o_small).x * 0.1 + 0.9;
	float clouds_shape = texture(cloud_shape_sampler, (p.xy + t_o) * clouds_scale).x;
	float height_curve = texture(cloud_curves, vec2(progress, 0.0)).x;
	float base_density = 1.0 - clouds_density;
	float density = 
	smoothstep(base_density - clouds_smoothness,
	base_density + clouds_smoothness,
	clouds_shape * noise * height_curve
	);
	return density;
}

vec2 cloud_ray_march(vec3 direction, vec3 sun_direction){
	
	float density = 0.0;
	float light = 0.0;
	
	float height = 0.03;
	vec3 sample_point = vec3(0.0, 0.0, 2.0);
	
	int loop_offset = clouds_samples * 3;
	
	for(int i = loop_offset; i < clouds_samples + loop_offset; i++) {
		float progress = float(i) / float(clouds_samples);
		sample_point = direction * height * progress;
		float point_density = cloud_density(sample_point, progress);
		density += point_density;
		
		float point_light = 0.0;
		for(int f = 0; f < shadow_sample; f++){
			float shadow_progress = float(f) / float(shadow_sample);
			vec3 shadow_offset = sun_direction * height * shadow_progress;
			point_light += cloud_density(sample_point + shadow_offset, progress);
		}
		light += point_light;
	}
	return vec2(density, light / float(shadow_sample * clouds_samples));
}

vec3 random_direction(float seed){
	float phi = rand(seed) * PI;
	float costheta = rand(seed + 100.0) * 2.0 - 1.0;
	float theta = acos(costheta);
	return vec3( sin(theta) * cos(phi), (theta) * sin(phi), cos(theta) );
}

float get_shooting_star(vec3 eyedir){
	float shooting_star = 0.0;
	for(int i = 0; i < 4; i++){
		float base_rand = rand(float(i));
		float time = TIME + base_rand * 2.0;
		float duration = 0.5 + base_rand;
		float seed = floor(time / duration) * duration + base_rand;
		float progress = mod(time, duration) / duration;
		float rand_value = rand(seed + 100.0);
		float rand_scale = base_rand * 10.0;
		float a = rand_value * 0.8;
		mat3 angle = mat3(vec3(cos(a), -sin(a), 0.0), vec3(sin(a), cos(a), 0.0), vec3(0.0, 0.0, 1.0));
		vec3 shooting_dir = direction_to_matrix(random_direction(seed)) * angle * eyedir;
		vec2 shooting_uv = ((shooting_dir.xy + vec2(0.0, progress * 0.4)) * (8.0 + rand_scale)) + vec2(0.5);

		float shooting_mask = ceil(
			clamp(shooting_uv.x * (1.0 - shooting_uv.x), 0.0, 1.0) *
			clamp(shooting_uv.y * (1.0 - shooting_uv.y), 0.0, 1.0)
			) * ceil(shooting_dir.z);
			
		shooting_star = clamp(
			shooting_star + texture(shooting_star_sampler, shooting_uv).x
			* sin(progress * PI)
			* shooting_mask * rand_value,
		0.0, 1.0);
	}
	return clamp(shooting_star, 0.0, 1.0);
}

void sky() {
	
	float horizon_mask = abs(EYEDIR.y);
	float bottom_mask = smoothstep(0.5, 0.45, SKY_COORDS.y);
	
	vec3 dir = direction_to_matrix(LIGHT0_DIRECTION) * EYEDIR;
	vec2 astro_uv = (-(dir.xy / dir.z) * astro_scale) + vec2(0.5);
	float astro_mask = ceil(
		clamp(astro_uv.x * (1.0 - astro_uv.x), 0.0, 1.0) *
		clamp(astro_uv.y * (1.0 - astro_uv.y), 0.0, 1.0)
		) * ceil(dir.z);
	vec4 astro_color = texture(astro_sampler, astro_uv);
	
	// Sky color
	
	vec3 sky_gradient = mix(bottom_color.rgb, top_color.rgb, clamp(EYEDIR.y, 0.0, 1.0));
	vec3 sunset_color = sun_scatter * (1.0 - horizon_mask);
	vec3 sky_color = clamp(sky_gradient + sunset_color, 0.0, 1.0);
	
	// Stars
	
	if(stars_intensity > 0.0){
		vec2 stars = voronoi(EYEDIR * 25.0).xz;
		sky_color += smoothstep(0.025 + ((1.0 + sin(TIME + stars.y)) / 2.0) * 0.05, 0.0, stars.x) * stars_intensity;
	}
	
	// Add shooting stars
	
	if(shooting_stars_intensity > 0.0){
		sky_color += get_shooting_star(EYEDIR) * shooting_stars_intensity * shooting_star_tint;
	}
	
	// Add astro
	
	sky_color = mix(sky_color, astro_color.rgb * astro_intensity * astro_tint, astro_color.a * astro_mask * bottom_mask);
	
	// Add high clouds
	
	if(high_clouds_density > 0.0){
		vec2 high_clouds_uv = (EYEDIR.xz / clamp(EYEDIR.y, 0.0, 1.0)) * 0.25 + TIME * 0.001;
		float high_clouds_mask = texture(high_clouds_sampler, high_clouds_uv).x;
		sky_color = mix(sky_color, clouds_light_color, smoothstep(0.0, 1.0, high_clouds_mask) * horizon_mask * bottom_mask * high_clouds_density);
	}
	
	// clouds
	if (AT_HALF_RES_PASS) {
		vec3 clouds_direction = vec3(EYEDIR.xz / clamp(EYEDIR.y, 0.0, 1.0), 1.0);
		vec2 clouds = EYEDIR.y > 0.0 ? cloud_ray_march(clouds_direction, LIGHT0_DIRECTION) : vec2(0.0);
		
		COLOR = mix(bottom_color, clouds_light_color, exp(-clouds.y * clouds_shadow_intensity));
		ALPHA = (1.0 - exp(-clouds.x * horizon_mask * bottom_mask * 10.0));
		
	}else{
		COLOR.rgb = mix(sky_color, HALF_RES_COLOR.rgb, HALF_RES_COLOR.a);
	}
}
Tags
clouds, shooting stars, sky, Stars, stylised, stylized, volumetric
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

Stylized sky with procedural sun and moon

Stylized Sky Shader With Clouds For Godot 4

Stylized Cloudy Sky (v2)

Subscribe
Notify of
guest

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
EnlightenedOne
EnlightenedOne
10 months ago

A bunch of tips for anybody using this.

Reflections can be messed up by the sun (everyone should do this by default):

   // This can be used to limit reflection intensity, if the sun's intensity goes
   // over 1 its HDR and can bleed through shadows badly and muddy shadows
   float working_astro_intensity = astro_intensity;
   if (AT_CUBEMAP_PASS) {
      working_astro_intensity = 1.0; // 0.9 may look better based on your environment!
   }

   // Add astro
   sky_color = mix(sky_color, astro_color.rgb * working_astro_intensity * astro_tint, astro_color.a * astro_mask * bottom_mask);

Stylistic enhancement. Dont like your sky stretching into infinity? One cheap and quick way to let your sky touch the horizon is adding to EYEDIR.y + 0.1:

      // Displacing Y on the division side here warps the texture giving us "curvature" over a flat plane of a sky
      vec3 clouds_direction = vec3(EYEDIR.xz/(EYEDIR.y + 0.1), 0.0);

You can do it for the high clouds also

vec2 high_clouds_uv = (EYEDIR.xz / clamp(EYEDIR.y + 0.1, 0.0, 1.0)) * 0.25 + TIME * 0.001;
nerdnils
nerdnils
10 months ago

This shows up fine in the editor but in the game the camera only renders one color. No gradients, no clouds, nothing to be seen in game. What am I doing wrong? Is it some setting in the camera?

guckles
4 months ago

Make this the get_shooting_star() function to have them fade out:

float get_shooting_star(vec3 eyedir) {
float shooting_star = 0.0;
for(int i = 0; i < 4; i++){
float base_rand = rand(float(i));
float time = TIME + base_rand * 2.0;
float duration = 0.5 + base_rand;
float seed = floor(time / duration) * duration + base_rand;
float progress = mod(time, duration) / duration;
float rand_value = rand(seed + 100.0);
float rand_scale = base_rand * 10.0;
float a = rand_value * 0.8;
mat3 angle = mat3(vec3(cos(a), -sin(a), 0.0), vec3(sin(a), cos(a), 0.0), vec3(0.0, 0.0, 1.0));
vec3 shooting_dir = direction_to_matrix(random_direction(seed)) * angle * eyedir;
vec2 shooting_uv = ((shooting_dir.xy + vec2(0.0, progress * 0.4)) * (8.0 + rand_scale)) + vec2(0.5);

float shooting_mask = ceil(
clamp(shooting_uv.x * (1.0 – shooting_uv.x), 0.0, 1.0) *
clamp(shooting_uv.y * (1.0 – shooting_uv.y), 0.0, 1.0)
) * ceil(shooting_dir.z);

// Calculate the fade factor
float fade_factor = 1.0 – smoothstep(0.0, 1.0, progress); // Modify this as needed for fade timing

shooting_star = clamp(
shooting_star + texture(shooting_star_sampler, shooting_uv).x
* sin(progress * PI)
* shooting_mask * rand_value * fade_factor, // Apply the fade factor
0.0, 1.0);
}
return clamp(shooting_star, 0.0, 1.0);
}

Last edited 4 months ago by guckles
Nepharious_Bread
3 months ago

This is perfect, thank you. Anyone having trouble with this, just download the demo and copy the settings over. Then customize it from there.