COS ST2 Assignment

A Scan Converting Renderer

Due: TBA

In this assignment you will implement a full featured scan converting renderer. You must conform to the interface specifications outlined below.

Scan converting renderers, in general, deal with triangles, and triangles only, so that's what we're going to do. Any higher order primitive (bicubic patch, mesh, sphere, whatever) can be approximated using many triangles.

This renderer will be immediate mode, that is, the interface will be a library of function calls rather than an input file (no parsing!).

Your Makefile should compile all your code, as well as the provided support code, into a library file libst2.a.


The Framebuffer

You will be provided with a "virtual" framebuffer, which you can think of as a window held in memory. All your calls should write to this framebuffer. The interface is as follows:
Color c;
The type Color will hold a color type. It can hold 8 bits for each of Red, Green, Blue, and Alpha channels. Color values range from 0 to 255.
void colorSet(Color *c, unsigned char r, unsigned char g, unsigned char b);
colorSet will set the Color structure pointed to by c to contain the red, green, and blue values specified in r, g and b. The alpha component will be set to zero.
void colorSetA(Color *c, unsigned char r, unsigned char g, unsigned char b, unsigned char a);
colorSetA is the same as colorSet, except you can set the alpha channel with it. The alpha channel is used for transparency.
void colorGray(Color *c, Color *g);
colorGray convererts the color in c to grayscale, and puts the result in g. It does not modify the alpha channel of g.
unsigned char R(Color *c);
use this macro to get/set the red component of a color.
unsigned char G(Color *c);
use this macro to get/set the green component of a color.
unsigned char B(Color *c);
use this macro to get/set the blue component of a color.
unsigned char A(Color *c);
use this macro to get/set the alpha component of a color.
int fbInit(int fbWidth, int fbHeight);
fbInit will create a new framebuffer that is of the specified width and height. There are a maximum of 20 framebuffers that can be created, although you can increase this number by changing a #define command at the top of the st2.h file. A framebuffer handle will be returned to you, which you must pass to all other framebuffer routines. It will be a checked runtime error to reference a framebuffer that hasn't been initialized. All framebuffers are initialized to black.
int fbIsInitialized(int fbHandle);
Returns whether or not the framebuffer referenced by fbHandle is initialized or not.
int fbRows(int fbHandle);
fbRows returns the number of rows in the framebuffer referenced by fbHandle.
int fbCols(int fbHandle);
fbCols returns the number of columns in the framebuffer referenced by fbHandle.
void fbFree(int fbHandle);
fbFree will free all memory associated with the framebuffer referenced by fbHandle, and set that framebuffer back into the uninitialized state.
void fbPutColor(int fbHandle, int x, int y, Color *c);
fbPutColor will write the Color stored in c to the framebuffer referenced by fbHandle at the location specified by x and y. Note that (0,0) is in the lower left of the framebuffer. It is a checked runtime error for c to be null, and for x and y to be out of bounds.
void fbGetColor(int fbHandle, int x, int y, Color *c);
fbGetColor will return the Color from the framebuffer referenced by fbHandle at the location specified by x and y in the structure pointed to by c. It is a checked runtime error for c to be null, and for x and y to be out of bounds.
void fbWrite(int fbHandle, char *filename);
fbWrite will write the framebuffer referenced by fbHandle to the file specified in filename. It is a checked runtime error to specify a null filename, or a filename that the renderer can't open for writing.
int fbRead(char *filename);
fbRead will read the raw PPM file specified in filename. It is a checked runtime error to specify a null filename, or a filename that the renderer can't open for writing. This function will initialize a new framebuffer, and return the handle to it.
void fbCopy(int fbHandleDest, int fbHandleSrc);
fbCopy copies the contents of the framebuffer referenced by fbHandleSrc to the framebuffer referenced by fbHandleDest. If the framebuffer in fbHandleDest is already initialized, it will be free'd just as if you had called fbFree on it.
This should be all you need. If you want the framebuffer to have some other fancy capabilities, send mail to the staff and we'll see what we can do.

Other utilities

Other random things that you may find useful, all in the st2.h and st2.c files. Note that the DSO functions are only implemented for SGI machines. If you're working on something else, they will produce an error.
void st2_error(char *format, ...);
This printf style function will print an error to stderr, and abort your program. It never returns.
void *st2_malloc(int bytes);
This function is a "safe" malloc; that is, it checks the return value of the call to malloc before returning the block of memory. The block returned is guaranteed to consist of all zeros.
st2_free(void *);
this macro checks the value agains null, frees it, and sets it to null.
DSO d;
This type is a handle to a dynamic shared object (dso). You should use it to refer to a .so file that you have opened with dsoOpen.
DSO dsoOpen(char *filename);
dsoOpen takes a filename and opens it as a dynamic shared object. This allows you to read in code at runtime, for things like procedural shaders.
void dsoClose(DSO d);
dsoClose closes a file that must have been previously opened using dsoOpen.
void *dsoGetSymbol(DSO d, char *symbolName);
dsoGetSymbol reads in a symbol from the specified DSO, and loads it into the programs namespace. It returns a pointer to the requested data. This means that if the symbolName is a function, you will get a pointer to that function, suitable for calling from C. If the symbolname is a char *, you will get back a char **, etc.

Renderer State

The renderer you design should have "state". That is, there are many paramaters that have the notion of the "current" value of that paramater. Each state element will be outlined in this document as it is needed.

Some applications will need to access information about the internals of the renderer, but we don't want to expose the implementation to the outside world. So, we provide a routine to get the current value of any element of the current renderer's state:

int st2GetState(st2Enum request, void *data);
This function will query the renderer for the information associated with the value specified in the enum value request. If the value is simply something that is enabled or disabled, this function will return the values ST2_ENABLED or ST2_DISABLED. If the value has data associated with it, that data is copied into the memory location pointed to by the paramater data. It is a checked runtime error to pass a null pointer to this function. It is up to the user to make sure that data points to enough memory to hold the information. The enum to pass to this function is either the one with which the required value is set, or the one explicitly mentioned in this document.
In addition, the user must call the routine st2Initialize to ensure that the renderer is in a steady state at the beginning of the application. This routine should do anything you need to do to ensure that. If the user attempts to make any other st2 calls before calling st2Initialize, the results are undefined.
void st2Initialize(void);
This function intializes the renderer. Any application using the library must call this function first, otherwise unpredictable things may happen.

Checkpoint 1

For the first checkpoint, you should implement a two dimensional, triangle based rendering system. Features involved in this are:

Additions to the renderer's state

For this checkpoint, we only need to know a few things about the state of the renderer. Each of these is set as follows:
void st2Color(Color *c);
Sets the current color to the color pointed to by c. The startup value should be black. You can query this value by using the enum ST2_CUR_COLOR.
void st2ClearColor(Color *c);
Sets the current clear color to the color pointed to by c. This color will be used when clearing the framebuffer (described below). The startup value should be black. You can query this value by using the enum ST2_CUR_CLEAR_COLOR.
void st2AntiAliasQuality(int quality);
Sets the current anti-aliasing level. Typically, this will be the number of samples per pixel, but if you use a different anti-aliasing method, you should still support this command for varying qualities of anti-aliasing. The startup value should be 1 (no anti-aliasing). It is a checked run-time error to specify a value less than one. You can query this value by using the enum ST2_ANTIALIAS_QUALITY. Note that the value actually *used* is the one that is currently active when you set the current framebuffer. That is, the anti-alias quality should be thought of as a property of the framebuffer, and you can't change the value in the middle of a render unless you switch framebuffers. Acceptable values range from 1 to 5, and the passed paramater should be clamped to that range.
void st2ShadeMode(st2Enum mode);
Sets the current shading mode. Acceptable paramaters are ST2_FLAT and ST2_GOURAUD. Anything else is an error. You can query this value by using the enum ST2_SHADE_MODE.
void st2FrameBuffer(int fbHandle);
Sets the current framebuffer to the framebuffer referenced by fbHandle. It is a checked runtime error to pass a handle to an uninitialized framebuffer. All operations, unless specifically stated otherwise, refer to this framebuffer. You must set a current framebuffer before you perform any framebuffer specific operations. You can query this value by using the enum ST2_CUR_FRAMEBUFFER.

The current Frame Buffer

All operations require reads or writes from a frame buffer should be carried out in the current frame buffer unless specifically specified otherwise.

Clearing the Frame Buffer

It sounds simple, but you may need to clear the frame buffer. A frame buffer starts out as black, but you may want another background color, or you may want to use a second frame buffer as a temporary buffer, or you may want to write out several frames of an animation to disk using the same frame buffer.
void st2ClearFrameBuffer(void);
Clears the current to the current clear color. It is a checked runtime error to pass a handle to an uninitialized framebuffer.

Drawing Triangles

To draw a triangle, you must specify the three verticies. Of course, we want to be able to have a different color at each vertex. Triangles should be anti-aliased to the current anti-aliasing level, and shaded according to the current shading mode. If the mode is ST2_FLAT, then the color of the third vertex should be used. Otherwise, the triangle should be gouraud shaded.
void st2Vertex2D(double x, double y);
Specify a vertex in screen coordinates. The vertex should have the current color. The paramaters are doubles so that it is possible to specify sub-pixel positioning of points (for anti-aliasing purposes). Calls to st2Vertex2D can be made at any time, but nothing will be drawn until every third call.

Advice

Do anti-aliasing last. The simplest way to do anti-aliasing is to render the scene N times bigger than it really is (where N is the current anti-aliasing level), and then treat each NxN block as a single pixel, averaging all the color values.

Make your scan converter modular. Later on, you will need to phong shade as well as gouraud shade, so you should be able to call a procedure at each pixel. In fact, you may want to be able to call an arbitrary procedure at each pixel. Look at the rest of the assignment and figure out what needs to be done, so you don't have to rewrite your whole scan converter later.

Look for common bugs. Do something reasonable with degenerate triangles. Make sure that if triangles share an edge, there's no seam between them.

For transparency (which is optional), it may be advisable to try to make sure that you drop the right hand side pixel off your triangles (unless they're one pixel wide). You don't want to be drawing pixels twice if you specify a transparent mesh. There will be no penalty for drawing pixels twice if everything is opaque, but if you do transparency, you should do it right.

It may be faster for debugging purposes to open an OpenGL window (or an X window, or a Mac window, or a Windows window, or a NeXT window, whatever) and draw into that instead of the framebuffer provided. Not only will this make it easier to see what's going on (try adding some delay in your scan converter so it "paints" down the screen), but you can also get timing results to figure out how fast things are going.

Make this code as FAST as possible. Hand optimize it. Profile it. Unroll loops. Write special cases (lots of them). Write it in assembly on your favorite machine. This is where you will spend a lot of your time, so make this code REALLY tight. It might not even be a bad idea to special case all triangles of sizes less than about 10 pixels (if you're really insane).


Checkpoint 2

For the second checkpoint, we're going 3D! At the end of this part, you should have a 3D renderer that has texture mapping and Z-buffering. Features involved in this are:

Additions to the renderer's state

Each of these is set as follows:
void st2MatrixStack(st2Enum stack);
Sets the current matrix stack. Acceptable values are ST2_PROJECTION, ST2_MODELVIEW, and ST2_TEXTURE. You can query this value by using the enum ST2_CUR_MATRIX_STACK.
void st2Enable(st2Enum code); / void st2Disable(st2Enum code);
These functions enable and disable many features of the renderer. For this checkpoint, the only accepted values are ST2_ZBUFFER and ST2_TEXTURE.
void st2TextureImage(int fbHandle);
This function specifies which framebuffer to use as the texture image. Note that it can either be something rendered using the ST2 renderer, or an image obtained by using fbRead. You can query this value by using the enum ST2_CUR_TEXTURE_BUFFER.
void st2TextureCoord(float s, float t);
Sets the current texture coordinate. If either s or t is less than zero or more than one, it will get changed according to the Repeat Mode. You can query this value using the enum ST2_CUR_TEXTURE_COORDINATE.
void st2TextureMode(st2Enum mode);
Sets the Texture mapping mode. Acceptable values are ST2_DECAL, and ST2_MODULATE. You can query this value by using the enum ST2_TEXTURE_MODE.
void st2TextureRepeatMode(st2Enum mode);
Sets the Texture Repeat mode. Acceptable values are ST2_CLAMP and ST2_REPEAT. You can query this value by using the enum ST2_TEXTURE_REPEAT_MODE

Matrix Stacks

Our rendering system supports 4x4 matrix "stacks". This allows us to have both the notion of a "current" transformation, and also allow for transformation hierarchies. There are three matrix stacks, one for viewing, one for object transformations, and one to modify texture coordinates.

All matrix stacks are initialized to the identity matrix.

void st2LoadIdentity(void);
Sets the matrix on the top of the current stack to the identity matrix.
void st2PushMatrix(void);
Push the current matrix onto the top of the stack. At the end of this call, there should be two identical matricies on the top of the stack.
void st2PopMatrix(void);
Pop the current matrix off the stack. Incurs an error if there's only one matrix on the stack.
void st2Translate(double x, double y, double z);
Multiplies the current transformation matrix by a translation matrix.
void st2Rotate(double angle, double x, double y, double z);
Multiplies the current transformation matrix by a rotation matrix. The rotation matrix should represent a clockwise rotation of angle degrees around the vector defined by x, y and z.
void st2Scale(double x, double y, double z);
Multiplies the current transformation matrix by a scaling matrix.

Texture Mapping

Texture coordinates range from (0,0) to (1,1). Each vertex in a triangle has a texture coordinate associated with it, which should be interpolated in the same way that colors and normals are in the scan conversion process. A texture coordinate should be multiplied by the top matrix on the texture matrix stack before it is stored.

If the current texture mode is ST2_DECAL, the color from the texture map should simply be drawn. If the current texture mode is ST2_MODULATE, the color from the texture map should be considered the surface color at that point, and used as such in the lighting calculations. This won't make any difference until part 3.

Drawing 3D Triangles

To draw a triangle in 3D, we specify the coordinates of each vertex in object coordinates. The coordinates are multiplied by the top matrix on the modelview matrix stack to transform them into world coordinates. Then the world coordinates are multiplied by the top matrix in the projection matrix stack to transform them into screen coordinates. Then the x and y coordinates of the screen coordinate point can be used to scan convert a 2D triangle using the code from part 1.
void st2Vertex3D(double x, double y, double z);
Specify a vertex in object coordinates.

Z-Buffering

If the Z-buffer is turned on, the screen space z-coordinates should be maintained along with the verticies, and interpolated during the scan conversion, giving each pixel a depth from the eye. The Z-buffer test is simple; if the Z value of the pixel about to be drawn is greater than the one that is already there, the pixel is thrown away. This interpolated z value can also be used to test against the front and back clipping planes.

Viewing

In any 3D system, it's necessary to specify what the viewing frustum looks like.
void st2Perspective(double fov, double aspect, double near, double far);
Multiplies the current transformation by a matrix representing a perspective transformation. fov is the desired field of view, aspect is the aspect ratio, and near and far are the distances of the near and far clipping planes, respectively.
void st2LookAt(double eyex, double eyey, double eyez, double lookatx, double lookaty, double lookatz, double upx, double upy, double upz);
Specify the eye position, the point you are looking at, and an up vector. This should also multiply the current transformation matrix by the appropriate thing.
void st2Ortho(double minx, double maxx, double miny, double maxy, double minz, double maxz);
Specifies an orthographic projection matrix.

Checkpoint 3

For this checkpoint, you will be implementing lighting. Features involved in this are:

Additions to the renderer's state

These will be set as follows:
void st2Enable(st2Enum mode); / void st2Disable(st2Enum mode);
These functions now accept ST2_LIGHTING to turn lighting on or off, and ST2_LIGHT[0-9] to turn on or off each individual light (there are 10 of them).
void st2ShadeMode(st2Enum mode);
This function now accepts the paramater ST2_PHONG.
void st2Normal(double x, double y, double z);
Specifies the current normal vector. You can query this value by using the enum ST2_CUR_NORMAL.
void st2AmbientColor(Color *c);
Specify the current ambient light color. You can query this value by using the enum ST2_CUR_AMBIENT_COLOR.
void st2Material(Color *ambient, Color *diffuse, Color *specular, Color *emission, double shininess);
Specify the current material. ambient represents the amount that the current object reflects each component of the ambient light. diffuse represents the amount that the object diffusely scatters each component. It should be thought of as the "surface color" of the material (and is what should be modified by texture modulation). Specular represents the specular color of the object. If this is set to white, specular highlights should appear as the color of the light. emission is the amount of light being emitted by the object. Note that this doesn't mean that the object will act as a light source, but rather that this color will be added in to the final computed color, so that the object will glow. If you set all other paramaters to zero, and place an emissive object near a light, you can make the light "visible". shininess is the specular exponent of the material. You can query this value by using the enum ST2_CUR_MATERIAL. The data pointer of the st2GetState call should point to a structure that will hold the arguments to the st2Material call in order.

Normals

In order to do lighting calculations, you need to specify normals for each point. Note that it is possible to have different normals at each vertex of a polygon; this is so we can better approximate a surface with a mesh of triangles.

A normal at a point defines a plane at that point. Therefore, we should, upon specification of a normal, multiply that normal on the RIGHT by the inverse of the modelview matrix before storing it with a vertex.

Lighting

The lighting method depends on the shading mode. In ALL modes, if lighting is turned on, the color specified using st2Color has no effect on the shading computations.
ST2_FLAT
For flat shaded polygons, the lighting calculation should be performed at the third vertex of the triangle, and that color should be used in the scan conversion of the triangle.
ST2_GOURAUD
For gouraud shaded polygons, the lighting calculations should be performed only at the three verticies, and the colors should be interpolated as usual. This works great for small triangles, but for larger ones, you lose a lot of lighting information.
ST2_PHONG
For phong shaded polygons, the normals to the polygon should be interpolated along with the z coordinate and the texture coordinate, and the lighting calculations should be performed at each pixel. This will give the best results.

Lights

There are many paramaters you can specify about a light. They are all set by one function:
void st2Light(st2Enum light, st2Enum param_name, void *value);
Sets the light paramater param_name of the light light to value.
light can be any of ST2_LIGHT[0-9], since there are a maximum of 10 lights.

value should point to an appropriate piece of data for the supplied paramater. Accepted paramaters and their value types are:

ST2_LIGHT_AMBIENT
Specify the ambient color of the light. Values should be Color structures.
ST2_LIGHT_DIFFUSE
Specify the diffuse color of the light. Values should be Color structures.
ST2_LIGHT_SPECULAR
Specify the specular color of the light. Values should be Color structures.
ST2_LIGHT_POSITION
Specify the position of the light in object coordinates. Value should point to an array of four doubles. Note that if the fourth value is zero, the light is taken to be located infinitely far away, and the other three values specify a direction. Otherwise, the three values are taken to be the position of the light.
ST2_SPOT_DIRECTION
Specify the direction in which a spotlight points. This is specified in object coordinates. Value should point to an array of four doubles.
ST2_SPOT_EXPONENT
Specify the intensity distribution of a spotlight. Value should point to a double. Acceptable values range from 0 to 128.
ST2_SPOT_CUTOFF
Specify the cutoff angle for the light. Value should point to a double. Values between 0 and 90 are accepted, as well as 180. If this value is 180, all spotlight related calculations are ignored.
ST2_CONSTANT_ATTENUATION
Specify the constant attenuation factor of this light. Value should point to a double.
ST2_LINEAR_ATTENUATION
Specify the linear attenuation factor of this light. Value should point to a double.
ST2_QUADRATIC_ATTENUATION
Specify the quadratic attenuation factor of this light. Value should point to a double.
Note: You can loop over all lights, since it is guaranteed that ST2_LIGHTn = ST2_LIGHT0 + n.

Lighting contributions

So how do we do the lighting computations? Here's a small document explaining how.

Extra Credit features

There is a lot of extra stuff that can be added to a rendering system, and ours is no exception. Here I'll outline an API for the extra credit features and briefly describe them.

Note that you are responsible for testing these features on your own. People checking your code will only be looking for bugs in the required code. Extra credit will not be considered until the final turn in point, which means you have all six weeks to do it in.

Extra credit modules, in order of difficulty:

A User Interface

This one is pretty simple, and everyone should probably do it from the start. It basically involves drawing to a window on the screen as well as the virtual framebuffer. You can do this on any platform you want, although if you have an amiga or something, you will have to demonstrate that it works somehow.

Backface Culling

This is another simple one. If backface culling is enabled and lighting is turned on, then any pixel that has a normal pointing away from the eye is discarded. This is a good idea if all of your objects are solid. It allows you to throw away entire triangles that you know will be obscured.
void st2Enable(st2Enum mode); / void st2Disable(st2Enum mode);
These functions now accept the paramater ST2_BACKFACE_CULLING.

Alpha Blending

Wondering why we haven't mentioned the alpha channel up till now? It's generally used for transparency, although it can be used to give an extra 8 bits of data about a color if you want. If alpha blending is turned on, then a pixel about to be drawn into the framebuffer should be blended with the color that is already there. There are interesting viewing problems with this that stem from z-buffering. If you want real transparency, it would be a good idea to leave Z-buffering on, and make sure you draw your opaque objects first, and then draw your transparent objects from front to back.

In any case, if the incoming pixel passes the z-buffer test and alpha blending is turned on, the alpha value of the incoming pixel should be used to determine the color that is actually written into the framebuffer.

void st2Enable(st2Enum mode); / void st2Disable(st2Enum mode);
These functions now accept the paramater ST2_ALPHA_BLENDING.

Atmospheric Attenuation (Fog)

Fog can be used intelligently to add dramatic realism to a scene.
void st2Enable(st2Enum mode); / void st2Disable(st2Enum mode);
These functions now accept the paramater ST2_FOG.
void st2Fog(st2Enum mode, double density, double start, double end, Color *c);
Specify the fog paramaters. mode can be either ST2_FOG_LINEAR, ST2_FOG_EXPONENTIAL, or ST2_FOG_EXPONENTIAL2. density represents the density of the fog (from 0 to 1). start and end are used by the linear fog equation, and c is the color of the fog.
If z is the distance from the eye to the fragment to be fogged, and Cf is the color of the fog, then we compute the fog factor f in one of three ways, depending on the fog mode.

for ST2_FOG_LINEAR, we have f = (end - z)/(end-start).
for ST2_FOG_EXPONENTIAL, we have f = e^(-density*z).
for ST2_FOG_EXPONENTIAL2, we have f = (e^(-density*z))^2.

Once computed, f is clamped to the range [0,1], and then the color of the pixel to be drawn (Cr) is replaced by: f(Cr) + (1-f)(Cf).

Spheres

It would be kind of neat to have spheres.
void st2Sphere(double radius, double x, double y, double z, int divisions);
Specifies a sphere of radius radius, centered at (x,y,z) in object coordinates. This function should generate triangles approximating the sphere, based on the paramater divisions, which ranges from 1 to 10. If divisions is 1, this routine should generate a tetrahedron. If divisions is 10, this routine should generate a heck of a lot of triangles (enough to make a convincing looking sphere that fills a 500x500 framebuffer). The sphere should have correct normals at the verticies, and also texture coordinates for a spherical mapping of a texture onto the surface.

Procedural texture / bump maps using DSOs

Procedural shading provides ways to create very impressive looking pictures with little effort. If you think of phong shading as a lighting procedure that is called at each pixel, think of procedural shading as an arbitrary procedure that is called at each pixel, which computes a color to be drawn.

You should use the provided DSO facility to load in a shader at runtime. The interface to your shaders should be simple. There's no way for the API to ensure that all your shaders will be interchangable, as some of you will have different interfaces to your renderer's internals. When it comes time to worry about this, people who want to do it should sit down and come up with a common interface to internal variables so you can share shaders.

void st2ShadeMode(st2Enum mode);
This function now accepts the paramater ST2_PROCEDURAL. The default procedure is "procshade".
void st2ProceduralShader(char *name);
This procedure looks for a .so file called <name>.so in the current directory, and then in the directory in the environment variable ST2_SHADER_DIRECTORY, if there is one. If it finds one, it loads it from disk, and sets it to the current shading function to call.
In your .c file, you should create a function called "Shade" that will get called. The paramaters are up to you, and the way it returns color is up to you, although I encourage people to work out a common interface to share shaders.

Note that shaders can do just about anything. They can throw pixels away in a checkerboard pattern, for instance, and create a lace pattern. They can evaluate the mandelbrot set. Whatever!

One thing that your shaders should be able to do, in addition to modifying the color, is modifying the normal at that point. That is, the color that's computed by your procedural shader should be considered to be the current diffuse color of the material (you can have it modify anything, of course, but it has to modify the diffuse color). When the lighting computations are applied, a shader that can perturb the normal can do nifty things like make the surface look bumpy.

Procedural Displacement Maps

This is an extension to the procedural shading section above. Another common feature of high performance renderers is the ability to actually physically displace the surface according to some function. If, for example, you wanted to render a light bulb, you could model the screw on the bottom as a cylinder, and move the surface towards the center of the cylinder as some sinusoidal function. This is extremely hard to do in a raytracing system, but not as hard in a scan converting renderer.

Shadows

One main difference between raytracers and scan converting renderers is the lack of shadows in the latter. Shadows can be added in, of course, but it's quite hard.
void st2Enable(st2Enum mode); / void st2Disable(st2Enum mode);
These functions now accept the paramater ST2_SHADOWS.
You can, of course, put a raytracing system into your renderer and fire a ray at each light to see whether or not to add its contribution to the lighting. An alternate method is to render the scene from the point of view of the light, storing depth information instead of color. The resultant image is called a "shadow map". Then, when you render the scene from the point of view of the eye, you transform the point to be lit into the coordinate system of the light, and figure out what pixel in the shadow map it corresponds to. Then you simply compare the distance from the point to be lit to the light to the value stored in the shadow map. If it's equal, that means that the light can "see" the point. Otherwise, it's in shadow. Shadow maps use huge amounts of memory, and of course they are slow (although probably a lot faster than the raytracing method).

Something else

If you think of something else that would be a good addition to the renderer, let us know. Either it's big enough that you could do it as a project, or it's small enough that we'll add it here.

Turn In Policies

This assignment is very large; however, you have 6 weeks to do it. At two week intervals, you will be required to turn in what you have done so that other students may beat on your rendering system and try to break it. You should fix all the bugs reported to you and turn it back in within three days of the check in point. Any feature that is not implemented and in some wokring condition at the end of those three days will be only given half credit if it is completed at the end of the assignment. A list of features that are required at each check in point is given below.

Grading

Grading is pretty simple; each feature is worth a certain amount of points. Your final grade on the assignment will be the sum of all features that you can demonstrate work correctly (I.E. You should have a test picture that shows that feature working BY ITSELF). Extra credit will be added on, and then divided by the total sum of the possible points you could have gotten for getting all the required features right. Pretty simple, huh?

FeatureValue
Required for checkpoint 1
2D Flat Shaded Triangles7 points
Gouraud shading7 points
Antialiasing4 points
Required for checkpoint 2
3D triangles7 points
Z-Buffering7 points
Texture mapping4 points
Required for Checkpoint 3
Point Lights5 points
Parallel Lights5 points
Spot Lights4 points
Phong Shading4 points
Optional Features
Nifty User Interface (on any platform)2 points
Backface Culling2 points
Alpha blending3 points
Atmospheric Attenuation (fog)3 points
Spheres3 points
Bicubic Patches 4 points
Procedural texture / bump maps using DSOs 5 points
Procedural displacement maps 5 points
Shadows7 points

It may be the case that the API needs to be extended to support all of these features. If this is the case, DO NOT simply change the API on your own. Let us know and we'll change it so that everyone's renderers will be compatible.


Back to the assignments page
Back to the COS ST2 page