Volumetric Clouds

Raymarched Clouds.

Shader code
shader_type spatial;
render_mode unshaded;

uniform sampler3D cloud_noise_texture;
uniform float cloud_scale : hint_range(0.001, 5.0) = 1.0;
uniform float cloud_threshold : hint_range(0.0, 1.0) = 0.5;
uniform float cloud_threshold_multiplier: hint_range(0.00, 10.00) = 0.05;
uniform int max_steps : hint_range(1, 200) = 64;
uniform float step_size : hint_range(0.01, 0.5) = 0.1;
uniform float cloud_scroll_speed : hint_range(-1.0, 1.0) = 0.5;
uniform vec3 minBounds = vec3(-1.0, -1.0, -1.0);
uniform vec3 maxBounds = vec3(1.0, 1.0, 1.0);

uniform sampler3D detail_noise_texture;
uniform float detail_intensity = 0.5;

float sample_detail_noise(vec3 position) {
    return texture(detail_noise_texture, position).r;
}

float sample_cloud(vec3 position) {
    vec3 offsetPos = position + cloud_scroll_speed * TIME;
    float main_cloud = texture(cloud_noise_texture, offsetPos).r;
    float detail = sample_detail_noise(position * 10.0);  // 10.0 is an arbitrary value for higher frequency
    return mix(main_cloud, detail, detail_intensity);
}

float raymarch(vec3 ro_model, vec3 rd_model, vec2 intersections) {
    float total_density = 0.0;
    float t = intersections.x;  // start from the entry point
    float max_t = intersections.y;  // march up to the exit point

    for (int i = 0; i < max_steps && t < max_t; i++) {
        vec3 pos_model = ro_model + t * rd_model;
        float density = sample_cloud(pos_model * cloud_scale);
        density = smoothstep(cloud_threshold - 0.05, cloud_threshold + cloud_threshold_multiplier, density);
        total_density += density * step_size;
        t += step_size;
    }
    return total_density;
}


// Return both entry and exit intersections with the mesh
vec2 getRayIntersections(vec3 ro, vec3 rd) {
    vec3 t1 = (minBounds - ro) / rd;
    vec3 t2 = (maxBounds - ro) / rd;

    vec3 tmin = min(t1, t2);
    vec3 tmax = max(t1, t2);

    float t_near = max(max(tmin.x, tmin.y), tmin.z);
    float t_far = min(min(tmax.x, tmax.y), tmax.z);

    if (t_near > t_far || t_far < 0.0) {
        return vec2(-1.0, -1.0); // No intersection
    }

    return vec2(t_near, t_far);
}

void fragment() {
    vec3 ro_world = INV_VIEW_MATRIX[3].xyz;
    vec4 clipPos = vec4((FRAGCOORD.xy / VIEWPORT_SIZE.xy) * 2.0 - 1.0, FRAGCOORD.z, 1.0);
    vec4 viewPos = INV_PROJECTION_MATRIX * clipPos;
    vec3 rd_world = normalize(viewPos.xyz / viewPos.w);
    rd_world = mat3(INV_VIEW_MATRIX) * rd_world;

    vec2 intersections = getRayIntersections(ro_world, rd_world);

    // Check if there's no intersection
    if (intersections.x == -1.0) {
        discard;
    }

    vec3 ro_model = mat3(inverse(MODEL_MATRIX)) * (ro_world - MODEL_MATRIX[3].xyz);
    vec3 rd_model = mat3(inverse(MODEL_MATRIX)) * rd_world;

    float cloud_intensity = raymarch(ro_model, rd_model, intersections);

    ALBEDO = vec3(0.8, 0.8, 0.9) * cloud_intensity;
    ALPHA = cloud_intensity;
}
Tags
clouds
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 thepathgame

SDF Range Rings (3D)

Volumetric Raymarched (animated) Clouds v2

CRT with variable fisheye

Related shaders

Volumetric nebulae/clouds

Volumetric Raymarched (animated) Clouds v2

Screen Space Frost, with volumetric Snow

Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Rakun
Rakun
7 months ago

How to actually use this shader?