ray trace with specular:
<!-- base raytracing with specular: -->
<body bgcolor="black">
<canvas id="canvas" width="800" height="800"></canvas>
<script src="../assets/webgl.js"></script>
<script>
function Scene() {
this.vertex_shader = `#version 300 es
in vec3 a_pos;
out vec3 v_pos;
void main() {
gl_Position = vec4(a_pos, 1.);
v_pos = a_pos;
}`;
this.fragment_shader = `#version 300 es
precision highp float;
in vec3 v_pos;
out vec4 frag_col;
uniform float u_time;
//focal length: q
float f = 3.;
vec4 s = vec4(0.0, 0.0, -3.0, 0.75);
//tracing a ray to a sphere:
float ray_to_sphere(vec3 v, vec3 w, vec4 s){
v -= s.xyz; //position of camera relative to sphere.
float r = s.w;
//need dot products for the equation (W•W) t2 + 2 (W•V) t + (V•V) - r2 = 0.
//w is a unit length vector.
float vw = dot(v,w);
float vv = dot(v,v); //length of itself.
float d = vw * vw - (vv - r*r);
if (d < 0.){
return -1.0;
}else{
//hit the circle. :
return -vw - sqrt(d);
}
}
void main() {
vec3 pos = v_pos;
frag_col = vec4(vec3(0.0), 1.0);
//ray from camera to this pixel:
vec3 v = vec3(0.0); //cam position.
vec3 w = normalize(vec3(v_pos.xy, -f));
float t = ray_to_sphere(v,w,s);
if (t>=0.){
frag_col = vec4(1.0);
//find the point on the sphere's surface:
vec3 p = v + t * w;
//light pos:
vec3 l_pos = vec3(-1.0, -1.0, -2.0);
//surface normal:
vec3 n = normalize(p - s.xyz);
//direction from point to light:
vec3 d = normalize(l_pos - p);
vec3 surface = vec3 (1., 0., 0.);
vec3 ambient = .1 * surface;
//lambert's cosine:
float diffuse = max(0., dot(n,d));
vec3 c = ambient + diffuse * surface;
//specular:
//for this, we first need a reflection of the light direction about the normal:
vec3 reflected_light = 2. * dot(n, d) * n - d;
float shininess = 20.0;
vec3 spec_col = vec3(1.0);
vec3 spec = pow(max(0., dot(-w, reflected_light)), shininess) * spec_col;
//if you don't take max, you get negative (powers turn negatives positive):
// vec3 spec = pow(dot(-w, reflected_light), shininess) * spec_col;
c+=spec;
frag_col = vec4(sqrt(c), 1.);
}
}`;
let start_time = Date.now() / 1000;
this.update = () => {
let t = Date.now() / 1000 - start_time;
// console.log(t % 1);
set_uniform("1f", "u_time", t);
};
}
gl_start(canvas, new Scene());
</script>
</body>
specular highlights:
ray trace to a half space:
<!-- base cube: -->
<body bgcolor="black">
<canvas id="canvas" width="800" height="800"></canvas>
<script src="../assets/webgl.js"></script>
<script>
function Scene() {
this.vertex_shader = `#version 300 es
in vec3 a_pos;
out vec3 v_pos;
void main() {
gl_Position = vec4(a_pos, 1.);
v_pos = a_pos;
}`;
this.fragment_shader = `#version 300 es
precision highp float;
in vec3 v_pos;
out vec4 frag_col;
uniform float u_time;
//focal length: q
float f = 3.;
vec4 s = vec4(0.0, 0.0, -3.0, 0.75);
//tracing a ray to a sphere:
float ray_to_sphere(vec3 v, vec3 w, vec4 s){
v -= s.xyz; //position of camera relative to sphere.
float r = s.w;
//need dot products for the equation (W•W) t2 + 2 (W•V) t + (V•V) - r2 = 0.
//w is a unit length vector.
float vw = dot(v,w);
float vv = dot(v,v); //length of itself.
float d = vw * vw - (vv - r*r);
if (d < 0.){
return -1.0;
}else{
//hit the circle. :
return -vw - sqrt(d);
}
}
float ray_to_half_space(vec4 v, vec4 w, vec4 sp){
//ax + by + cz + d < 0.
return (dot(v, sp) / dot(w,sp));
}
void main() {
vec3 pos = v_pos;
frag_col = vec4(vec3(0.0), 1.0);
//ray from camera to this pixel:
vec4 v = vec4(0.0, 0.0, 0.0, 1.0); //cam position.
vec4 w = vec4(normalize(vec3(v_pos.xy, -f)), 0.0);
vec4 sp = vec4(0.0,1.0, 0.0, -.5);
// float t = ray_to_sphere(v,w,s);
float t = ray_to_half_space(v,w,sp);
if (t>=0.){
frag_col = vec4(1.0);
}
}`;
let start_time = Date.now() / 1000;
this.update = () => {
let t = Date.now() / 1000 - start_time;
// console.log(t % 1);
set_uniform("1f", "u_time", t);
};
}
gl_start(canvas, new Scene());
</script>
</body>
To find where the ray enters the cube, you need to take the maximum t0 of the values of t for the half-spaces that the ray enters into.
To find where the ray emerges out of the cube, you need to take the minimum t1 of the values of t for the half-spaces that the ray emerges out of.
you have 6 infinite planes, and you’re looking at all points it enters & exits.
point on the surface of the plane the my ray hits:
vec3 p = v.xyz + t * w.xyz; //ray on the surface:
frag_col = vec4(vec3(p), 1.0);
some cool stuff.
<!-- many cubes: raytracing, shadows & reflections on many cubes & many light sources -->
<body bgcolor="black">
<canvas id="canvas" width="800" height="800"></canvas>
<script src="../assets/webgl.js"></script>
<script>
//helper to map:
let map = (v, in_lo, in_hi, out_lo, out_hi) => {
return out_lo + ((v - in_lo) / (in_hi - in_lo)) * (out_hi - out_lo);
};
//helper to normalize a 3d vector:
let normalize = (v) => {
let s = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
return [v[0] / s, v[1] / s, v[2] / s];
};
//every cube: xyz = center, w = half-size.
let cubes = [];
const ring_r = 0.5;
const count = 20;
for (let i = 0; i < count; i++) {
let a = map(i, 0, count, 0, 2 * Math.PI);
let x = ring_r * Math.cos(a);
let y = ring_r * Math.sin(a);
cubes.push(x, y, -3.0, a * 0.06);
}
const num_cubes = cubes.length / 4;
const num_lights = 2;
//diffuse color of each cube:
let colors = [];
for (let i = 0; i < num_cubes; i++) {
let a = (2 * Math.PI * i) / num_cubes;
colors.push(
0.5 + 0.5 * Math.cos(a),
0.5 + 0.5 * Math.cos(a + 2.1),
0.5 + 0.5 * Math.cos(a + 4.2),
);
}
//ambient and specular for each cube:
let ambients = [];
let speculars = [];
for (let i = 0; i < num_cubes; i++) {
ambients.push(0.2 * colors[3 * i], 0.2 * colors[3 * i + 1], 0.2 * colors[3 * i + 2]);
speculars.push(0.5, 1, 1, 75);
}
//light dirs:
let lights = [normalize([-1, 1, 0]), normalize([1, -1, 2.])].flat();
//light colors (r, g, b), one entry per light:
let light_cols = [1, 0.9, 0.8, 0.3, 0.4, 0.6];
function Scene() {
this.vertex_shader = `#version 300 es
in vec3 a_pos;
out vec3 v_pos;
void main() {
gl_Position = vec4(a_pos, 1.);
v_pos = a_pos;
}`;
//note: the \${...} values below are filled in by javascript before compiling.
this.fragment_shader = `#version 300 es
precision highp float;
in vec3 v_pos;
out vec4 frag_col;
uniform float u_time;
//every cube: xyz = center, w = half-size.
uniform vec4 u_cubes[${num_cubes}];
//ambient, diffuse (surface) and specular color of every cube.
//specular: rgb = highlight color, a = shininess power.
uniform vec3 u_ambients[${num_cubes}];
uniform vec3 u_diffuses[${num_cubes}];
uniform vec4 u_speculars[${num_cubes}];
//light directions:
uniform vec3 u_lights[${num_lights}];
//light colors:
uniform vec3 u_light_cols[${num_lights}];
//focal length:
float f = 3.;
//tracing a ray to a cube; returns xyz = surface normal, w = t (or -1. on a miss).
vec4 ray_to_cube(vec3 v, vec3 w, vec4 c){
float r = c.w;
vec4 p[6];
p[0] = vec4(-1., 0., 0., c.x - r); //xlo
p[1] = vec4( 1., 0., 0., -c.x - r); //xhi
p[2] = vec4( 0., -1., 0., c.y - r); //ylo
p[3] = vec4( 0., 1., 0., -c.y - r); //yhi
p[4] = vec4( 0., 0., -1., c.z - r); //zlo
p[5] = vec4( 0., 0., 1., -c.z - r); //zhi
float t0 = -1000.; //latest entry.
float t1 = 1000.; //earliest exit.
//normal:
vec3 n = vec3(0.0);
//v & w as vec4-s:
vec4 v4 = vec4(v, 1.0);
vec4 w4 = vec4(w, 0.0);
for (int i = 0; i < 6; i++){
float wp = dot(w4, p[i]);
float t = -dot(v4, p[i]) / wp;
if (wp < 0.) {
//entering this half-space: keep the latest one.
if (t > t0) {
t0 = t;
n = p[i].xyz;
}
}
if (wp > 0.) {
//exiting this half-space: keep the earliest one.
t1 = min(t1, t);
}
}
if (t0 < t1 && t0 > 0.) {
return vec4(n, t0);
}
return vec4(0., 0., 0., -1.);
}
void main() {
frag_col = vec4(0.);
//ray from camera to this pixel:
vec3 v = vec3(0.0); //cam position.
vec3 w = normalize(vec3(v_pos.xy, -f));
// float t_min = mix(2., 20., (0.5 + 0.5 * sin(u_time)) * 0.25);
float t_min = 1000.0;
for (int i = 0; i < ${num_cubes}; i++) {
vec4 hit = ray_to_cube(v, w, u_cubes[i]);
float t = hit.w;
if (t >= 0. && t < t_min) {
t_min = t;
//point on the cube's surface:
vec3 p = v + t * w;
//surface normal (from whichever face we entered through):
vec3 n = hit.xyz;
//ambient:
vec3 c = u_ambients[i];
//add up the light from every light source:
for (int j = 0; j < ${num_lights}; j++) {
//direction from point to light:
vec3 l = u_lights[j];
//reflect the light direction around the normal:
vec3 r = 2. * dot(n, l) * n - l;
//lambert's cosine (diffuse) + specular.
//specular: how closely the bounce lines up with the direction back to the camera.
vec3 cj = .8 * max(0., dot(n, l)) * u_diffuses[i] * u_light_cols[j]
+ pow(max(0., dot(-w, r)), u_speculars[i].a) * u_speculars[i].rgb * u_light_cols[j];
//with shadows:
//we throw a ray from this point, and see if it intersects with any other cube.
vec3 ws = l;
vec3 vs = p + .001 * l;
for (int k = 0; k < ${num_cubes}; k++)
//if any cube is in front, this light is blocked:
if (ray_to_cube(vs, ws, u_cubes[k]).w > 0.)
cj = vec3(0.);
c += cj;
}
//reflection per point:
vec3 cr = vec3(0.);
vec3 wr = w - 2. * dot(n, w) * n;
vec3 vr = p + .001 * wr;
//find the nearest cube the reflected ray hits:
float tr_min = 1000.;
for (int k = 0; k < ${num_cubes}; k++) {
vec4 hitr = ray_to_cube(vr, wr, u_cubes[k]);
float tr = hitr.w;
if (tr >= 0. && tr < tr_min) {
tr_min = tr;
//if the reflected ray hits a cube, compute shading for that cube:
vec3 nr = hitr.xyz;
cr = u_ambients[k];
for (int j = 0; j < ${num_lights}; j++) {
vec3 l = u_lights[j];
vec3 r = 8. * dot(nr, l) * nr - l;
cr += .4 * max(0., dot(nr, l)) * u_diffuses[k] * u_light_cols[j]
+ pow(max(0., dot(-wr, r)), u_speculars[k].a) * u_speculars[k].rgb * u_light_cols[j];
}
}
}
c += .25 * cr;
//gamma correction:
frag_col = vec4(sqrt(c), 1.);
}
}
}`;
let start_time = Date.now() / 1000;
this.update = () => {
let t = Date.now() / 1000 - start_time;
set_uniform("1f", "u_time", t);
//rebuild for animation:
cubes = [];
for (let i = 0; i < count; i++) {
let d = i * 0.01;
let a = map(i, 0, count, 0, 2 * Math.PI);
let x = ring_r * Math.cos(a / t) * Math.sin(t);
let y = ring_r * Math.sin(a - t);
// cubes.push(x, y, map(Math.sin(a + d), -1, 1, -6.0, -2.0), d);
cubes.push(x,y,-a * 0.3, i * 0.03);
// cubes.push(x,y,-a * 0.3, 0.1 * i *);
}
// for (let i = 0; i < count; i++) {
// let a = map(i, 0, count, 0, 2 * Math.PI);
// let x = ring_r * Math.cos(a - t);
// let y = ring_r * Math.sin(a + t) + 0.5 * Math.cos(t);
// let z = -2. - a * i * 0.2; // from -3 back to about -4.1
// cubes.push(x, y, z, 0.1 + z * 0.000001);
// }
//send the arrays to the shader:
set_uniform("4fv", "u_cubes", cubes);
set_uniform("3fv", "u_ambients", ambients);
set_uniform("3fv", "u_diffuses", colors);
set_uniform("4fv", "u_speculars", speculars);
set_uniform("3fv", "u_lights", lights);
set_uniform("3fv", "u_light_cols", light_cols);
};
}
gl_start(canvas, new Scene());
</script>
</body>