2019 March 11,

CS 311: Third Part

Day 23

Ray Tracing a Sphere

600camera.c: Start with a copy of 150camera.c. Implement the following function.

/* Assumes viewport is <0, 0, width, height>. Returns the 4x4 matrix that 
transforms screen coordinates to world coordinates. Often the W-component of 
the resulting world coordinates is not 1, so perform homogeneous division after 
using this matrix. As long as the screen coordinates are truly in the viewing 
volume, the division is safe. */
void camWorldFromScreenHomogeneous(const camCamera *cam, double width, 
		double height, double homog[4][4])

600mainSphere.c: Download. Implement the sphereIntersection function and finish implementing the render function. The latter should use the former to render the two spheres, whose isometries, radii, and colors are already configured for you. Test. The keyboard handlers should help you inspect your work. Here's a screenshot of mine:

600mainSphere screenshot

Texture Mapping a Sphere

610isometry.c: Start with a copy of 140isometry.c. Please correct an oversight of mine: In isoUntransformPoint, isoRotateVector, and isoUnrotateVector, the isometry argument should be qualified const.

610vector.c: Start with a copy of 120vector.c. Add the following function, which I've written for you.

/* Partial inverse to vec3Spherical. Always returns 0 <= rho, 0 <= phi <= pi, 
and 0 <= theta <= 2 pi. In cartography this function is called the 
equirectangular projection, I think. */
void vec3Rectangular(const double v[3], double *rho, double *phi, 
		double *theta) {
	*rho = sqrt(vecDot(3, v, v));
	if (*rho == 0.0) {
		/* The point v is near the origin. */
		*phi = 0.0;
		*theta = 0.0;
	} else {
		*phi = acos(v[2] / *rho);
		double rhoSinPhi = *rho * sin(*phi);
		if (rhoSinPhi == 0.0) {
			/* The point v is near the z-axis. */
			if (v[2] >= 0.0) {
				*rho = v[2];
				*phi = 0.0;
				*theta = 0.0;
			} else {
				*rho = -v[2];
				*phi = M_PI;
				*theta = 0.0;
			}
		} else {
			/* This is the typical case. */
			*theta = atan2(v[1], v[0]);
			if (*theta < 0.0)
				*theta += 2.0 * M_PI;
		}
	}
}

610mainTexturing.c: Start with a copy of 600mainSphere.c. Include 610vector.c, 610isometry.c, and 040texture.c. Implement the following function using isoUntransformPoint, vec3Rectangular, and texSample. Then use the function to decide the pixel color in render. A screenshot of mine is below.

/* Fills the RGB color with the color sampled from the specified texture. */
void sphereColor(const isoIsometry *isom, double radius, const double e[3], 
		const double d[3], double tEnd, const texTexture *tex, double rgb[3])

610mainTexturing screenshot

Clean up and hand in all "600" and "610" files, as well as your image file.

Day 24

Today we implement shadows and mirrors — features that are difficult in rasterization but easy in ray tracing. Do not try to make your code elegant or well-abstracted. (My code is a tangled mess of global variables and poorly chosen helper functions.) It is hard to plan the correct abstractions until we've explored what ray tracing can do. We start engineering our code in the next assignment.

620mainLighting.c: Start with a copy of 610mainTexturing.c. So you already have a positional camera. Now add a directional light. Do the diffuse, specular, and ambient calculations in either local or global coordinates, but be consistent. In the specular calculation, compare the reflected camera direction to the light direction, rather than comparing the reflected light direction to the camera direction.

630mainShadows.c: Start with a copy of 620mainLighting.c. In sphereColor, add shadowing by casting a new ray toward the light from the pixel being colored. The pixel is lit if and only if the ray hits no bodies on its way to the light. A screenshot of mine is below. Notice the shadow cast by one sphere on the other.

630mainShadows screenshot

640mainMirrors.c: Start with a copy of 630mainShadows.c. Make one of the spheres, but not the other, a perfect mirror. To implement this effect, I wrote an alternative to sphereColor called sphereReflection. This function doesn't do any texture sampling or lighting calculations. Rather, it casts a new ray in the reflected camera direction and uses whatever color it finds there. Here's a screenshot:

640mainMirrors screenshot

Clean up and hand in 620mainLighting.c, 630mainShadows.c, and 640mainMirrors.c.

Day 25

Today we improve some of the engineering that underlies our ray tracing graphics engine. For the sake of simplicity, we assume that each body is texture-mapped with a single texture. We also ignore shadows and mirrors.

Abstracting the Body

650ray.c: Study. This file implements an abstract superclass (interface, protocol, etc.) for ray-traceable bodies.

650cylinder.c: Study. This file implements a class for infinitely long cylinders.

650mainBody.c: Study. It is crucial that you understand how polymorphism is implemented in the earlier files and used here. The program should draw three coordinate axes in red, green, and blue.

660sphere.c: Mimicking the cylCylinder class, write a sphereSphere class. It should have instance variables for the class, isometry, radius, and texture. Implement its two methods by adjusting the sphere code that you wrote around Day 23.

660mainBody.c: Start with a copy of 650mainBody.c. Add a demonstration that your sphere class works. Here's a screenshot of mine:

660mainBody screenshot

Plane

670plane.c: Make a plaPlane class. It should have instance variables for the class, isometry, and texture.

670mainPlane.c: Start with a copy of 660mainBody.c. Add a demonstration that your plane class works.

Clean up and hand in all "660" and "670" files and any image files that they require.

Day 26

In this assignment we abstract the lights. In addition to making our code cleaner, this work paves the way for recursive ray tracing in the next assignment. It may look like a lot of work, but most of the edits are small and use code that you've already written.

Abstracting the Light

680light.c: Study. This file implements an abstract superclass for lights.

680omnidirectional.c: Study. This file implements an omnidirectional light class.

680ray.c: Study. The color method takes more arguments than it used to. And I've moved the diffuseAndSpecular helper function into here.

680cylinder.c: Update the arguments to the coloring function to match the new specification in 680ray.c. I recommend that you do not yet change the implementation of the coloring function. Baby steps. But do change diffuseAndSpecular to rayDiffuseAndSpecular.

680sphere.c: Make the same minimal edits to the coloring function.

680plane.c: Make the same minimal edits to the coloring function.

680mainLighting.c: Include the "680" files. Delete diffuseAndSpecular. Make an omnidirectional light and a light list containing only that light. Pass the correct arguments to the coloring functions. Test. Your program should produce the same output as it did before this change, because you are not yet using the lights.

680cylinder.c: Update the coloring function so that it loops over the lights, adding each one's diffuse and specular contributions to the ambient color. Do not yet implement shadows.

680sphere.c: Update the coloring function.

680plane.c: Update the coloring function.

680mainLighting.c: Test.

More Light Classes

690directional.c: Do either this or 680spot.c; only one of the two exercises is required. Implement directional lights. That is, by mimicking 680omnidirectional.c, make dirLight and dirClass. Be careful in how you treat distance.

690spot.c: Do either this or 680directional.c; only one of the two exercises is required. Implement spot lights. That is, by mimicking 680omnidirectional.c, make spotLight and spotClass. Be careful to incorporate the spot effect into the clight returned by your lighting function.

690mainLighting.c: Add a second light to your scene, to demonstrate that your new light class is working. To clarify, your scene should have two active lights of differing classes. Configure the lights with differing colors, positions, etc. so that their visual effects are obvious. Here is a screenshot of mine, with all three kinds of light at once:

690mainLighting screenshot

Shadows Again

700ray.c: Implement the following function, probably by tweaking the queryScene function hanging out in your 690mainLighting.c.

/* If the ray does not intersect the scene, then sets index to -1. If the ray 
intersects the scene, then updates query->tEnd, outputs the index of the body 
in the list of bodies, and returns the corresponding rayResponse. */
rayResponse rayIntersection(int bodyNum, const void *bodies[], rayQuery *query, 
		int *index)

700mainShadows.c: Include 700ray.c. Delete queryScene and use rayIntersection in its place. Test. Your program should produce the same output as it did before this change.

700cylinder.c: For each light, incorporate shadows into your coloring function as follows. Cast a ray toward the light using rayIntersection. Be careful in how you configure the ending time of the ray. Incorporate the light's contribution if and only if the ray does not intersect the scene.

700sphere.c: Make the analogous edits.

700plane.c: Make the analogous edits.

700mainShadows.c: Include the "700"files. Make no other edits. Test. You should now have shadows. Here's a screenshot of mine:

700mainShadows screenshot

Clean up and hand in all "680", "690", and "700" files, unless you want to do the following optional part first.

Optional

If your code is like mine, then the coloring functions for cylinders, spheres, and planes do a lot of nearly identical calculations. Some of these calculations have already been encapsulated in rayDiffuseAndSpecular. I have encapsulated even more of the calculations into a new helper function rayMultipleDiffuseAndSpecular, in 700ray.c, which calls rayDiffuseAndSpecular. Consider doing the same now. Our coloring functions get more complicated in the next assignment.

Day 27

Today we add mirror reflection back into our ray tracing — but this time recursively. Also we implement recursive transmission of light through bodies, including refraction and total internal reflection.

Preparing for Recursion

710ray.c: Implement the following function by ripping code out of the render function in 700mainShadows.c. To understand what I mean, it might be helpful to read ahead about how 710mainMirrors.c will be edited.

/* Outputs the RGB color of the specified ray. If the ray hits nothing in the 
scene, then outputs some fixed background color. Also returns the response to 
the given query. */
rayResponse rayColor(int bodyNum, const void *bodies[], int lightNum, 
		const void *lights[], const double cAmbient[3], rayQuery *query, 
		int recursionNum, double rgb[3])

710mainMirrors.c: Start with a copy of 700mainShadows.c. Include 710ray.c. In the rendering function, for each pixel, simply construct the query and then call rayColor. Test. Your program should produce the same output as it did before this change.

710ray.c: In rayClass, add one more argument to the coloring method: int recursionNum, just before the final rgb argument. Do the same for int rayColor, and edit that function to pass int recursionNum to the body's coloring method.

710cylinder.c: Add the recursionNum argument, but don't actually use it yet. Baby steps.

710sphere.c: Add the argument.

710plane.c: Add the argument.

710mainMirrors.c: Include the "710" files. In the rendering function, pass a recursionNum of 1, say. Test. Your program should produce the same output as it did before this change.

Mirror Reflection

710cylinder.c: In the coloring method, keep the lighting (diffuse, specular, ambient), and add a mirror contribution as follows. When recursionNum is 0, the mirror contribution is black. When recursionNum is positive, a recursive call to rayColor is made, with decremented recursionNum, in the reflected camera direction. The mirror contribution is the returned color modulated with cspec.

710sphere.c: Implement mirroring.

710plane.c: Implement mirroring.

710mainMirrors.c: Test. Everything should have a mirror effect. You might want to adjust your specular color. Also test with a recursion number of 2. Here's a screenshot of mine:

710mainMirrors screenshot

Transmission

720ray.c: Add the following function, which I have written for you.

/* Given a unit outward-pointing normal, an incident ray passing through a 
medium with index of refraction indexInc, and an index of refraction indexRefr 
for the medium, through which the refracted ray should pass. Computes the 
refracted (or internally reflected) ray. To clarify the sign convention, all 
vectors are based at the point where the incident ray hits the interface; the 
incident and refracted rays point in approximately opposite directions; they 
are truly opposite when dInc = dNormal. */
void rayRefraction(const double dNormal[3], double indexInc, 
		const double dInc[3], double indexRefr, double dRefr[3]) {
	/* Gram-Schmidt to get dTan perpendicular to dNormal. */
	double dIncDNormal, proj[3], dTan[3];
	dIncDNormal = vecDot(3, dInc, dNormal);
	vecScale(3, dIncDNormal, dNormal, proj);
	vecSubtract(3, dInc, proj, dTan);
	if (vecUnit(3, dTan, dTan) == 0.0) {
		/* dInc is parallel to dNormal. The ray doesn't refract. */
		vecScale(3, -1.0, dInc, dRefr);
		return;
	}
	double sinTheta1 = vecDot(3, dInc, dTan);
	double sinTheta2 = sinTheta1 * indexInc / indexRefr;
	if (sinTheta2 > 1.0) {
		/* Total internal reflection. */
		vecScale(3, 2.0 * dIncDNormal, dNormal, dRefr);
		vecSubtract(3, dRefr, dInc, dRefr);
	} else {
		/* Refraction. */
		double cosTheta2 = sqrt(1.0 - sinTheta2 * sinTheta2);
		if (dIncDNormal < 0.0)
			cosTheta2 = -cosTheta2;
		vecScale(3, -cosTheta2, dNormal, dRefr);
		vecScale(3, -sinTheta2, dTan, dTan);
		vecAdd(3, dTan, dRefr, dRefr);
	}
}

720sphere.c: In the coloring method, keep the lighting and mirror effects, and add a transmission contribution as follows. When recursionNum is 0, the transmission contribution is black. When recursionNum is positive, a recursive call to rayColor is made, with decremented recursionNum, in the direction given by rayRefraction. The transmission contribution is the returned color modulated with ctran.

720mainTransmission.c: Include the "720" files. If there is not already a sphere in your scene, then add one. Test.

720mainTransmission screenshot

Notice that I am not asking you to implement transmission for the other primitives. Clean up and hand in all "710" and "720" files.

Day 28

There is no new homework today. Finish your old homework and study for the final exam.

The final exam is cumulative, although it focused disproportionately on the material since the previous exam. The time slot is 150 minutes long, but the exam does not have much more content than a 70-minute exam does, so you should not feel rushed. Here are a few study questions about ray tracing: