0.00
60.0 fps

Yet another Cornell Box

Use the slider to change the number of light bounces.

Log in to post a comment.

#version 300 es
precision highp float;

// Yet another Cornell Box. Created by Reinder Nijhoff 2019
// Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
// @reindernijhoff
//
// https://www.shadertoy.com/view/3dfGR2
// 
// Yet another Cornell Box. I have optimised the code of my shader "RIOW 2.07: Instances"
// for the Cornell Box and added direct light sampling to reduce noise. Only Lambertian 
// solid materials and cubes are supported. 
//
// These shaders are my implementation of the raytracer described in the (excellent) 
// book "Ray tracing in one weekend" and "Ray tracing: the next week"[1] by Peter Shirley 
// (@Peter_shirley).
//
// = Ray tracing in one week =
// Chapter  7: Diffuse                           https://www.shadertoy.com/view/llVcDz
// Chapter  9: Dielectrics                       https://www.shadertoy.com/view/MlVcDz
// Chapter 11: Defocus blur                      https://www.shadertoy.com/view/XlGcWh
// Chapter 12: Where next?                       https://www.shadertoy.com/view/XlycWh
//
// = Ray tracing: the next week =
// Chapter  6: Rectangles and lights             https://www.shadertoy.com/view/4tGcWD
// Chapter  7: Instances                         https://www.shadertoy.com/view/XlGcWD
// Chapter  8: Volumes                           https://www.shadertoy.com/view/XtyyDD
// Chapter  9: A Scene Testing All New Features  https://www.shadertoy.com/view/MtycDD
//
// [1] http://in1weekend.blogspot.com/2016/01/ray-tracing-in-one-weekend.html
//

uniform vec2  iResolution;
uniform float iTime;
uniform int   iFrame;
uniform int   uBounces; // value=3, min=0, max=4, step=1

out vec4 fragColor;

#define MOVE_CAMERA

#define MAX_FLOAT 1e5
#define MAX_RECURSION 4
#define EPSILON 0.01
#define SAMPLES (12+min(0,iFrame))

#define LAMBERTIAN 0
#define DIFFUSE_LIGHT 1

// Hash without Sine
// MIT License...
// Copyright (c)2014 David Hoskins.
// https://www.shadertoy.com/view/4djSRW

float g_seed = 0.;

float hash1(float p) {
    p = fract(p * .1031);
    p *= p + 33.33;
    p *= p + p;
    return fract(p);
}

vec2 hash2(inout float p) {
	vec3 p3 = fract(vec3(p+=1.) * vec3(.1031, .1030, .0973));
	p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.xx+p3.yz)*p3.zy);
}

vec3 hash3(inout float p) {
   vec3 p3 = fract(vec3(p+=1.) * vec3(.1031, .1030, .0973));
   p3 += dot(p3, p3.yzx+33.33);
   return fract((p3.xxy+p3.yzz)*p3.zyx); 
}

//
// Ray trace helper functions
//

vec3 random_cos_weighted_hemisphere_direction( const vec3 n, inout float seed ) {
  	vec2  r  = hash2(seed);
	vec3  uu = normalize(cross(n, abs(n.y) > .5 ? vec3(1.,0.,0.) : vec3(0.,1.,0.)));
	vec3  vv = cross(uu, n);
	float ra = sqrt(r.y);
	float rx = ra*cos(6.28318530718*r.x); 
	float ry = ra*sin(6.28318530718*r.x);
	float rz = sqrt(1.-r.y);
	vec3  rr = vec3(rx*uu + ry*vv + rz*n);
    return normalize(rr);
}

vec2 random_in_unit_disk(inout float seed) {
    vec2 h = hash2(seed) * vec2(1.,6.28318530718);
    float phi = h.y;
    float r = sqrt(h.x);
	return r * vec2(sin(phi),cos(phi));
}

vec3 rotate_y(const in vec3 p, const in float t) {
    float co = cos(t);
    float si = sin(t);
    vec2 xz = mat2(co,si,-si,co)*p.xz;
    return vec3(xz.x, p.y, xz.y);
}

//
// Ray
//

struct ray {
    vec3 origin, direction;
};

ray ray_translate(const in ray r, const in vec3 t) {
    ray rt = r;
    rt.origin -= t;
    return rt;
}

ray ray_rotate_y(const in ray r, const in float t) {
    ray rt = r;
    rt.origin = rotate_y(rt.origin, t);
    rt.direction = rotate_y(rt.direction, t);
    return rt;
}

//
// Material
//

struct material {
    int type;
    vec3 color;
};

//
// Hit record
//

struct hit_record {
    float t;
    vec3 p, normal;
    material mat;
};

hit_record hit_record_translate(const in hit_record h, const in vec3 t) {
    hit_record ht = h;
    ht.p -= t;
    return ht;
}
   
hit_record hit_record_rotate_y(const in hit_record h, const in float t) {
    hit_record ht = h;
    ht.p = rotate_y(ht.p, t);
    ht.normal = rotate_y(ht.normal, t);
    return ht;
}

void material_scatter(const in ray r_in, const in hit_record rec, out vec3 attenuation, 
                      out ray scattered) {
    scattered = ray(rec.p, random_cos_weighted_hemisphere_direction(rec.normal, g_seed));
    attenuation = rec.mat.color;
}

vec3 material_emitted(const in hit_record rec) {
    if (rec.mat.type == DIFFUSE_LIGHT) {
        return rec.mat.color;
    } else {
        return vec3(0);
    }
}

//
// Hitable
//

struct hitable { // always a box
    vec3 center, dimension; 
};
    
bool box_intersect(const in ray r, const in float t_min, const in float t_max,
                   const in vec3 center, const in vec3 rad, out vec3 normal, inout float dist) {
    vec3 m = 1./r.direction;
    vec3 n = m*(r.origin - center);
    vec3 k = abs(m)*rad;
	
    vec3 t1 = -n - k;
    vec3 t2 = -n + k;

	float tN = max( max( t1.x, t1.y ), t1.z );
	float tF = min( min( t2.x, t2.y ), t2.z );
	
	if( tN > tF || tF < 0.) return false;
    
    float t = tN < t_min ? tF : tN;
    if (t < t_max && t > t_min) {
        dist = t;
		normal = -sign(r.direction)*step(t1.yzx,t1.xyz)*step(t1.zxy,t1.xyz);
	    return true;
    } else {
        return false;
    }
}

bool hitable_hit(const in hitable hb, const in ray r, const in float t_min, 
                 const in float t_max, inout hit_record rec) {
    float dist;
    vec3 normal;
    if (box_intersect(r, t_min, t_max, hb.center, hb.dimension, normal, dist)) {
        rec.t = dist;
        rec.p = r.origin + dist*r.direction;
        rec.normal = normal;
        return true;
    } else {
        return false;
    }
}

//
// Camera
//

struct camera {
    vec3 origin, lower_left_corner, horizontal, vertical, u, v, w;
    float lens_radius;
};

camera camera_const(const in vec3 lookfrom, const in vec3 lookat, const in vec3 vup, 
                    const in float vfov, const in float aspect, const in float aperture, 
                    const in float focus_dist) {
    camera cam;    
    cam.lens_radius = aperture / 2.;
    float theta = vfov*3.14159265359/180.;
    float half_height = tan(theta/2.);
    float half_width = aspect * half_height;
    cam.origin = lookfrom;
    cam.w = normalize(lookfrom - lookat);
    cam.u = normalize(cross(vup, cam.w));
    cam.v = cross(cam.w, cam.u);
    cam.lower_left_corner = cam.origin  - half_width*focus_dist*cam.u -half_height*focus_dist*cam.v - focus_dist*cam.w;
    cam.horizontal = 2.*half_width*focus_dist*cam.u;
    cam.vertical = 2.*half_height*focus_dist*cam.v;
    return cam;
}
    
ray camera_get_ray(camera c, vec2 uv) {
    vec2 rd = c.lens_radius*random_in_unit_disk(g_seed);
    vec3 offset = c.u * rd.x + c.v * rd.y;
    return ray(c.origin + offset, 
               normalize(c.lower_left_corner + uv.x*c.horizontal + uv.y*c.vertical - c.origin - offset));
}

//
// Color & Scene
//

bool world_hit(const in ray r, const in float t_min, 
               const in float t_max, out hit_record rec) {
    rec.t = t_max;
    bool hit = false;

    const material red = material(LAMBERTIAN, vec3(.65,.05,.05));
    const material white = material(LAMBERTIAN, vec3(.73));
    const material green = material(LAMBERTIAN, vec3(.12,.45,.15));

    const material light = material(DIFFUSE_LIGHT, vec3(15));
    
    if (hitable_hit(hitable(vec3(278,555,279.5), vec3(65,1,52.5)),r,t_min,rec.t,rec)) 
        hit=true, rec.mat=light;   
   
    ray r_ = ray_rotate_y(ray_translate(r, vec3(130,0,65)), -18./180.*3.14159265359);
    hit_record rec_ = rec;    
    if (hitable_hit(hitable(vec3(82.5), vec3(82.5)),r_,t_min,rec.t,rec_)) 
        hit=true, 
        rec=hit_record_translate(hit_record_rotate_y(rec_, 18./180.*3.14159265359),-vec3(130,0,65.)), 
        rec.mat=white;
    
	r_ = ray_rotate_y(ray_translate(r, vec3(265,0,295)), 15./180.*3.14159265359);
    rec_ = rec;    
    if (hitable_hit(hitable(vec3(82.5,165,82.5), vec3(82.5,165,82.5)),r_,t_min,rec.t,rec_)) 
        hit=true, 
        rec=hit_record_translate(hit_record_rotate_y(rec_, -15./180.*3.14159265359),-vec3(265,0,295)), 
        rec.mat=white;

  	if (hitable_hit(hitable(vec3(556,277.5,277.5), vec3(1,277.5,277.5)),r,t_min,rec.t,rec)) 
        hit=true, rec.mat=green;
    if (hitable_hit(hitable(vec3(-1,277.5,277.5), vec3(1,277.5,277.5)),r,t_min,rec.t,rec)) 
        hit=true, rec.mat=red;
   
    if (hitable_hit(hitable(vec3(277.5,556,277.5), vec3(277.5,1,277.5)),r,t_min,rec.t,rec)) 
        hit=true, rec.mat=white;
    if (hitable_hit(hitable(vec3(277.5,-1,277.5), vec3(277.5,1,277.5)),r,t_min,rec.t,rec)) 
        hit=true, rec.mat=white;
    if (hitable_hit(hitable(vec3(277.5,277.5,556), vec3(277.5,277.5,1)),r,t_min,rec.t,rec)) 
        hit=true, rec.mat=white;
    
    return hit;
}


bool shadow_hit(const in ray r, const in float t_min, const in float t_max) {
    hit_record rec;
    rec.t = t_max;
   
    ray r_ = ray_rotate_y(ray_translate(r, vec3(130,0,65)), -18./180.*3.14159265359);  
    if (hitable_hit(hitable(vec3(82.5), vec3(82.5)),r_,t_min,rec.t,rec)) 
        return true;
    
	r_ = ray_rotate_y(ray_translate(r, vec3(265,0,295)), 15./180.*3.14159265359);  
    if (hitable_hit(hitable(vec3(82.5,165,82.5), vec3(82.5,165,82.5)),r_,t_min,rec.t,rec)) 
        return true;
  
    return false;
}

vec3 color(in ray r) {
    vec3 col = vec3(0);
    vec3 emitted = vec3(0);
	hit_record rec;
    
    for (int i=0; i<MAX_RECURSION &&  world_hit(r, EPSILON, MAX_FLOAT, rec); i++) {
        if (rec.mat.type == DIFFUSE_LIGHT) { // direct light sampling code
            return i == 0 ? rec.mat.color : emitted;
        } else if (i < uBounces) {
            vec3 attenuation;
            material_scatter(r, rec, attenuation, r);
            col = i == 0 ? attenuation : col * attenuation;
    
            // direct light sampling
            vec3 pointInSource = (2.*hash3(g_seed)-1.) * vec3(65,1,52.5) + vec3(278,555,279.5);
            vec3 L = pointInSource - rec.p;
            float rr = dot(L, L);
            L = normalize(L);
    
            ray shadowRay = ray(rec.p, L);
            if (L.y > 0.01 && dot(rec.normal, L) > 0. && !shadow_hit(shadowRay, .01, 1000.)) {
    	        const float area = (65.*52.5*4.);
                float weight = area * L.y * dot(rec.normal, L) / (3.14 * rr);
                emitted += col * 15. * weight;
            }
        }
    }
    return emitted;
}

//
// Main
//

void main() {
    float aspect = iResolution.x/iResolution.y;
#ifdef MOVE_CAMERA
    vec3 lookfrom = vec3(278. + sin(iTime * .7)*200., 278, -800. + sin(iTime)*100.);
#else
    vec3 lookfrom = vec3(278. , 278, -800.);
#endif
    vec3 lookat = vec3(278,278,0);
    g_seed = 100.*(iTime+hash1(gl_FragCoord.x)+hash1(-gl_FragCoord.y));

    vec3 tcol = vec3(0);
    
    for (int i=0, l = SAMPLES; i<l; i++) {
        vec2 uv = (gl_FragCoord.xy + hash2(g_seed))/iResolution.xy;

        camera cam = camera_const(lookfrom, lookat, vec3(0,1,0), 40., aspect, .0, 10.);
        ray r = camera_get_ray(cam, uv);
        tcol += color(r);
    }
    
    fragColor = vec4(sqrt(tcol / float(SAMPLES)), 1.);
}