px(v) { return px(v[0],v[1],v[2]); }
py(v) { return py(v[0],v[1],v[2]); }
px(x,y,z) { return (7 * x - 3.4 * z) / (10 - .1*z); }
py(x,y,z) { return (7 * y - 3.4 * z - .3*x) / (10 - .1*z); }
set(v, x, y, z) { v[0] = x; v[1] = y; v[2] = z; }
dot(v, w) { return v[0] * w[0] + v[1] * w[1] + v[2] * w[2]; }
diff(a, b, dst) {
for (j = 0 ; j < 3 ; j++)
dst[j] = a[j] - b[j];
}
normalize(vec) {
double norm = Math.sqrt(dot(vec, vec));
vec[0] /= norm;
vec[1] /= norm;
vec[2] /= norm;
}
drawEye(x, y) {
draw.setColor(Color.red);
draw.drawDisk(x,y-.01,.13,.07);
draw.fillDisk(x,y-.01,.13,.07);
draw.setColor(Color.white);
draw.drawText("eye",x,y);
draw.setColor(Color.black);
}
drawSphere(cx,cy,r,color) {
draw.setColor(color);
draw.fillDisk(cx,cy,r);
draw.setColor(Color.black);
draw.drawDisk(cx,cy,r);
}
drawArrow(ax,ay,bx,by,r,color) {
draw.setColor(color);
draw.fillArrow(ax,ay,bx,by,.012);
draw.setColor(Color.black);
draw.drawArrow(ax,ay,bx,by,.012);
}
Principles of ray tracing
Defining a ray
parameter t
double[] c0 = { .0,-.0, 1.8};
double[] c1 = { .0,-.0, 1.0};
double[] ca = {-.3,-.0,-1.8};
double[] cb = {-.3,-.0,-1.8};
draw() {
for (int j = 0 ; j < 3 ; j++) {
ca[j] = c0[j] + (c1[j] - c0[j]) * t * 5;
}
draw.setColor(Color.gray);
draw.draw(px(c0),py(c0),px(ca),py(ca),.012);
draw.setColor(Color.red);
draw.fillDisk(px(ca),py(ca),.023);
draw.setColor(Color.black);
draw.fillArrow(px(c0),py(c0),px(c1),py(c1),.012);
draw.drawRect(0,0,width-1,height-1);
draw.drawText(coords==1 ? "v = (vx,vy,vz)" : "v", px(c0) + .04, py(c0) - .02, 1);
draw.drawText(coords==1 ? "w = (wx,wy,wz)" : "w", px(c1) + .03, py(c1) - .07, 1);
draw.drawText(coords==1 ? "p = (px,py,pz)" : "p", px(ca) + .03, py(ca) - .07, 1);
s = (int)(500 * t) / 100.0;
draw.drawText("p = v + t w", -1.0, 0.7, 1);
draw.drawText("t="+s, -1.0, 0.5, 1);
}
Ray + surface + lighting
cx = 0;
cy = -.5;
cr = 0.25;
bx = 0;
by = .1;
br = 0.15;
lx = .3;
ly = .4;
lr = 0.05;
ex = -1.2;
ey = 0;
px = cx-.707*cr;
py = cy+.707*cr;
nx = -.707;
ny = +.707;
dx = ex - px;
dy = ey - py;
dot = dx * nx + dy * ny;
rx = 2 * dot * nx - dx;
ry = 2 * dot * ny - dy;
sx = px + .38 * (lx - px);
sy = py + .38 * (ly - py);
clearMagenta = new Color(255,0,255,32);
draw() {
drawSphere(cx,cy,cr,Color.cyan);
draw.drawText("object", cx, cy);
if (mode > 4)
drawSphere(bx,by,br,Color.magenta);
else if (mode > 3)
drawSphere(bx,by,br,clearMagenta);
if (mode > 0) {
drawSphere(lx,ly,lr,Color.yellow);
draw.setColor(Color.black);
draw.drawText("light", lx+.25,ly);
}
if (mode > 0) {
draw.setColor(Color.black);
draw.fillDisk(px,py,.02);
}
if (mode > 1) {
draw.setColor(Color.black);
draw.fillArrow(px,py,cx+nx*.7,cy+ny*.7,.012);
draw.drawText("N", cx+nx*.77,cy+ny*.77);
}
if (mode > 0) {
draw.setColor(Color.gray);
draw.fillArrow(ex,ey,px,py,.012);
}
if (mode > 2) {
draw.setColor(Color.gray);
draw.fillArrow(px,py,px+rx,py+ry,.012);
draw.drawText("R", px+rx*1.05,py+ry*1.05);
}
if (mode > 2)
drawArrow(px,py, mode<5 ? lx : sx, mode<5 ? ly : sy, .012, Color.orange);
drawEye(ex,ey);
}
Scanning a ray over an image
scanning
double[] c0 = { .0, .0, 1.8};
double[] c1 = { .0, .0,-1.8};
double[] p0 = { .5,-.35,0};
double[] p1 = { .5, .35,0};
double[] p2 = {-.5, .35,0};
double[] p3 = {-.5,-.35,0};
Color clearColor = new Color(0,255,255,200);
int[] X = { draw.x(px(p0)),
draw.x(px(p1)),
draw.x(px(p2)),
draw.x(px(p3)) };
int[] Y = { draw.y(py(p0)),
draw.y(py(p1)),
draw.y(py(p2)),
draw.y(py(p3)) };
double[] cp = {0,0,0};
double[] cw = {0,0,0};
draw() {
if (scanning) {
c1[0] = -1.0 + 2.0 * ((1.00 * time) % 1.0);
c1[1] = 0.7 - 1.4 * ((0.05 * time) % 1.0);
}
for (int j = 0 ; j < 3 ; j++) {
cp[j] = (c0[j] + c1[j]) / 2;
cw[j] = (c0[j] + cp[j]) / 2;
}
draw.setColor(Color.black);
draw.fillArrow(px(cp), py(cp), px(c1), py(c1), .01);
draw.setColor(clearColor);
draw.fillPolygon(X, Y, 4);
draw.setColor(Color.magenta);
draw.fillBox(px(cp), py(cp), .02);
draw.setColor(Color.black);
draw.draw(px(c0), py(c0), px(cp), py(cp), .01);
draw.setColor(Color.black);
draw.draw(px(p0), py(p0), px(p1), py(p1), .008);
draw.draw(px(p1), py(p1), px(p2), py(p2), .008);
draw.draw(px(p2), py(p2), px(p3), py(p3), .008);
draw.draw(px(p3), py(p3), px(p0), py(p0), .008);
draw.setColor(Color.red);
draw.fillDisk(px(c0),py(c0),.02);
draw.setColor(Color.black);
draw.drawText("v", px(c0) + .1, py(c0));
draw.drawText("w", px(cw) + .1, py(cw));
}
Intersecting a ray with a sphere
Combine the equation for the ray and the equation for the sphere.
A point P along a ray is:
P = [ vx + t wx , vy + t wy , vz t + t wz ] , where t 0.
A point P on a sphere at center C with radius r is:
(P - C)2 - r2 = 0
Plugging the first equation into the second:
(wx t + (vx - cx))2 + (wy t + (vy - cy))2 + (wz t + (vz - cz))2 - r2 = 0
Multiplying out and rearranging terms:
t2 wx wx +
t2 wy wy +
t2 wz wz +
2 t wx (vx-cx) +
2 t wy (vy-cy) +
2 t wz (vz-cz) +
vx-cx + vy-cy + vz-cz - r2 = 0
which is just:
(ww) t2 + 2 w(v-c) t + (v-c)(v-c) - r2 = 0
Coefficients for a quadratic equation in t
A = ww (which is 1.0)
B = 2 w(v-c)
C = (v-c)(v-c) - r2
t = -w(v-c) sqrt ( (w(v-c))2 - (v-c) (v-c) + r2)
The ray hits the sphere iff the discriminant is non-negative.
The first of the two roots is where the ray enters the sphere.
A simple ray tracer
declarations
ncols = 50;
nrows = ncols * height / width;
f = 100;
double[] v = new double[3];
double[] w = new double[3];
double[] s = { -0.1, 0.2, 0.0, 0.21 };
double[] v_s = new double[3];
double[] nn = new double[3];
double[] t = new double[2];
boolean solveQuadraticEquation(A, B, C, double[] t) {
discriminant = B * B - 4 * A * C;
if (discriminant < 0)
return false;
d = Math.sqrt(discriminant);
t[0] = (-B - d) / (2 * A);
t[1] = (-B + d) / (2 * A);
return true;
}
boolean raytrace(double[] v, double[] w, double[] t) {
diff(v, s, v_s);
A = 1.0;
B = 2 * dot(w, v_s);
C = dot(v_s, v_s) - s[3] * s[3];
return solveQuadraticEquation(A, B, C, t);
return false;
}
void computeShading(nn) {
// I leave this up to you to implement.
}
draw() {
s[0] = 0.2 * Math.sin(2 * time);
s[1] = 0.2 * Math.cos(2 * time);
s[2] = 2.2 * Math.cos(4 * time);
draw.drawBox(0,0,0.81, 0.61);
draw.drawText("cols=" + ncols, 0,.67);
draw.drawText("rows=" + nrows, -1.06, 0);
draw.drawText("f=" + f, 0,-.67);
for (i = 0 ; i < ncols ; i++)
for (j = 0 ; j < nrows ; j++) {
x = 0.8 * ((i+0.5) / ncols - 0.5);
y = 0.8 * ((j+0.5) / ncols - 0.5 * nrows / ncols);
draw.setColor(Color.black);
draw.drawRect(draw.x(2 * x), draw.y(2 * y), 1, 1);
set(v, 0, 0, f);
set(w, x, y, -f);
normalize(w);
if (raytrace(v, w, t)) {
/*
for (j = 0 ; j < 3 ; j++)
nn[j] = v[j] + t[0] * w[j] - c[j];
normalize(nn);
computeShading(nn);
*/
draw.setColor(Color.red);
draw.fillBox(2 * x, 2 * y, 1.2 / ncols);
}
}
}
HOMEWORK
Your assignment, due before class on Wednesday April 17, is to implement a ray tracer that can trace to multiple spheres, showing the nearest one in the final image. You should also incorporate light sources, materials and Phong reflectance, just as in the previous assignment.
For extra credit, you can implement shadows, as in the second diagram in these lecture notes. Remember, there is a shadow if the ray from a surface point toward a light source encounters another object. When such a shadow is detected, you should not add in any diffuse or specular component for that light source.