Skip to content

Instantly share code, notes, and snippets.

@mwalczyk
Created May 9, 2020 23:04
Show Gist options
  • Save mwalczyk/e1a09bab6633af0d40a8792c99ce40d1 to your computer and use it in GitHub Desktop.
Save mwalczyk/e1a09bab6633af0d40a8792c99ce40d1 to your computer and use it in GitHub Desktop.

Revisions

  1. mwalczyk created this gist May 9, 2020.
    324 changes: 324 additions & 0 deletions pathtracer.glsl
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,324 @@
    uniform float u_elapsed_frames;
    uniform vec2 u_scene_seed;
    uniform vec2 u_camera_shift;

    layout(location = 0) out vec4 o_color;

    const float pi = 3.1415926535897932384626433832795;
    const uint number_of_bounces = 8;
    const float epsilon = 0.001;
    const float max_distance = 10000.0;
    const vec3 x_axis = vec3(1.0, 0.0, 0.0);
    const vec3 y_axis = vec3(0.0, 1.0, 0.0);
    const vec3 z_axis = vec3(0.0, 0.0, 1.0);
    const vec3 origin = vec3(0.0);
    const vec3 miss = vec3(-1.0);
    const vec3 black = vec3(0.0);
    const vec3 white = vec3(1.0);

    struct sphere
    {
    float r;
    vec3 c;
    vec3 albedo;
    };

    struct plane
    {
    vec3 n;
    vec3 c;
    };

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

    float rand(vec2 seed)
    {
    return fract(sin(dot(seed, vec2(12.9898, 78.233) + u_elapsed_frames)) * 43758.5453);
    }

    vec3 palette(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d)
    {
    return a + b*cos(2.0 * pi * (c * t + d));
    }

    vec3 sphere_color(in float t)
    {
    return palette(t * 0.5, vec3(0.5, 0.5, 0.5), vec3(0.5, 0.5, 0.5), vec3(1.0, 1.0, 1.0), vec3(0.00, 0.10, 0.20));
    }

    const uint number_of_spheres = 8;
    const uint number_of_planes = 1;
    const float radius = 0.5;

    vec3 position_on_circle(int index)
    {
    float theta = (float(index) / float(number_of_spheres - 2)) * 2.0 * pi;
    const float r = 1.45;
    float x = cos(theta) * r;
    float z = sin(theta) * r;

    return vec3(x, -0.5, z);
    }

    sphere spheres[] = {
    // Ground
    sphere(100.0, vec3(0.0, -100.9, 0.0), vec3(1.0, 0.8, 0.8)),

    sphere(radius, position_on_circle(0), sphere_color(1.0 / float(number_of_spheres))),
    sphere(radius, position_on_circle(1), sphere_color(2.0 / float(number_of_spheres))),
    sphere(radius, position_on_circle(2), sphere_color(3.0 / float(number_of_spheres))),
    sphere(radius, position_on_circle(3), sphere_color(4.0 / float(number_of_spheres))),
    sphere(radius, position_on_circle(4), sphere_color(5.0 / float(number_of_spheres))),
    sphere(radius, position_on_circle(5), sphere_color(6.0 / float(number_of_spheres))),

    // Top
    sphere(0.95, vec3(0.0, 1.0, 0.0), vec3(0.8))
    };

    plane planes[] = {
    plane(y_axis, origin)
    };

    bool intersect_sphere(in sphere s, in vec3 ro, in vec3 rd, out vec3 hit)
    {
    vec3 v = ro - s.c;
    float b = 2.0 * dot(v, rd);
    float c = dot(v, v) - s.r * s.r;

    float discriminant = b * b - 4.0 * c;

    if (discriminant < 0.0)
    {
    hit = miss;
    return false;
    }

    discriminant = sqrt(discriminant);
    float t0 = -b + discriminant;
    float t1 = -b - discriminant;

    float tf;
    if (t1 > epsilon)
    {
    tf = t1;
    }
    else if (t0 > epsilon)
    {
    tf = t0;
    }
    else
    {
    hit = miss;
    return false;
    }

    hit = ro + rd * tf * 0.5;

    return true;
    }

    bool intersect_plane(in plane p, in vec3 ro, in vec3 rd, out vec3 hit)
    {
    float denom = dot(rd, p.n);

    if (abs(denom) > epsilon)
    {
    vec3 temp = p.c - ro;
    float t = dot(temp, p.n) / denom;

    hit = ro + rd * t;

    if (t >= epsilon && hit.y < 1.0)
    {
    return true;
    }
    }
    else
    {
    return false;
    }
    }

    vec3 rand_sphere(in vec3 normal, in vec2 seed)
    {
    float z = rand(seed);
    float r = sqrt(1.0 - z * z);
    float phi = 2.0 * pi * rand(seed + 100.0);
    float x = cos(phi) * r;
    float y = sin(phi) * r;

    vec3 major_axis;
    const float isqrt = 1.0 / sqrt(3.0);
    if (abs(normal.x) < isqrt)
    {
    major_axis = x_axis;
    }
    else if (abs(normal.y) < isqrt)
    {
    major_axis = y_axis;
    }
    else
    {
    major_axis = z_axis;
    }

    vec3 u = normalize(cross(major_axis, normal));
    vec3 v = cross(normal, u);
    vec3 w = normal;

    return normalize(u * x + v * y + w * z);
    }

    vec3 shade_sky(in vec3 rd)
    {
    float pct = 0.5 * (rd.y + 1.0);
    const vec3 sky = vec3(0.5, 0.7, 1.0);

    return mix(white, sky, pct);
    }

    bool intersect_scene(in vec3 ro, in vec3 rd, out vec3 closest_point)
    {
    float closest_t = max_distance;
    uint closest_index = 0;
    bool hit_anything = false;

    for(uint j = 0; j < number_of_spheres; ++j)
    {
    vec3 hit;
    if (intersect_sphere(spheres[j], ro, rd, hit))
    {
    hit_anything = true;

    float t = length(ro - hit);
    if (t < closest_t)
    {
    closest_t = t;
    closest_point = hit;
    closest_index = j;
    }
    }
    }

    for(uint j = 0; j < number_of_planes; ++j)
    {
    vec3 hit;
    if (intersect_plane(planes[j], ro, rd, hit))
    {
    hit_anything = true;

    float t = length(ro - hit);
    if (t < closest_t)
    {
    closest_t = t;
    closest_point = hit;
    closest_index = j;
    }
    }
    }

    return hit_anything;
    }

    mat3 lookat(in vec3 t, in vec3 p)
    {
    vec3 k = normalize(t - p);
    vec3 i = cross(k, vec3(0.0, 1.0, 0.0));
    vec3 j = cross(i, k);
    return mat3(i, j, k);
    }

    vec3 trace()
    {
    // Sample texture that contains random noise
    vec2 jitter = texture(sTD2DInputs[0], vUV.st).rg * 2.0 - 1.0;

    vec2 uv = vUV.st;
    uv = uv * 2.0 - 1.0;
    uv += (jitter / 1024.0) * 2.0;

    vec3 camera_position = vec3(0.0, -0.25, -4.0);
    vec3 ro = camera_position;
    ro.xy += u_camera_shift;

    vec3 rd = normalize(lookat(origin, ro) * vec3(uv, 1.0));

    vec3 attenuation = white;

    bool hit_anything = false;

    for (uint i = 0; i < number_of_bounces; ++i)
    {
    float closest_t = max_distance;
    vec3 closest_point;
    uint closest_index = 0;

    bool valid_intersection = false;

    for(uint j = 0; j < number_of_spheres; ++j)
    {
    vec3 hit;
    if (intersect_sphere(spheres[j], ro, rd, hit))
    {
    valid_intersection = true;
    hit_anything = true;

    float t = length(ro - hit);
    if (t < closest_t)
    {
    closest_t = t;
    closest_point = hit;
    closest_index = j;
    }
    }
    }

    if (i == (number_of_bounces - 1))
    {
    attenuation *= black;
    break;
    }

    if (valid_intersection)
    {
    attenuation *= spheres[closest_index].albedo;
    vec3 n = normalize(closest_point - spheres[closest_index].c);

    vec3 rand_dir = rand_sphere(n, gl_FragCoord.xy + rd.z + i);

    float mat = hash(vec2(closest_index));
    if (closest_index == 0) mat = 0.8;

    ro = closest_point + n * epsilon;
    rd = normalize(mix(reflect(rd, n), rand_dir, mat));
    }
    else
    {
    attenuation *= shade_sky(rd);
    break;
    }
    }

    if (!hit_anything)
    {
    return shade_sky(rd);
    }
    else
    {
    return attenuation;
    }
    }

    void main()
    {
    // Path tracing routine
    vec3 trace_color = trace();

    // Apply gamma correction
    trace_color = pow(trace_color, vec3(1.0 / 2.2));

    o_color = vec4(trace_color, 1.0);
    }