Documentation

Shader Example: Raymarching Cloud

Volumetric raymarching cloud.

Header

Your content here

Snippets

Shadertoy

// Shadertoy: 3D Raymarched Cloud Example
// iResolution, iTime, iMouse available

#define MAX_STEPS 96
#define LIGHT_STEPS 8
#define FAR 40.0

float hash(vec3 p)
{
    p = fract(p * 0.3183099 + 0.1);
    p *= 17.0;
    return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
}

float noise(vec3 x)
{
    vec3 i = floor(x);
    vec3 f = fract(x);
    f = f * f * (3.0 - 2.0 * f);

    float n000 = hash(i + vec3(0,0,0));
    float n100 = hash(i + vec3(1,0,0));
    float n010 = hash(i + vec3(0,1,0));
    float n110 = hash(i + vec3(1,1,0));
    float n001 = hash(i + vec3(0,0,1));
    float n101 = hash(i + vec3(1,0,1));
    float n011 = hash(i + vec3(0,1,1));
    float n111 = hash(i + vec3(1,1,1));

    return mix(
        mix(mix(n000, n100, f.x), mix(n010, n110, f.x), f.y),
        mix(mix(n001, n101, f.x), mix(n011, n111, f.x), f.y),
        f.z
    );
}

float fbm(vec3 p)
{
    float v = 0.0;
    float a = 0.5;
    for (int i = 0; i < 5; i++)
    {
        v += a * noise(p);
        p *= 2.02;
        a *= 0.5;
    }
    return v;
}

float cloudDensity(vec3 p)
{
    // Move clouds over time
    p.xz += vec2(iTime * 0.8, iTime * 0.25);

    // Vertical shaping
    float h = p.y;
    float base = smoothstep(-1.0, 0.2, h);
    float top  = 1.0 - smoothstep(1.2, 2.8, h);
    float heightMask = base * top;

    // Large cloud body + details
    float d = fbm(p * 0.45);
    d += 0.5 * fbm(p * 1.2);
    d -= 0.55;

    d *= heightMask;

    return clamp(d, 0.0, 1.0);
}

float lightmarch(vec3 pos, vec3 lightDir)
{
    float transmittance = 1.0;
    float t = 0.2;

    for (int i = 0; i < LIGHT_STEPS; i++)
    {
        vec3 p = pos + lightDir * t;
        float d = cloudDensity(p);
        transmittance *= exp(-d * 1.6);
        t += 0.5;
    }

    return clamp(transmittance, 0.0, 1.0);
}

mat3 camera(vec3 ro, vec3 ta)
{
    vec3 f = normalize(ta - ro);
    vec3 r = normalize(cross(vec3(0,1,0), f));
    vec3 u = cross(f, r);
    return mat3(r, u, f);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;

    // Camera
    vec3 ro = vec3(0.0, 1.5, -6.0);
    vec3 ta = vec3(0.0, 1.2, 0.0);

    // Optional mouse orbit
    if (iMouse.z > 0.0)
    {
        float mx = (iMouse.x / iResolution.x - 0.5) * 6.2831;
        float my = (iMouse.y / iResolution.y - 0.5) * 1.2;
        ro = vec3(6.0 * sin(mx), 1.5 + my * 4.0, -6.0 * cos(mx));
    }

    mat3 cam = camera(ro, ta);
    vec3 rd = normalize(cam * vec3(uv, 1.6));

    // Sky
    float sunAmount = max(dot(rd, normalize(vec3(0.6, 0.7, 0.5))), 0.0);
    vec3 skyCol = mix(vec3(0.6, 0.75, 0.95), vec3(0.2, 0.4, 0.8), rd.y * 0.5 + 0.5);
    skyCol += vec3(1.0, 0.85, 0.6) * pow(sunAmount, 64.0) * 0.8;

    vec3 col = skyCol;

    vec3 lightDir = normalize(vec3(0.6, 0.7, 0.5));

    float t = 0.0;
    float transmittance = 1.0;
    vec3 accum = vec3(0.0);

    for (int i = 0; i < MAX_STEPS; i++)
    {
        if (t > FAR || transmittance < 0.01) break;

        vec3 p = ro + rd * t;

        // Limit marching to cloud layer
        if (p.y > -1.5 && p.y < 3.5)
        {
            float d = cloudDensity(p);

            if (d > 0.01)
            {
                float light = lightmarch(p, lightDir);

                vec3 cloudBase = mix(vec3(0.65, 0.68, 0.72), vec3(1.0), light);
                vec3 scatter = cloudBase * (0.35 + 0.65 * light);

                // Beer-Lambert
                float stepSize = 0.18;
                float alpha = 1.0 - exp(-d * 2.0 * stepSize);

                accum += scatter * alpha * transmittance;
                transmittance *= (1.0 - alpha);
            }
        }

        // Adaptive stepping: faster in empty space
        t += mix(0.08, 0.3, clamp(t * 0.03, 0.0, 1.0));
    }

    col = accum + skyCol * transmittance;

    // Simple tonemap + gamma
    col = col / (1.0 + col);
    col = pow(col, vec3(0.4545));

    fragColor = vec4(col, 1.0);
}