Skip to content

Instantly share code, notes, and snippets.

@jamonholmgren
Last active August 20, 2026 23:11
Show Gist options
  • Select an option

  • Save jamonholmgren/9192bec5ac8d9d6d01674e18603324c3 to your computer and use it in GitHub Desktop.

Select an option

Save jamonholmgren/9192bec5ac8d9d6d01674e18603324c3 to your computer and use it in GitHub Desktop.
Jammin Games cloud shader. Feel free to use -- MIT licensed. I just ask that you leave a comment with a screenshot showing me what you made with it.

Jammin Games Godot Cloud Shader

Jammin Games cloud shader. Feel free to use -- MIT licensed. I just ask that you leave a comment with a screenshot showing me what you made with it.

Built for Godot 4.7.

Gunship Origins Cloud Layer

How to use this shader

  1. Create a MeshInstance3D, add a Plane mesh at like 50_000 x 50_000 meters size, add this shader to it in a ShaderMaterial, raise it up to the elevation you want
  2. Tweak the shader parameters until it looks right

Dynamically adjusting shader parameters with day/night cycle

Create a script on your MeshInstance3D that looks like the CloudLayer.gd file and tweak for your specific setup.

Who's using it

class_name CloudLayer extends MeshInstance3D
# Summary: 2D cloud plane driver: syncs CloudLayer.gdshader sun direction/color/energy to Sky3D's dominant light and coverage to mission fog.
### Public signals and properties ###
@onready var _sky: Sky3D = owner # https://github.com/TokisanGames/Sky3D
@onready var _mat: ShaderMaterial = get_active_material(0)
### Godot hooks ###
func _process(_delta: float) -> void:
if Engine.get_process_frames() % 10: _sync_to_sky() # every 10 frames
### Private helper methods ###
const COVERAGE_CLEAR: float = 0.45
const MOON_BRIGHTNESS_SCALE: float = 0.05
const NIGHT_BRIGHTNESS_FLOOR: float = 0.001
const SHADOW_TINT: Color = Color(0.5, 0.55, 0.65)
func _sync_to_sky() -> void:
if not _mat or not _sky or not _sky.sun or not _sky.moon or not _sky.sky: return
var sun_brightness: float = clampf(_sky.sun.light_energy / maxf(_sky.sky.sun_light_energy, 0.001), 0.0, 1.0)
var moon_brightness: float = clampf(_sky.moon.light_energy / maxf(_sky.sky.moon_light_energy, 0.001), 0.0, 1.0) * MOON_BRIGHTNESS_SCALE
var light: DirectionalLight3D = _sky.sun if sun_brightness >= moon_brightness else _sky.moon
var brightness: float = maxf(maxf(sun_brightness, moon_brightness), NIGHT_BRIGHTNESS_FLOOR)
var lit: Color = light.light_color * brightness
_mat.set_shader_parameter(&"sun_direction", -light.global_basis.z)
_mat.set_shader_parameter(&"lit_color", lit)
_mat.set_shader_parameter(&"shadow_color", lit * SHADOW_TINT)
_mat.set_shader_parameter(&"coverage", _coverage())
# If you have a Game autoload with a world_fog property...
func _coverage() -> float: return remap(Game.world_fog, 0.1, 1.0, COVERAGE_CLEAR, 1.0)
shader_type spatial;
render_mode unshaded, blend_mix, depth_draw_never, cull_disabled, shadows_disabled;
uniform sampler2D cloud_noise : repeat_enable, filter_linear_mipmap;
uniform float noise_scale : hint_range(500.0, 20000.0) = 4000.0;
uniform vec2 wind_direction = vec2(1.0, 0.3);
uniform float wind_speed : hint_range(0.0, 100.0) = 12.0;
uniform float churn_speed : hint_range(0.0, 0.2) = 0.02;
uniform float warp_strength : hint_range(0.0, 1.0) = 0.35;
uniform float coverage : hint_range(0.0, 1.0) = 0.5;
uniform float softness : hint_range(0.01, 1.0) = 0.25;
uniform vec4 lit_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform vec4 shadow_color : source_color = vec4(0.55, 0.6, 0.7, 1.0);
uniform vec3 sun_direction = vec3(-0.4, -0.7, -0.3);
uniform float sun_shading : hint_range(0.0, 12.0) = 4.0;
uniform float sun_sample_distance : hint_range(10.0, 1000.0) = 250.0;
uniform float scatter_strength : hint_range(0.0, 2.0) = 0.6;
uniform float scatter_power : hint_range(1.0, 64.0) = 12.0;
uniform float opacity : hint_range(0.0, 1.0) = 0.85;
uniform float fade_start = 15000.0;
uniform float fade_end = 30000.0;
// Double-precision camera-relative rendering: MODEL_MATRIX translation is not absolute world
// space, so anchor the noise to the plane's local coords and measure distance in view space.
varying vec3 plane_pos;
void vertex() {
plane_pos = VERTEX;
}
float fbm(vec2 uv, float t) {
float d = texture(cloud_noise, uv).r * 0.55;
d += texture(cloud_noise, uv * 2.7 + vec2(t, -t * 0.6)).r * 0.3;
d += texture(cloud_noise, uv * 6.3 - vec2(t * 1.7, t)).r * 0.15;
return d;
}
void fragment() {
float t = TIME * churn_speed;
vec2 uv = (plane_pos.xz + wind_direction * TIME * wind_speed) / noise_scale;
vec2 warp = vec2(
texture(cloud_noise, uv * 0.6 + vec2(t * 0.4, 0.0)).r,
texture(cloud_noise, uv * 0.6 + vec2(0.37, t * 0.4 + 0.5)).r
) - 0.5;
vec2 wuv = uv + warp * warp_strength;
float cov_mod = mix(0.85, 1.25, texture(cloud_noise, uv * 0.11).r);
float threshold = mix(0.85, 0.25, coverage);
float d_raw = fbm(wuv, t) * cov_mod;
float density = smoothstep(threshold, threshold + softness, d_raw);
vec3 sun = normalize(sun_direction);
vec2 sun_offset = sun.xz * (sun_sample_distance / noise_scale);
float d_sun_raw = fbm(wuv - sun_offset, t) * cov_mod;
float light = clamp(1.0 - (d_sun_raw - d_raw) * sun_shading, 0.0, 1.0);
light *= mix(1.0, 0.7, density);
vec3 col = mix(shadow_color.rgb, lit_color.rgb, light);
vec3 view_dir = normalize(mat3(INV_VIEW_MATRIX) * VERTEX);
float fwd = pow(max(dot(view_dir, sun), 0.0), scatter_power);
col += fwd * scatter_strength * (1.0 - density) * lit_color.rgb;
float fade = 1.0 - smoothstep(fade_start, fade_end, length(VERTEX));
ALBEDO = col;
ALPHA = density * opacity * fade;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment