Ray marching ocean waves + atmosphere

Original source: https://www.shadertoy.com/view/3lyGDd

Ported from shadertoy as part of my own learning about ray marching. It consists of an ocean (probably based on gerstner wave function since I noticed horizontal-axis motion in waves) redndered using raymarching. And an atmosphere based on rayleigh + mie models.

Shader code
shader_type canvas_item;

const float INFINITY = 999.9e16;
//const float PI = 3.14159265358979323;
const float PHI = 1.61803398874989484820459; // Golden Ratio
const float EARTH_RADIUS = 6360000.0; // in m
const float ATMOSPHERE_RADIUS = 6420000.0; // in m
const float SUN_POWER = 20.0;
const int SKY_SAMPLES = 16;
const int SUN_SAMPLES = 8;
#define SUNTIME (TIME / 2.0)
#define EPSILON_NRM (0.1 / (1024.0))//scren pixel size

const int ITER_GEOMETRY = 3;
const int ITER_FRAGMENT = 5;
const float SEA_HEIGHT = 0.6;
const float SEA_CHOPPY = 2.0;
const float SEA_SPEED = 0.8;
const float SEA_FREQ = 0.16;
const vec3 SEA_BASE = vec3(0.0, 0.09, 0.18);
const vec3 SEA_WATER_COLOR = vec3(0.8, 0.9, 0.6) * 0.6;
const mat2 octave_m = mat2(vec2(1.6, 1.2), vec2(-1.2, 1.6));
const int NUM_STEPS = 8;

float hash(vec2 p) {
    return fract(sin(dot(p.xy, vec2(12.9898, 78.233))) * 43758.5453);
}

float noise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    vec2 u = f * f * (3.0 - 2.0 * f);
    return -1.0 + 2.0 * mix(mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
                             mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);
}

float sea_octave(vec2 uv, float choppy) {
    uv += noise(uv);
    vec2 wv = 1.0 - abs(sin(uv));
    vec2 swv = abs(cos(uv));
    wv = mix(wv, swv, wv);
    return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy);
}

float map(vec3 p) {
    float freq = SEA_FREQ;
    float amp = SEA_HEIGHT;
    float choppy = SEA_CHOPPY;
    vec2 uv = p.xz;
    uv.x *= 0.75;

    float d, h = 0.0;
    for (int i = 0; i < ITER_GEOMETRY; i++) {
        d = sea_octave((uv + TIME) * freq, choppy);
        d += sea_octave((uv - TIME) * freq, choppy);
        h += d * amp;
        uv *= octave_m;
        freq *= 1.9;
        amp *= 0.22;
        choppy = mix(choppy, 1.0, 0.2);
    }
    return p.y - h;
}

float map_detailed(vec3 p) {
    float freq = SEA_FREQ;
    float amp = SEA_HEIGHT;
    float choppy = SEA_CHOPPY;
    vec2 uv = p.xz;
    uv.x *= 0.75;

    float d, h = 0.0;
    for (int i = 0; i < ITER_FRAGMENT; i++) {
        d = sea_octave((uv + TIME) * freq, choppy);
        d += sea_octave((uv - TIME) * freq, choppy);
        h += d * amp;
        uv *= octave_m;
        freq *= 1.9;
        amp *= 0.22;
        choppy = mix(choppy, 1.0, 0.2);
    }
    return p.y - h;
}

vec3 getNormal(vec3 p, float eps) {
    vec3 n;
    n.y = map_detailed(p);
    n.x = map_detailed(vec3(p.x + eps, p.y, p.z)) - n.y;
    n.z = map_detailed(vec3(p.x, p.y, p.z + eps)) - n.y;
    n.y = eps;
    return normalize(n);
}

float heightMapTracing(vec3 origin, vec3 direction, inout vec3 p) {
    float tm = 0.0;
    float tx = 1000.0;
    float hx = map(origin + direction * tx);
    if (hx > 0.0)
        return tx;
    float hm = map(origin + direction * tm);
    float tmid = 0.0;
    for (int i = 0; i < NUM_STEPS; i++) {
        tmid = mix(tm, tx, hm / (hm - hx));
        p = origin + direction * tmid;
        float hmid = map(p);
        if (hmid < 0.0) {
            tx = tmid;
            hx = hmid;
        } else {
            tm = tmid;
            hm = hmid;
        }
    }
    return tmid;
}

float diffuse(vec3 n, vec3 l, float p) {
    return pow(dot(n, l) * 0.4 + 0.6, p);
}

float specular(vec3 n, vec3 l, vec3 e, float s) {
    float nrm = (s + 8.0) / (PI * 8.0);
    return pow(max(dot(reflect(e, n), l), 0.0), s) * nrm;
}

bool solveQuadratic(float a, float b, float c, out float t0, out float t1) {
    float discrim = b * b - 4.0 * a * c;
    if (discrim < 0.0)
        return false;
    float rootDiscrim = sqrt(discrim);
    float q = (b > 0.0) ? -0.5 * (b + rootDiscrim) : -0.5 * (b - rootDiscrim);
    t1 = q / a;
    t0 = c / q;
    return true;
}

bool PlanetSphereIntersect(vec3 origin, vec3 direction, float rad, vec3 pos, inout float t0, inout float t1) {
    vec3 L = origin - pos;
    float a = dot(direction, direction);
    float b = 2.0 * dot(direction, L);
    float c = dot(L, L) - (rad * rad);

    if (!solveQuadratic(a, b, c, t0, t1))
        return false;

    float temp;
    if (t0 > t1) {
        temp = t0;
        t0 = t1;
        t1 = temp;
    }
    return true;
}

vec3 calculateSkySphereColor(vec3 origin, vec3 direction, float tmin, float tmax, vec3 sunDirection) {
    vec3 earthCenter = vec3(0, -1.0 * EARTH_RADIUS, 0);
    vec3 betaR = vec3(3.8e-6, 13.5e-6, 33.1e-6);
    vec3 betaM = vec3(21e-6);
    float Hr = 7994.0;
    float Hm = 1200.0;
    float t0, t1;
    if (!PlanetSphereIntersect(origin, direction, ATMOSPHERE_RADIUS, earthCenter, t0, t1) || t1 < 0.0)
        return vec3(0);
    if (t0 > tmin && t0 > 0.0)
        tmin = t0;
    if (t1 < tmax)
        tmax = t1;
    float segmentLength = (tmax - tmin) / float(SKY_SAMPLES);
    float tCurrent = tmin;
    vec3 sumR = vec3(0); // rayleigh contribution
    vec3 sumM = vec3(0); // mie contribution
    float opticalDepthR = 0.0;
    float opticalDepthM = 0.0;
    float mu = dot(direction, sunDirection);
    float phaseR = 3.0 / (16.0 * PI) * (1.0 + mu * mu);
    float g = 0.76;
    float phaseM = 3.0 / (8.0 * PI) * ((1.0 - g * g) * (1.0 + mu * mu)) / ((2.0 + g * g) * pow(max(0.0, 1.0 + g * g - 2.0 * g * mu), 1.5));

    for (int i = 0; i < SKY_SAMPLES; ++i) {
        vec3 samplePosition = origin + (tCurrent) * direction;
        float height = length(samplePosition - earthCenter) - EARTH_RADIUS;

        float hr = exp(-height / Hr) * segmentLength;
        float hm = exp(-height / Hm) * segmentLength;
        opticalDepthR += hr;
        opticalDepthM += hm;

        float t0Light, t1Light;
        PlanetSphereIntersect(samplePosition, sunDirection, ATMOSPHERE_RADIUS, earthCenter, t0Light, t1Light);
        float segmentLengthLight = t1Light / float(SUN_SAMPLES);
        float tCurrentLight = 0.0;
        float opticalDepthLightR = 0.0;
        float opticalDepthLightM = 0.0;
        int jCounter = 0;
        for (int j = 0; j < SUN_SAMPLES; ++j) {
            vec3 samplePositionLight = samplePosition + (tCurrentLight) * sunDirection;
            float heightLight = length(samplePositionLight - earthCenter) - EARTH_RADIUS;
            if (heightLight < 0.0)
                break;
            jCounter += 1;
            opticalDepthLightR += exp(-heightLight / Hr) * segmentLengthLight;
            opticalDepthLightM += exp(-heightLight / Hm) * segmentLengthLight;
            tCurrentLight += segmentLengthLight;
        }
        if (jCounter == SUN_SAMPLES) {
            vec3 tau = betaR * (opticalDepthR + opticalDepthLightR) + betaM * 1.1 * (opticalDepthM + opticalDepthLightM);
            vec3 attenuation = vec3(exp(-tau.x), exp(-tau.y), exp(-tau.z));
            sumR += attenuation * hr;
            sumM += attenuation * hm;
        }
        tCurrent += segmentLength;
    }

    vec3 attenuate_vec = (sumR * betaR * phaseR + sumM * betaM * phaseM) * SUN_POWER;
    if (attenuate_vec.x < 1.413) {
        attenuate_vec.x = pow(attenuate_vec.x * 0.38317, 1.0 / 2.2);
    } else {
        attenuate_vec.x = 1.0 - exp(-attenuate_vec.x);
    }
    if (attenuate_vec.y < 1.413) {
        attenuate_vec.y = pow(attenuate_vec.y * 0.38317, 1.0 / 2.2);
    } else {
        attenuate_vec.y = 1.0 - exp(-attenuate_vec.y);
    }
    if (attenuate_vec.z < 1.413) {
        attenuate_vec.z = pow(attenuate_vec.z * 0.38317, 1.0 / 2.2);
    } else {
        attenuate_vec.z = 1.0 - exp(-attenuate_vec.z);
    }
    return attenuate_vec;
}

vec3 perspectiveCameraRayDirection(vec2 pixelCoord) {
    vec2 pixelPos = (pixelCoord) * 2.0 - 1.0;
    vec3 camRight = vec3(1.0, 0.0, 0.0);
    vec3 camUp = vec3(0.0, 1.0, 0.0);
    vec3 camForward = vec3(0.0, 0.0, 1.0);
    return normalize(pixelPos.x * camRight + pixelPos.y * camUp + camForward);
}

vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) {
    float fresnel = clamp(1.0 - dot(n, -eye), 0.0, 1.0);
    fresnel = pow(fresnel, 3.0) * 0.5;

    vec3 reflected = calculateSkySphereColor(p, reflect(eye, n), 0.0, INFINITY, l);
    vec3 base = SEA_BASE * calculateSkySphereColor(p, vec3(1.0), 0.0, INFINITY, l);
    vec3 refracted = base + diffuse(n, l, 80.0) * SEA_WATER_COLOR * 0.12;

    vec3 color = mix(refracted, reflected, fresnel);

    float atten = max(1.0 - dot(dist, dist) * 0.01, 0.0);
    color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
    color += vec3(specular(n, l, eye, 20.0)) * 0.8 * reflected;
    return color;
}

vec4 pixel(vec2 uv) {
    vec3 origin = vec3(0.0, 3.0, -15.0);
    vec3 direction = perspectiveCameraRayDirection(uv);
    vec3 sunDir = normalize(vec3(0, abs(cos(SUNTIME)) / 2.0 - 0.2, abs(sin(SUNTIME)) * 2.0));
    vec3 sky = calculateSkySphereColor(origin, direction, 0.0, INFINITY, sunDir);
    vec3 p;
    heightMapTracing(origin, direction, p);
    vec3 dist = p - origin;
    vec3 n = getNormal(p, dot(dist, dist) * EPSILON_NRM);

    vec3 waves = mix(
        sky,
        getSeaColor(p, n, sunDir, direction, dist),
        pow(smoothstep(0.0, -0.5, direction.y), 0.5));

    return vec4(waves, 1.0);
}

void fragment() {
    vec2 uv = FRAGCOORD.xy / vec2(1.0/SCREEN_PIXEL_SIZE.x, 1.0/SCREEN_PIXEL_SIZE.y);
    uv.y = 1.0 - uv.y; // Invert the y-component
    vec2 aa = 0.25 / vec2(1.0/SCREEN_PIXEL_SIZE.x, 1.0/SCREEN_PIXEL_SIZE.y);
    vec4 fragColor = pixel(uv);
    fragColor += pixel(uv + aa);
    fragColor *= 0.5;
    COLOR = fragColor;
}

Tags
#shadertoy, mie, ray marching, rayleigh, raymarching, sunset
This shader is a port from an existing Shadertoy project. Shadertoy shaders are by default protected under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0) license unless anything else has been stated by the author. For more info, see our License terms.

More from mujtaba-io

sine wave camera view shader

Film Grain Shader

Gerstner Wave Ocean Shader

Related shaders

Gerstner Wave Ocean Shader

Animated selection rectangle (Marching ants)

Shield with impact waves

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments