For our graphics projects, for Computer Science honours at Rhodes University, we were given the task of creating a 3D environment using OpenGL and Renderman, containing certain fundamental features (that I will list below).
The Project allowed the choice between two scenarios, either:
- Creating a fantasy island: as isolated virtual world populated by inhabitants of your choice. In this case, a fly-through of the scene is required. Interactive control may be provided, but the program should be capable of choosing a suitable default path if no input is given.
- Select an inaccurate advertisement for your choice, and produce a new version with the correct details. In this case, the video footage will consist of the images as seen from the viewer/camera position. Sound is not a requirement, a script on the web page will suffice.
The features (that I mentioned earlier) that were to be included in the project in some creative way are:
- A path to be followed by the camera/viewer, using splines.
- At least one light, producing specular highlights.
- Terrain suitable to the context.
- An animated object containing internal transformations (moving parts, for example, flapping bird, walking dog).
- Texture mapping.
- A number of objects moving according to a flocking algorithm.
- At least one of the following effects: fog, motion blur, depth of field, shadows, environment mapping, transparency.
- Use of Renderman shaders (third party shaders are permitted, if referenced).
I decided to do the fantasy Island option as my project. The first concern for me was creating the landscape. After some initial thought it occured to me that I would want a floor with alternating levels, so a grid which stored height values, but those height values must not be too varied, you don't want too many sudden changes, you basically want your changes to fall within a bell shaped curve of occurance. Much help tp Yusuf for helping me put this algorithm into code. It is set up in the constructor of the GLWorld class. During the making of the island I also added the lights to code, so that we would be able to see the images.
The code to implement the height map of the island looks like this:
level [0][0] = 0.0;
for (int i = 0; i< tiles; i++)
{
for (int j = 0; j< tiles; j++)
{
if (j == 0 || j == tiles-1 || i == 0 || i == tiles-1)level[j][i] = 0.0;
else {
level[j][i] = rand()%100 > 50 && level[j-1][i]-level_change > 0 ? level[j-1][i] - level_change : level[j-1][i] + level_change;
level[j][i] = (level[j][i-1]+level[j][i])/2.0;
}//else
}//middle for
}//first for, setting up random heights of the terrain
The lights are set up within the GLWorld class:
int light1;
int light2;
int light3;
//...
light1 = 1;
light2 = 0;
light3 = 0;
//...
tiSetLight (0, 0.0, 0.6, 0.1, 1.0);
tiSetLight (1, 0.0, 1.0, 0.2, 0.0);
tiSetLight (2, 0.0, 0.4, 1.0, 0.0);
//...
// fixed light, relative to floor.
GLfloat light2pos [] = { -2.0, 30.0, 30.0, 1.0 };
GLfloat light2dir [] = { 2.0, -30.0, -30.0, 0.0};
glLightfv (GL_LIGHT2, GL_POSITION, light2pos);
glLightfv (GL_LIGHT2, GL_SPOT_DIRECTION, light2dir);
The floor was drawn initially as follows:
for (int i = 0; i < tiles; i++)
{
for (int j = 0; j < tiles; j++)
{
double x = tilesize * (i - tiles / 2);
double z = tilesize * (j - tiles / 2);
if (((i + j) / 10) % 2)
{
GLfloat material_ambient[] = { 0.0, 0.6, 0.3, 1.0 };
GLfloat material_diffuse [] = { 0.0, 0.7, 0.2, 1.0 };
GLfloat material_specular [] = { 0.0, 0.4, 0.2, 0.8 };
GLfloat material_emission [] = { 0.01, 0.3, 0.1, 0.0 };
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, material_ambient);
glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,
material_specular);
glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION,
material_emission);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 2.0);
}
else
{
GLfloat material_ambient[] = { 0.0, 0.6, 0.2, 0.0 };
GLfloat material_diffuse [] = { 0.0, 0.9, 0.2, 0.0 };
GLfloat material_specular [] = { 0.0, 0.7, 0.2, 0.0 };
GLfloat material_emission [] = { 0.01, 0.3, 0.1, 0.0 };
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, material_ambient);
glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,
material_specular);
glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION,
material_emission);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 4.0);
}
tiPrimitiveBegin (TYPE_POLYGON);
//tiPrimitiveNormal (sin(x * 0.1), 1.0, sin(z * 0.1));
tiPrimitiveTextureCoordinate(0.0, 0.0);
tiPrimitiveVertex (x, level[i][j], z);
tiPrimitiveTextureCoordinate(0.0, 1.0);
tiPrimitiveVertex (x + tilesize, level[i+1][j], z);
tiPrimitiveTextureCoordinate(1.0, 1.0);
tiPrimitiveVertex (x + tilesize, level[i+1][j+1], z + tilesize);
tiPrimitiveTextureCoordinate(1.0, 0.0);
tiPrimitiveVertex (x, level[i][j+1], z + tilesize);
tiPrimitiveEnd ();
}
}
The result is an island that looks as follows:
and then from different angles I have:
After having accomplished an island (and doing my little dance of joy ;-) I moved onto navigation through my little Island. I wanted to have keys to let the user navigate themselves through the Island and also that the camera would follow a set path via a spline. This took some time, again thanks to Yusuf and David for help with the special keys, which was then followed by the spline. The spline used is that of a Bezier spline, I had considered a B-spline with knotts, inorder to have a little circuit right round the island, but I decided in the end it was more work and over-kill.
The code for navigation was relatively simple, for the user governed
navigation, I did this:
void keyPressEvent(QKeyEvent* e) {
switch (e->key()) {
case Key_A: m_vy-=0.05; break;
case Key_Z: m_vy+=0.05; break;
case Key_X: m_vx+=0.05; break;
case Key_C: m_vx-=0.05; break;
case Key_K: m_vz+=0.05; break;
case Key_M: m_vz-=0.05; break;
case Key_T: m_rx+=1.0; break;
case Key_Y: m_rx-=1.0; break;
case Key_G: m_ry+=1.0; break;
case Key_H: m_ry-=1.0; break;
case Key_B: m_rz+=1.0; break;
case Key_N: m_rz-=1.0; break;
}
}
and the spline was relatively simple after that: here is the source code and then it is implemented (called) from painGL() in the GLWorld class.
//camera moving via Spline, calls to the splineAt function in the
//spline class
spline->splineAt(actualpos, vx, vz);
The GLWorld class Source code
After adding the camera motion, it occured to me that my little island needed some sort of texture, so next was the quick and easy step of adding the texture, which in the end was the flowers that you saw on the island on the images above.
This was accomplished by adding the TextureLandscape Class, again
source code here
and including the following lines of code in my drawfloor() in GLWorld
class:
int textureid = TLS->textureID();
glEnable (GL_TEXTURE_2D);
glBindTexture (GL_TEXTURE_2D, textureid);
....
glDisable (GL_TEXTURE_2D);
The next task I undertook was to create the boid, or flocking algorithm. I created a flocking class and then instantiated the class in the GLWorld class, in the paintGl class. This probably took the most time for me, my knowledge of C and graphics being very limited, basically this course being the most it has been challenged. Again many thanks to Yusuf for his endless patience in explaining and really clever algorithms for accomplishing all that I wanted to do. Once I had a flock of square boids floating around my island I decided to give them some personality, so I made them a flock of dragons and mapped a dragon image onto each of them. Actually I found a number of cool dragon images, so I tend to change them regularly for a bit of variety ;-)
As I said, the boids, took a while, they can be found in the flocking
class, source code
The class was then made use of in the paintGL() in the GLWorld class:
//call boid class and draw them
for (int i = 0; i < 10; i++)
{
f->myFlock[i].boidIndex = i;
f->updateFlock(f->myFlock[i]);
}
The texture of the boids, namely creating my flock of dragons, was added to the boids in much the same way that the texturing was done for landscape. Its all in the source code.
This is what my island looks like with my boid flying around:
Misty Dragons
Black silhouettes of dragons
At this point I have accomplished 5 out of 8 tasks. I have:
- Implemented a path to be followed by the camera/viewer, using splines.
- Implemented at least one light, producing specular highlights.
- Implemented a Terrain suitable to the context.
- Implemented Texture mapping.
- Implemented a number of objects moving according to a flocking algorithm.
What I haven't done is:
- Implemented an animated object containing internal transformations (moving parts, for example: flapping bird, walking dog).
- Implemented at least one of the following effects: fog, motion blur, depth of field, shadows, environment mapping, transparency.
- Implemented use of Renderman shaders (third party shaders are permitted, if referenced).
The reasons for this are, there is currently so much other work that needs to be done, that I actually have to remove this off my todo list and continue with those, as well as I am not confident in my abilities to do any more on this project.
So basically, here is where I stop. This is, unfortunately, as far as I can go with this endeavour. It has been a lot of fun and a major learning experiance and I think in my fact I will play around a lot more with graphics and see what else I can learn.
Everything, (with the other classes needed to make everything work well) all together is available in source code here