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.);
}