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:
- Scan Conversion
- Gouraud Shading
- Anti-Aliasing
Additions to the renderer's state
For this checkpoint, we only need to know a few things about the state
of the renderer.
- Current Color
- Current Clear Color
- Current Framebuffer
- Current Anti-alias level
- Current Shading mode
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:
- A matrix transformation package
- A matrix stack for hierarchical transforms
- A Z-Buffer
- Texture coordinate computations
- Viewing Specifications
- 3D triangle drawing
Additions to the renderer's state
- Current Matrix Stack
- Zbuffer enabled
- Texture mapping enabled
- Texture framebuffer
- Current texture coordinate
- Current texture mapping mode
- Current Texture Repeat Mode
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:
- Point Lights
- Parallel Lights
- Ambient Light
- Spot Lights
- Phong Shading
- Addition of normal vectors to each vertex.
- Materials
Additions to the renderer's state
- Lighting enabled
- New shading mode: phong
- Enabling of individual lights
- Current Normal vector
- Current material
- Current ambient color
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?
Feature | Value |
Required for checkpoint 1 |
2D Flat Shaded Triangles | 7 points |
Gouraud shading | 7 points |
Antialiasing | 4 points |
Required for checkpoint 2 |
3D triangles | 7 points |
Z-Buffering | 7 points |
Texture mapping | 4 points |
Required for Checkpoint 3 |
Point Lights | 5 points |
Parallel Lights | 5 points |
Spot Lights | 4 points |
Phong Shading | 4 points |
Optional Features |
Nifty User Interface (on any platform) | 2 points |
Backface Culling | 2 points |
Alpha blending | 3 points |
Atmospheric Attenuation (fog) | 3 points |
Spheres | 3 points |
Bicubic Patches | 4 points |
Procedural texture / bump maps using DSOs | 5 points |
Procedural displacement maps | 5 points |
Shadows | 7 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