Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    18
  • comments
    19
  • views
    14759

About this blog

We are in desolate places as dead men.

Entries in this blog

 

Texture Hunting in the Middle East

I've been trying to build up a texture library in order to texture map my levels. I brought my digital camera to the park here in Knoxville, and took some shots of the grass, some rocks by the river, the sky. They were nice but I needed a little more variety.

So I logged onto expedia and bought two round airline trip tickets to the Middle East. The first stop was in Mersin in southeastern Turkey, where I did not see another tourist at all. Next we traveled to Goreme in the middle of the country. We ended up in Istanbul. The entire trip took about half a month. While there we went hot air ballooning, horse back riding, trekking, white water rafting, and abseiling. Many oppurtunities to sample some textures.

Here is a small sample of what I was able to collect. I have hundreds more of these that need processing. If anyone is interested in the full collection in high resolution, let me know. I will let you have these to use in a non-commercial game royalty free.






































We made it back more or less intact. My wife suffered from a fractured hand, and several lacerations, cuts, and scrapes. I only had to visit a rural doctor once, where he loaded me up with four unknown prescription drugs. One of our guides was bitten by a scorpion and required hospitalization, another guide engaged in a brutal fistfight for some unknown reason, another fell down a series of stone steps and broke his arm, and yet another stepped on a rusty nail and had to be taken to the hospital.

And for those that are interested, here is a shot from our hotair balloon as we accidently violated the airspace over a maximum security prison. Maybe I will make a Turkish prison level.





jdaniel

jdaniel

 

Quad-Trees and Erratas

I finished coded and implementing the quad-tree data structure to handle my collision detection geometry. I figured that a quad-tree would do the trick because I see no reason to partition the geometry along the y axis. If my level geometry consists of several rooms above rooms, I don't think it will affect the framerate that much.

Basically I recursively subdivide the collision detection geometry into a 2D quad-tree, ignoring the y values. Then during a collision pass I build a 2D bounding box around my bounding ellipsoid representing the player's current position and the ellipsoid representing the players final position. I then, non-recursively, test this bounding box against the subdivided quads. Each quad that contained the player's bounding box is then passed to the collision detection routine.

It works well, but I need some larger level geometry to test it. With the partial test level I have constructed consisting of 555 triangles, an average of 11 triangles was passed to the collision routine. This was with 3 levels of recursion when building the quad-tree.

Next I will work on finishing the test level geometry in Maya and then texturing and populating the rooms with various geometry. This will give me a good testing environment to finish up the visibility culling. It should also make for some more interesting screenshots.

As far as visibility culling is concerned, I have decided that I will do a basic frustum cull on the bounding boxes of each object. I will then render all the "room" geometry. Finally, I will make a GL_ARB_occlusion_query call on each "non-room" bounding box that passed the frustum cull. If the occlusion query call returns an integer greater than my threshold, I will send the geometry down the pipe. My game will consists primarily of indoor environments so this approach should allow for fast framerates.

I also have a seperate code that allows me to run a Perlin noise filter through a mesh. I want to incorporate this vertex shader into my engine for various animated effects. The first geometry I will tweak with this shader will be flames. Torchlight will play a key role in my game and I would like for it to look nice.

-----------------

On a different note, I have spent a couple of evenings last week reading Alan Watt's 3D Computer Graphics (Third Edition). This was the textbook we used in Computer Graphics CS594 at the University of Tennessee when I was a teaching assistant for the class. Since then, the professor has changed to Real-Time Rendering (2nd Edition), which I currently have on order from amazon.

In the first chapter entitled, Mathematical Fundamentals of Computer Graphics I noticed several errors.

On page 14, the last sentence on the page reads, "or the angle between two
vectors is the dot product of their normalized versions". This statement
is false. I believe what the author meant to say was "or the cosine of
the angle between two vectors is the dot product of their normalized
versions".

On page 19, the last paragraph second sentence reads "This means that the
ray is in the half space, defined by the plane that does not contain the
polygon". This too is a false statement. I believe what the author meant
to say was "This means that the ray is in the half space, defined by the
plane that does not contain the polyhedron".

On page 24 there are two diagrams at the bottom of the page. The first
has a caption that reads "R = I + 2N cos theta". This is not true. What
the author probably meant was "R = I + 2N cos phi". This error is particularly
brutal because theta is used to describe another angle in the same diagram.

These mistakes are unfortunate, because they can lead the reader in the wrong direction. Watt doesn't do a great job of explaining the math in the first place, often leaving it to the reader to fill in the gaps. This approach might be suitable for a quick refresher for someone that is already familar with the concepts, but the errors might make this text particularly treacherous for someone learning for the first time.

There is also no errata that I could find online.

jdaniel

jdaniel

 

Paul Nettle, Night of the Raven, and Adaptoids

In a recent entry I made some comments about Paul Nettle's collision detection article here on gamedev. You can find his article here:

General Collision Detection for Games Using Ellipsoids

I spent some time this weekend combing through my code to try to find my mistakes. I found them and fixed them. Long story short, with the exception of adding one parallel check to abort computations if a collision could never occur (which is necessary or anomalies will appear), his pseudo-code is correct. I have implemented it fully and it works just as advertised.

In fact, his article is the best I have seen in regards to implementing sliding plane collision detection. It is still used widely in games today (i.e. Oblivion) and works really well with human shaped ellipsoids.

I went on a shopping spree also. I ordered the following books and games from Amazon:

Mathematics for 3D Game Programming and Computer Graphics Second Edition
Real-Time Rendering (2nd Edition)
OpenGL(R) Shading Language
OpenGL(R) Programming Guide : The Official Guide to Learning OpenGL(R), Version 2 (5th Edition)

and I saw Night of the Raven was finally translated to English, so although I still own the original Gothic II, I ordered this to get the expansion...
Gothic 2 Gold

I wrote the pseudo-code for a quad-tree to sort my collision detection geometry. I also pulled down a few frustum culling papers that I will leverage to cull the actual scene geometry. These are the two coding events that will occur next in my engine.

-------------

On the gaming front, I was able to play a little bit of Oblivion. I am amazed at the technology every time I play this. It is quite possibly the best RPG to date. I also played a little online of Fight Night 3 on the 360, which is also a wonderful game, with my brother that lives in D.C.

I recently hooked up my PC (which is in my office) to my home theater (that is in my living room). The whole project involved drilling through the floor several times, crawling around under the house running cables, and once nearly receiving a concussion when a huge oak piece of my home theater cabinet crashed into my skull.

But I have it hooked up now and even bought an Adaptoid for much more money that I should have spent on one. Ever heard of an adaptoid? It basically allows you to make your old Nintendo 64 into a programmable PC joystick. It is a wonderful device that is no longer being produced. So if you want one, you have to compete with the rest of the geeks on eBay. But the experience playing the original Golden Ax emulated through Mame on my big screen and surround sound with the N64 joystick is priceless. :)
-------------------

jdaniel

jdaniel

 

Smooth key scrolling

I fixed the key repeat issue with my engine tonight. Before, I was relying on the operating system key repeat setting to implement movement. So when the player held down the 'W' key, for example, the engine would move forward a frame, pause, and then start smooth scrolling forward.

There were two problems with this. First, the pause before scrolling kicked in was ugly. Second, the player could only hold down one key at a time. There was no way to hold down 'w' and 'a' at the same time and move diagonally. Now all that is fixed.

It works well with the collision detection and sliding planes also. Basically, I used the keypress routine here (as found on the old lighthouse3D website)




#include
#include

#include


float angle=0.0,deltaAngle = 0.0,ratio;
float x=0.0f,y=1.75f,z=5.0f;
float lx=0.0f,ly=0.0f,lz=-1.0f;
GLint snowman_display_list;
int deltaMove = 0;


void changeSize(int w, int h)
{

// Prevent a divide by zero, when window is too short
// (you cant make a window of zero width).
if(h == 0)
h = 1;

ratio = 1.0f * w / h;
// Reset the coordinate system before modifying
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// Set the viewport to be the entire window
glViewport(0, 0, w, h);

// Set the clipping volume
gluPerspective(45,ratio,1,1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(x, y, z,
x + lx,y + ly,z + lz,
0.0f,1.0f,0.0f);


}


void drawSnowMan() {


glColor3f(1.0f, 1.0f, 1.0f);

// Draw Body
glTranslatef(0.0f ,0.75f, 0.0f);
glutSolidSphere(0.75f,20,20);


// Draw Head
glTranslatef(0.0f, 1.0f, 0.0f);
glutSolidSphere(0.25f,20,20);

// Draw Eyes
glPushMatrix();
glColor3f(0.0f,0.0f,0.0f);
glTranslatef(0.05f, 0.10f, 0.18f);
glutSolidSphere(0.05f,10,10);
glTranslatef(-0.1f, 0.0f, 0.0f);
glutSolidSphere(0.05f,10,10);
glPopMatrix();

// Draw Nose
glColor3f(1.0f, 0.5f , 0.5f);
glRotatef(0.0f,1.0f, 0.0f, 0.0f);
glutSolidCone(0.08f,0.5f,10,2);
}



GLuint createDL() {
GLuint snowManDL;

// Create the id for the list
snowManDL = glGenLists(1);

// start list
glNewList(snowManDL,GL_COMPILE);

// call the function that contains the rendering commands
drawSnowMan();

// endList
glEndList();

return(snowManDL);
}

void initScene() {

glEnable(GL_DEPTH_TEST);
snowman_display_list = createDL();

}



void orientMe(float ang) {


lx = sin(ang);
lz = -cos(ang);
glLoadIdentity();
gluLookAt(x, y, z,
x + lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}


void moveMeFlat(int i) {
x = x + i*(lx)*0.1;
z = z + i*(lz)*0.1;
glLoadIdentity();
gluLookAt(x, y, z,
x + lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}

void renderScene(void) {

if (deltaMove)
moveMeFlat(deltaMove);
if (deltaAngle) {
angle += deltaAngle;
orientMe(angle);
}

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw ground

glColor3f(0.9f, 0.9f, 0.9f);
glBegin(GL_QUADS);
glVertex3f(-100.0f, 0.0f, -100.0f);
glVertex3f(-100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, -100.0f);
glEnd();

// Draw 36 SnowMen

for(int i = -3; i 3; i++)
for(int j=-3; j 3; j++) {
glPushMatrix();
glTranslatef(i*10.0,0,j * 10.0);
glCallList(snowman_display_list);;
glPopMatrix();
}
glutSwapBuffers();
}

void pressKey(int key, int x, int y) {

switch (key) {
case GLUT_KEY_LEFT : deltaAngle = -0.01f;break;
case GLUT_KEY_RIGHT : deltaAngle = 0.01f;break;
case GLUT_KEY_UP : deltaMove = 1;break;
case GLUT_KEY_DOWN : deltaMove = -1;break;
}
}

void releaseKey(int key, int x, int y) {

switch (key) {
case GLUT_KEY_LEFT :
case GLUT_KEY_RIGHT : deltaAngle = 0.0f;break;
case GLUT_KEY_UP :
case GLUT_KEY_DOWN : deltaMove = 0;break;
}
}

void processNormalKeys(unsigned char key, int x, int y) {

if (key == 27)
exit(0);
}

int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(640,360);
glutCreateWindow("SnowMen from Lighthouse 3D");

initScene();

glutIgnoreKeyRepeat(1);
glutKeyboardFunc(processNormalKeys);
glutSpecialFunc(pressKey);
glutSpecialUpFunc(releaseKey);

glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);

glutReshapeFunc(changeSize);

glutMainLoop();

return(0);
}





I already have a timer built into the engine to calculate framerate. Next I will modify the code to implement framerate independence.

I also want to implement a perlin noise shader soon in Cg. I plan to use this shader for various effects throughout the levels. I recently had to give a presentation regarding GPU depth buffers, so I have Cg fresh on my mind.

Sound is also on the horizon, and this is something that I do not have much experience with at the moment. I believe I will use openAl to do this, but I really know nothing about it currently. It should be interesting learning about it.

Finally, I found a bug the other day in my code that was responsible for constructing my data structures from OBJs. It was a small little looping bug, but it caused certain faces to be added twice, while the face count was only incremented once. Since the faces that were added twice were identical, there was no noticeable difference when rendering. Unfortunately, all my rendering benchmarks are now suspect. Suspect for the better, but suspect nonetheless. I might benchmark again sometime to get more accurate results, but for the moment I would like to continue coding.

jdaniel

jdaniel

 

Leveling Up

If the rules of life were really like the game Oblivion I would have woke up this morning and thought...

"So that's how you do it. What once was a struggle has now become second nature. Please distribute attribute points."

I will add +3 to my Collision Detection skill please.

I finally finished the collision detection routine in my 3D engine. This was no easy task for me and there were many times that I thought I had reached a barrier that I could not overcome. I sometimes felt at the verge of panic. All this work so far only to be defeated.

It seemed like I almost had it working several times. And the worst was when I thought I had it working. I would cautiously become optimistic only to have the chair pulled out from under me as I sat down. Inevitably, I would discover someway to clip through a corner and find the camera outside the walls of the test level.

While although shady company, printf has been my constant companion through collision detection hell. Here is a sample of one of the last debug iterations.

recursion level: 1
The distance the player wants to travel is 1.100000
The player is currently at [sourcePoint] 6.184948 0.000000 0.780793
The velocity vector is -0.164489 0.000000 -1.087632
The normalized velocity vector is -0.149535 0.000000 -0.988756
The player wants to move to 6.020459 0.000000 -0.306839
The bounding sphere radius is 1.000000
Now traversing triangle list in zone object.

Now considering triangle #24.
Point 1 on triangle: 7.000000 -1.500000 0.000000
Point 2 on triangle: 12.000000 -1.500000 0.000000
Point 3 on triangle: 7.000000 3.500000 0.000000
The origin of the plane is (a point on the triangle): 7.000000 -1.500000 0.000000
Normalized plane normal (normal of the triangle): 0.000000 0.000000 1.000000
The distance from the sphere origin to the plane is absolute 0.780793 relative 0.780793.
NOTE: If value is negative we are dealing with a backface and no additional tests will occur.
The bounding sphere is embedded in the triangle plane.
setting planeCollisionPoint to 6.184948 0.000000 0.000000 for embedded sphere.
Plane intersection point is 6.184948 0.000000 0.000000
**** crossings: 0
The planeIntersectionPoint is not contained within the triangle
Closest Point on Triangle to the collision point: 7.000000 -1.500000 0.000000
polygonIntersectionPoint is 7.000000 -1.500000 0.000000
The distance from the polygonIntersectionPoint and the sourcePoint is 1.877218
(tt) The distance from the polygonIntersectionPoint to the sphereIntersectionPoint is -1.000000

Now considering triangle #25.
Point 1 on triangle: 7.000000 -1.500000 0.000000
Point 2 on triangle: 7.000000 3.500000 0.000000
Point 3 on triangle: 7.000000 -1.500000 -5.000000
The origin of the plane is (a point on the triangle): 7.000000 -1.500000 0.000000
Normalized plane normal (normal of the triangle): -1.000000 0.000000 0.000000
The distance from the sphere origin to the plane is absolute 0.815052 relative 0.815052.
NOTE: If value is negative we are dealing with a backface and no additional tests will occur.
The bounding sphere is embedded in the triangle plane.
setting planeCollisionPoint to 7.000000 0.000000 0.780793 for embedded sphere.
Plane intersection point is 7.000000 0.000000 0.780793
**** crossings: 0
The planeIntersectionPoint is not contained within the triangle
Closest Point on Triangle to the collision point: 7.000000 -0.500000 0.000000
polygonIntersectionPoint is 7.000000 -0.500000 0.000000
The distance from the polygonIntersectionPoint and the sourcePoint is 1.234483
(tt) The distance from the polygonIntersectionPoint to the sphereIntersectionPoint is -1.000000

There was no collision with any triangle. Moving sphere origin to 6.020459 0.000000 -0.306839


recursion level: 1
The distance the player wants to travel is 1.100000
The player is currently at [sourcePoint] 6.020459 0.000000 -0.306839
The velocity vector is 0.336267 0.000000 -1.047342
The normalized velocity vector is 0.305697 0.000000 -0.952129
The player wants to move to 6.356726 0.000000 -1.354181
The bounding sphere radius is 1.000000
Now traversing triangle list in zone object.

Now considering triangle #24.
Point 1 on triangle: 7.000000 -1.500000 0.000000
Point 2 on triangle: 12.000000 -1.500000 0.000000
Point 3 on triangle: 7.000000 3.500000 0.000000
The origin of the plane is (a point on the triangle): 7.000000 -1.500000 0.000000
Normalized plane normal (normal of the triangle): 0.000000 0.000000 1.000000
The distance from the sphere origin to the plane is absolute 0.306839 relative -0.306839.
NOTE: If value is negative we are dealing with a backface and no additional tests will occur.

Now considering triangle #25.
Point 1 on triangle: 7.000000 -1.500000 0.000000
Point 2 on triangle: 7.000000 3.500000 0.000000
Point 3 on triangle: 7.000000 -1.500000 -5.000000
The origin of the plane is (a point on the triangle): 7.000000 -1.500000 0.000000
Normalized plane normal (normal of the triangle): -1.000000 0.000000 0.000000
The distance from the sphere origin to the plane is absolute 0.979541 relative 0.979541.
NOTE: If value is negative we are dealing with a backface and no additional tests will occur.
The bounding sphere is embedded in the triangle plane.
setting planeCollisionPoint to 7.000000 0.000000 -0.306839 for embedded sphere.
Plane intersection point is 7.000000 0.000000 -0.306839
**** crossings: 1
The planeIntersectionPoint lies within the triangle.
The polygonIntersectionPoint equals the planeIntersectionPoint
polygonIntersectionPoint is 7.000000 0.000000 -0.306839
The distance from the polygonIntersectionPoint and the sourcePoint is 0.979541

SANITY ERROR: The polygonIntersectionPoint is within the bounding sphere.
A previous logic error did not properly detect a collision.
ABORTING








But a long story short: I did it. I implemented collision detection with recursive sliding planes and gravity.

The document I found most helpful was General Collision Detection for Games Using Ellipsoids by Paul Nettle. You can find it here:
https://www.gamedev.net/reference/articles/article1026.asp

This wasn't a perfect article though. I found a few errors. Or at least I was not able to implement exactly what Paul was suggesting. In particular, how the sliding planes itself were calculated seemed wrong to me. Paul suggested that sliding planes be calculated like this:

Point slidePlaneOrigin = nearestPolygonIntersectionPoint;
Vector slidePlaneNormal = nearestPolygonIntersectionPoint - sourcePoint;

When implemented like this, a sliding plane would be calculated that was not parallel to the actual triangle. Paul writes, "The normal of this [sliding] plane is the vector from the intersection point to the center of the sphere". However, this created more of a "bouncing plane" rather than a true sliding plane when I implemented this. To solve this problem, I simply used the original triangles normal for the sliding plane normal. This seems to work just fine and creates a nice smooth sliding plane on which to project the movement vector.

There were a couple of small inconsistencies that could cause problems. One, the order of arguments given in his routines were not consistent with the order that he passed them in the pseudo code. This problem was compounded by the almost cryptic variable names (i.e. r, rV, s, sR). In the psuedo code, for example, Paul would write:


Double t = intersectEllipse(sourcePoint, radiusVector, polygonIntersectionPoint, negativeVelocityVector);


However, the actual pseodo-code intersectEllipse routine does not accept the arguements in this order. Additionally, radiusVector is really a double while negativeVelocityVector is actually a vector. He also uses '*' to denote a scalar multiplication and then immediatly uses '*' to denote dot product. While it can eventually be deciphered, it caused me a little uneccesary pain. I am still in debt to Paul Nettle's document, however; I used it heavily to implement my collision detection.

One thing that Paul skipped over entirely in his article was point in polygon strategies. In the pseudo-code he would say


if (planeIntersectionPoint is not within the current polygon)


So I found another article that was really good at explaining these strategies, appropriately names Point in Polygon Strategies by Eric Haines. In fact, this article appears in Graphics Gems IV. Apparently the full article is better than the one you can find on the internet, but it is still very useful.

Another really good article was Intersection of Convex Objects: The Method of Separating Axes by David Eberly.

And now I am ready to move on to another area of development. Collision detection was a huge obstacle for me to overcome and my morale has been raised considerably by being able to overcome it. I have spent so much of my free time and energy on this particular area that I seriously felt like weeping for joy when it finally worked correctly. The more time and energy I poured into this, the greater the anxiety of possible failure. But when it finally worked correctly, all that anxiety left.

Here is screenshot of the expanded test level I was using to test this. I added some stairs to make sure gravity was working as expected and created a quarter dome room to test the recursive sliding plane calls. It all looks beautiful. At least from my vantage.


jdaniel

jdaniel

 

collision and backgrounds

I have basic collision detection working now. I still need to make the routine recursive and add sliding. Geometry that can collide is now a derived zone class of the world object. Only zone geometry is considered for collision tests. Selectable items will be another derived class of the world object class.

Here is a background I rendered to use as a backdrop texture against transparent windows, etc. I may explore around more with different fractals, but as soon as I decide upon a partitcular look, I will try to keep the landscape scenery consistent.


1900x1200 uncompressed png version

jdaniel

jdaniel

 

Quake 3 running at 11,520x3072 resolution

Have you ever seen Quake 3 running at 11,520 x 3072 resolution? Well, here is your chance. It required 27 DLP projectors running at 1280x1024 each to do this:

http://members.gamedev.net/jdaniel/movies/quake3_11520x3072.mpg

jdaniel

jdaniel

 

ray casting and printf

...fun with printf and the ray equation.


...

Ray origin (players current position): -24.394674 0.000000 8.963964
Normalized Ray vector (direction player wants to move): -0.750111 0.000000 0.661312
Point on plane (a point on this polygon): -105.251305 -131.641800 -105.308907
Normalized plane normal (normal of the polygon): 0.000070 -1.000000 -0.000217
Distance from the coordinate system origin to the plane: 131.657335
There is no intersection.

Ray origin (players current position): -25.219797 0.000000 9.691408
Normalized Ray vector (direction player wants to move): -0.736096 0.000000 0.676877
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 614149.375000 units from the ray's origin.
Point of intersection: -452098.389435 0.000000 415713.071655

Ray origin (players current position): -25.219797 0.000000 9.691408
Normalized Ray vector (direction player wants to move): -0.736096 0.000000 0.676877
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 614149.375000 units from the ray's origin.
Point of intersection: -452098.389435 0.000000 415713.071655

Ray origin (players current position): -25.219797 0.000000 9.691408
Normalized Ray vector (direction player wants to move): -0.736096 0.000000 0.676877
Point on plane (a point on this polygon): -105.251305 -131.641800 -105.308907
Normalized plane normal (normal of the polygon): 0.000070 -1.000000 -0.000217
Distance from the coordinate system origin to the plane: 131.657335
There is no intersection.

Ray origin (players current position): -26.029503 0.000000 10.435972
Normalized Ray vector (direction player wants to move): -0.724172 0.000000 0.689620
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 624261.437500 units from the ray's origin.
Point of intersection: -452098.383125 0.000000 430513.545323

Ray origin (players current position): -26.029503 0.000000 10.435972
Normalized Ray vector (direction player wants to move): -0.724172 0.000000 0.689620
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 624261.437500 units from the ray's origin.
Point of intersection: -452098.383125 0.000000 430513.545323

Ray origin (players current position): -26.029503 0.000000 10.435972
Normalized Ray vector (direction player wants to move): -0.724172 0.000000 0.689620
Point on plane (a point on this polygon): -105.251305 -131.641800 -105.308907
Normalized plane normal (normal of the polygon): 0.000070 -1.000000 -0.000217
Distance from the coordinate system origin to the plane: 131.657335
There is no intersection.

Ray origin (players current position): -26.826092 0.000000 11.194554
Normalized Ray vector (direction player wants to move): -0.704634 0.000000 0.709571
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 641569.562500 units from the ray's origin.
Point of intersection: -452098.407156 0.000000 455250.467820

Ray origin (players current position): -26.826092 0.000000 11.194554
Normalized Ray vector (direction player wants to move): -0.704634 0.000000 0.709571
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 641569.562500 units from the ray's origin.
Point of intersection: -452098.407156 0.000000 455250.467820

Ray origin (players current position): -26.826092 0.000000 11.194554
Normalized Ray vector (direction player wants to move): -0.704634 0.000000 0.709571
Point on plane (a point on this polygon): -105.251305 -131.641800 -105.308907
Normalized plane normal (normal of the polygon): 0.000070 -1.000000 -0.000217
Distance from the coordinate system origin to the plane: 131.657335
There is no intersection.

Ray origin (players current position): -27.601189 0.000000 11.975082
Normalized Ray vector (direction player wants to move): -0.669130 0.000000 0.743145
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 675609.437500 units from the ray's origin.
Point of intersection: -452098.404160 0.000000 502087.743091

Ray origin (players current position): -27.601189 0.000000 11.975082
Normalized Ray vector (direction player wants to move): -0.669130 0.000000 0.743145
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 675609.437500 units from the ray's origin.
Point of intersection: -452098.404160 0.000000 502087.743091

Ray origin (players current position): -27.601189 0.000000 11.975082
Normalized Ray vector (direction player wants to move): -0.669130 0.000000 0.743145
Point on plane (a point on this polygon): -105.251305 -131.641800 -105.308907
Normalized plane normal (normal of the polygon): 0.000070 -1.000000 -0.000217
Distance from the coordinate system origin to the plane: 131.657335
There is no intersection.

Ray origin (players current position): -28.337233 0.000000 12.792542
Normalized Ray vector (direction player wants to move): -0.634730 0.000000 0.772734
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 712224.562500 units from the ray's origin.
Point of intersection: -452098.408297 0.000000 550373.130179

Ray origin (players current position): -28.337233 0.000000 12.792542
Normalized Ray vector (direction player wants to move): -0.634730 0.000000 0.772734
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 712224.562500 units from the ray's origin.
Point of intersection: -452098.408297 0.000000 550373.130179

Ray origin (players current position): -28.337233 0.000000 12.792542
Normalized Ray vector (direction player wants to move): -0.634730 0.000000 0.772734
Point on plane (a point on this polygon): -105.251305 -131.641800 -105.308907
Normalized plane normal (normal of the polygon): 0.000070 -1.000000 -0.000217
Distance from the coordinate system origin to the plane: 131.657335
There is no intersection.

Ray origin (players current position): -29.035435 0.000000 13.642550
Normalized Ray vector (direction player wants to move): -0.604598 0.000000 0.796530
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 747718.500000 units from the ray's origin.
Point of intersection: -452098.401844 0.000000 595594.222113

Ray origin (players current position): -29.035435 0.000000 13.642550
Normalized Ray vector (direction player wants to move): -0.604598 0.000000 0.796530
Point on plane (a point on this polygon): -131.641815 -131.641815 -105.251305
Normalized plane normal (normal of the polygon): -0.000291 -1.000000 0.000000
Distance from the coordinate system origin to the plane: 131.680150
An intersection will occur at 747718.500000 units from the ray's origin.
Point of intersection: -452098.401844 0.000000 595594.222113

...


jdaniel

jdaniel

 

Preliminary level...

It snowed here over the weekend, so I finally convinced myself to do some work on this project. I am about ready to code up the collision detection, and I need some level geometry in order to test.

Here is a quick pencil sketch I jotted out on graph paper. I ran the image through a filter after I scanned it to give it a "blueprint" look.



So I brought out Maya to create the geometry. I realized I was a little rusty and had pull down the reference and utilize the online docs.





After exporting to OBJ, I pulled it into the engine. I had to scale the geometry and turn on two-sided lighting. I also changed the field of view and tweaked the movement speeds.





I'll finish up this test level and maybe do some texture work.

jdaniel

jdaniel

 

silhouette detection and tool development

I've noticed silhouette detection being used more commonly in next generation titles. Perfect Dark Zero uses silhouette detection as a special ability of one of the guns. Here is an example (and yes, this was my camera aimed at the television :P)


PERFECT DARK ZERO - XBOX 360

What is of particular interest here is the way objects in the foreground are handled. Notice in this image how the silhouette of a spacecraft is being detected. However, foliage in the foreground is also being detected as well. I am not aware of any procedural advantage to doing this, but it certainly possible. I suppose this effect could be desirable since its primary utility in the game is to provide easier enemy recognition. I might write an additional post in the forum to see if anyone has any interesting comments on this particular technique.

The other 360 game that I noticed that will be using silhouette detection is the upcoming Ghost Recon. The game has not yet been released, but here is a quick example:


GHOST RECON - XBOX 360


---------------

Engine development has progressed since my last entry and has focused on hammering out the development tools. I now have a workable set of tools to get objects into the entry and saved into level files. One disadvantage of my current toolset is that everything is driven with keyboard commands and there is not GUI. However, since I am the programmer, artist, designer, engineer, and janitor of this project, I enjoy certain liberties. If there were others working on this project, I would wrap these tools into a QT gui.

At this point I really need a basic design document and I should try to decide upon a definite set of game features. I have done a little pre-vis and imagery that sets the mood for the title, and I have a general idea of what I want to go about developing here.

I know the title will be a 3D adventure title that will be rendered from a first person perspective. I am thinking that there will be about seven levels, each progressively larger. Dynamic lighting will play a very important role. One thing that will also play an important role will be text and inventory management. Of course, I will elaborate more on this later.

------------------

And from the gaming front, I have really spent more time with the XBox360 than I should. The achievement system is strangely addicting. Combine this with the COIN-OP classic releases available on Live and you have a dangerous mix. I have spent more time than I would like to admit playing Robotron 2084 trying to beat the high score on the in-game scoreboard to unlock the "high score" achievement. And after numerous tries and a few burst of profanity that frightened my dog, I did. Now I need to channel this Robotron energy into my work...

jdaniel

jdaniel

 

Code Updates and XBox 360 Impressions

I fixed a bug that in the front/back face edge culling algorithm where the face normal was not being properly multiplied against the rotation matrix.

I added the ability to rotate or translate any selected object in the world.

I added a new red-black tree that is keyed on name (a string currently) - I have plans to key this on a 32-bit CRC-Hash in the future. I kept the red-black tree that is keyed on glname, so that objects can still be properly selected. Instancing is now properly supported.

I ported the Windows code over to Linux. I tested the code on a cluster of 15 dual-processor opteron machines with Nvidia Quadro 3000G cards connected through gig-E. I was able to render to a powerwall framebuffer of 11,520 x 3072 pixels, over 7,000,000 triangles, never falling below 35 frames per second. Very nice. I was using Chromium to kidnap and distribute the openGL state. Chromium Homepage

I added the ability to post the complete world level data to standard out. Redirecting stdout to a file will create a new instance of the modified world that can then be loaded.

I added the ability to scale any object in the world universally along all axis or independently. Before scaling can be achieved, the scaling state must be turned on (which will initialize GL_NORMALIZE). This will only be a development tool. I plan to add extra data in the world file that will track the original transformation matrix before scaling occurs and the scale factors along each axis. This will allow me to go into Maya and process the meshes correctly so that scaling can be once again turned off.





I have to confess to spending a lot of my time recently on the XBox 360. I received one over Thanksgiving unexpectedly and finally figured out how to have my N64, PS2, Xbox, and XBox 360 all hooked into my home theater receiver simultaneously.

I had tried the free XBox live trials before that came with games, but I never kept the service. I have always been hesitant to subscribe to services that automatically debit my accounts. But I found it so enjoyable this time around that I purchased a prepaid one year subscription to the service.

So far, I've played Perfect Dark Zero, Gun, Call of Duty 2, Gauntlet, and Smash TV. Surprisingly, or maybe not so surprisingly, Gauntlet and Smash TV proved to be great fun over XBox Live. I really like the "Coin-Op Arcade" feature of XBox Live (which comes free for all users - no subscription required). So far the selection is limited to Gauntlet, Smash TV, and Joust, but Microsoft has contracts with most of the major IP holders of these classics and more will be rolling out soon. Basically, you can browse the selection of classic coin-op games and download a free trial of any that you choose. To unlock the full game you pay around $5 in Microsoft Points (also available in prepaid card fashion). The game is then on your XBox 360 harddrive and can be backup up to another drive if you wish.

For multiplay, the most fun I have had is with Perfect Dark Zero co-op mode. The game was designed from the ground up with Co-op in mind. When playing offline, NPC bots usually fill the void nicely with strong A.I., but having a live human on the other end of the game character is a strong allure for me. Several situations throughout the game require both players to work together. For example, there may be an elevator that needs to be hacked from another location. Once hacked the elevator rides to the top. One player has to hack, the other has to ride up into the unknown. Situations like this are really great.

I also like the "Achievement Point" system. It's almost like a MMORPG in regards to the fact that you have a global point level. Anyone that plays against you can see how many points you have accrued. This allows a social motivation for single player games. While playing Gun, I am always glad to see the "Achievement Unlocked" screen appear.

I am much more impressed with the XBox 360 than I expected to be. I love the ability to download movie trailers, game trailers, game demos, new themes, and game updates at anytime. I also like the fact you can leave voice or text mail messages for any user or send a game invite asking another friend to join in on your multiplayer game.

I am really looking forward to see what comes next.


jdaniel

jdaniel

 

Performance Profiling and Optimization

I arrived at an appropriate development stage in my graphics engine that current bottlenecks are interesting to locate and certain optimizations may be appropriate. Here is an abbreviated account of what I did to explore these bottlenecks and search for possible optimizations.

The first thing I wanted to do was to identify any openGL errors that may have crept in my code unknowingly, since these errors can be a bottleneck in certain situations. I defined the following error checking macro and wrapped all the engine's openGL calls appropriately (i.e. CHECK_OPENGL_ERROR(glPopMatrix());)



#define CHECK_OPENGL_ERROR( cmd ) cmd;
{ GLenum error;
while ( (error = glGetError()) != GL_NO_ERROR)
{ printf( "[%s:%d], '%s' failed with error %s\n",
__FILE__, __LINE__, #cmd, gluErrorString(error) ); }
}

After running the engine with the gl calls wrapped, stdout posted the following:


[C:\src\engine\main.cpp:164], 'glPopMatrix()', failed with error stack underflow

The problem was that I was popping the matrix stack at the beginning of my render function and pushing at the end of the function. This was fine for everything but the first pass through the function, but the first call to glPopMatrix would pop an empty stack. Adding a glPushMatrix call to the setup routine solved this problem. No other errors were detected.

Satisfied that no openGL errors could be degrading my performance, I was ready to identify the bottlenecks in the rendering engine. I was prepared to identify five potential bottlenecks: a framebuffer limitation, a texture limitation, a transform limitation, a transfer limitation, and a CPU limitation.

For this particular exercise, I tested on a laptop. The laptop was a Dell Latitude D810 with a Pentium 2.13GHz and 1GB of RAM. The video card was an ATI mobility Radeon X600.

First, I rendered a simple mesh of Venus twice, without instancing, to a 1900x1200 framebuffer to get a baseline framerate.



At the 1900x1200 resolution, about 40,000 vertices and 87,000 faces were rendered at 37.2 frames per second. I dropped the framebuffer size to 1280x800 and rendered identical geometry.



The framerate did not change. This suggested that either a geometric bottleneck or a application CPU bottleneck was occurring at this level of scene complexity.

Before I determined whether this was CPU (application) limited or geometry (transfer) limited, I wanted to find out where fill limitation (framebuffer) threshold was crossed.

Instead of rendering Venus, I created a course helix in Maya to allow a higher degree of mesh granularity.



At 1920x1200, the engine rendered 48,000 vertices and 49,000 faces; the framerate was 78.4.




Rendering the same scene at 1280x800 revealed a framerate of 78.4. The same geometric-limitation or CPU-limitation existed at this level.


I removed a column of helix and rendered again.




Now a framerate discrepancy was revealed. Rendering 42,000 faces at 1920x1200 offered a framerate at 85.8; while rendering to a 1280x800 framebuffer offered a framerate at 90.2. The fill-limitation of this engine running on the laptop threshold was crossed at ~45K faces.

Now I needed to go back and determine if the original bottleneck was due to my data structures (CPU-limitation) or if the graphics hardware was pushing all the geometry that was possible at the given framerate.

To accomplish this, I needed to be able push the same quantity of data from the engine to the OpenGL pipeline. I used the C preprocessor to substitute glVertex calls with glNormal calls. I created the following header file:


#define glVertex2d(x, y) glNormal3d(x, y, 0)
#define glVertex2f(x, y) glNormal3f(x, y, 0)
#define glVertex2i(x, y) glNormal3i(x, y, 0)
#define glVertex2s(x, y) glNormal3s(x, y, 0)
#define glVertex3d(x, y, z) glNormal3d(x, y, z)
#define glVertex3f(x, y, z) glNormal3f(x, y, z)
#define glVertex3i(x, y, z) glNormal3i(x, y, z)
#define glVertex3s(x, y, z) glNormal3s(x, y, z)
#define glVertex4d(x, y, z, w) glNormal3d(x, y, z)
#define glVertex4f(x, y, z, w) glNormal3f(x, y, z)
#define glVertex4i(x, y, z, w) glNormal3i(x, y, z)
#define glVertex4s(x, y, z, w) glNormal3s(x, y, z)

#define glVertex2dv(v) glNormal3d(v[0], v[1])
#define glVertex2fv(v) glNormal3f(v[0], v[1])
#define glVertex2iv(v) glNormal3i(v[0], v[1])
#define glVertex2sv(v) glNormal3s(v[0], v[1])
#define glVertex3dv(v) glNormal3dv(v)
#define glVertex3fv(v) glNormal3fv(v)
#define glVertex3iv(v) glNormal3iv(v)
#define glVertex3sv(v) glNormal3sv(v)
#define glVertex4dv(v) glNormal3dv(v)
#define glVertex4fv(v) glNormal3fv(v)
#define glVertex4iv(v) glNormal3iv(v)
#define glVertex4sv(v) glNormal3sv(v)



















I now included this header file and rendered the mesh of Venus, exactly as I done in the previous step. This was useful because the same quantity of data is being pushed to the openGL pipeline but no geometry is being rendered.




When rendering to the 1900x1200 framebuffer a framerate of 147.7 was noted - a 400% increase in performance. Similarly, using the 1280x800 framebuffer, 333 frames per second was observed - a 425% increase in performance.

This confirmed that when rendering at a scene complexity of about 45,000 faces on my laptop, the engine moves from a fill-limitation to a geometry limitation; and a CPU limitation is never observed.

To test for texturing limitations, I rendered a Poser model with 128,000 texture coordinates using a 3000x3000 resolution .png skin texture. I then resampled the .png to 128x128 and rendered again. The framerate stayed consistent regardless of texture size, thus revealing no texture limitations.

I was able to observe that no transform limitation existed by not using any visibility culling. I then observed the framerate while not moving through the scene and observed the framerate while moving through the scene. The framerate stayed consistent, thus revealing that no transform limitation existed.



Another area of interest at this stage of development is the object selection feedback algorithms. In fact, there was much room for appropriate optimization here. For a baseline reading, I rendered a hellskull mesh to a 1900x1200 framebuffer.



The baseline revealed that rendering 14,000 faces to 1920x1200 framebuffer allowed the engine to maintain a 79.8 framerate.



When placing the mouse cursor over the skull and selected it, the following occurs:


While rendering the correct selection feedback silhouette, the framerate drops to 38.4 - a 52% drop in performance.

To achieve the silhouette effect, I was doing the following:
1. Setting the stencil buffer to create the stencil
2. Rendering the entire mesh into the stencil buffer
3. Setting the stencil buffer to use the stencil
4. Setting line width
4. Rendering the entire object in wireframe mode

This worked well visually. The line width stayed the same no matter where the camera was placed. But there were two things here that could be changed that might improve performance.

First, there was no reason to render the entire image (texture and normal calls included) to create the stencil. So I changed the code to create a display list that was as simple as possible to get the stencil. This revealed another problem in regards to the use of the glStencilOp state function. This was corrected.

Second, there was no need to render the entire wireframe when only the silhouette was needed. So I decided to render only the front and back facing edges.

This was not trivial, as it turned out. First, I had to go back into my original OBJ parsing code to create the edge list. I also had to move my camera class into the world class so I could have convenient access to the eye vector. Once this was accomplished, I had to decide what was a front facing polygon and what was a back facing polygon independent of viewpoint. I implemented this algorithm as a world class method as follows:

>
// This is an function that will render a silhouette around a selected object (if any) in the scene.
void myWorld::renderSelectedOutline(void) {

JRB tempedges; JRB myedges;
obj_edge *thisedge;
JRB tempfaces;
obj_face *thisface;
float eyeVector[3];
float newPoints[3];
float normVector[3];
float eyeDot[2];
GLfloat *tmat;

int i;

// if there is not a currently selected object, this function will return
if (selectedWorldObject == NULL) {
return;
}
else
{

myedges = selectedWorldObject->localobj->getEdgesJRB();
tmat = selectedWorldObject->getMatrix();

glPushMatrix();

glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
//glDisable(GL_DEPTH_TEST);

glPushMatrix();

// prepare stencil for rendering
glClearStencil(0x0);
glClear(GL_STENCIL_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
glStencilFunc (GL_ALWAYS, 0x1, 0x1);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

glMultMatrixf(selectedWorldObject->transMatrix);
glCallList(selectedWorldObject->stencillist);

// set stencil
glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);


glPopMatrix();

glLineWidth(3);

glMultMatrixf(selectedWorldObject->transMatrix);

jrb_traverse(tempedges, myedges)
{
thisedge = (obj_edge *)tempedges->val.v;


// This optimization is only concerned with edges that connect exactly two faces
if (thisedge->face_count == 2)
{


// The reference point used to calculate the eye vector is multiplied against the transformation matrix of this object
newPoints[0] = (tmat[0] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)]) +
(tmat[4] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+1]) +
(tmat[8] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+2]) + (tmat[12]);
newPoints[1] = (tmat[1] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)]) +
(tmat[5] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+1]) +
(tmat[9] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+2]) + (tmat[13]);
newPoints[2] = (tmat[2] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)]) +
(tmat[6] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+1]) +
(tmat[10] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+2]) + (tmat[14]);


// The eye vector is created
eyeVector[0] = -mycamera->cameraPosition[0] - newPoints[0];
eyeVector[1] = -mycamera->cameraPosition[1] - newPoints[1];
eyeVector[2] = -mycamera->cameraPosition[2] - newPoints[2];

i = 0;
jrb_traverse(tempfaces, thisedge->face_list)
{
thisface = (obj_face *)tempfaces->val.v;

// The surface normal of the face is multiplied against the rotation matrix. translations do not affect the normal.
normVector[0] = (tmat[0] * thisface->surface_normal[0]) + (tmat[4] * thisface->surface_normal[1]) + (tmat[8] * thisface->surface_normal[2]);
normVector[1] = (tmat[1] * thisface->surface_normal[0]) + (tmat[5] * thisface->surface_normal[1]) + (tmat[9] * thisface->surface_normal[2]);
normVector[2] = (tmat[2] * thisface->surface_normal[0]) + (tmat[6] * thisface->surface_normal[1]) + (tmat[10] * thisface->surface_normal[2]);


//calculate the dot product for this face
eyeDot = (eyeVector[0] * thisface->surface_normal[0]) +
(eyeVector[1] * thisface->surface_normal[1]) +
(eyeVector[2] * thisface->surface_normal[2]);


i++;

} // end of jrb face traverse inside edge class

// test for an edge that has both a front facing and back facing polygon
if ( (eyeDot[0] 0) && (eyeDot[1] >0) )
{
glBegin(GL_LINES);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[0]*3]);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[1]*3]);
glEnd();
}
else
if ( (eyeDot[0] > 0) && (eyeDot[1] 0) )
{
glBegin(GL_LINES);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[0]*3]);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[1]*3]);
glEnd();
}
} // end of face_count conditional

// else, this edge doesn't connect exactly two faces, so it will be rendered
else
{
glBegin(GL_LINES);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[0]*3]);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[1]*3]);
glEnd();
}

}



glEnable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);

glPopMatrix();

} // end of master else (if there was a selected object conditional)










Now I could achieve the same effect with the following results:


Now the engine enjoyed a 43.9 framerate when object selected was active. With this optimization, the framerate dropped 45% due to object selection, thus mitigating the expense by 7%.

It is important to note that in this particular scene, the object itself is the entire scene. This is an upper bound that would never be realistically presented to the engine in a production state. More commonly, an object that is selectable will be a low-polygon object that is only a small part of the entire scene.

Having coded the edge detection algorithm for the mesh, I decided it might be interesting to bypass the stencil buffer completely. I achieved the following results:



I was actually pleased with these results. Only a %15 drop in performance is observed when bypassing the stencil and the results are visually interesting. I included this code in the engine so that either choice can be made to inform the user what objects are selected in the world - either the stenciled silhouette or the complete edge detection highlights. Here is another example of the edge detection highlights option:







Related resources:

Shreiner, David; Performance OpenGL: Platform Independent Techniques Siggraph 2001 Course #3; April 27, 2001

Hart, Evan; OpenGL Performance Tuning; ATI; Game Developers Conference 2005

West, Mick; Practical Hash IDs - Using 32-bit CRC hash as a unique identifier for game resources; Game Developer Magazine; December 2005


>

jdaniel

jdaniel

 

Performance Profiling and Optimization

I arrived at an appropriate development stage in my graphics engine that current bottlenecks are interesting to locate and certain optimizations may be appropriate. Here is an abbreviated account of what I did to explore these bottlenecks and search for possible optimizations.

The first thing I wanted to do was to identify any openGL errors that may have crept in my code unknowingly, since these errors can be a bottleneck in certain situations. I defined the following error checking macro and wrapped all the engine's openGL calls appropriately (i.e. CHECK_OPENGL_ERROR(glPopMatrix());)



#define CHECK_OPENGL_ERROR( cmd ) cmd;
{ GLenum error;
while ( (error = glGetError()) != GL_NO_ERROR)
{ printf( "[%s:%d], '%s' failed with error %s\n",
__FILE__, __LINE__, #cmd, gluErrorString(error) ); }
}

After running the engine with the gl calls wrapped, stdout posted the following:


[C:\src\engine\main.cpp:164], 'glPopMatrix()', failed with error stack underflow

The problem was that I was popping the matrix stack at the beginning of my render function and pushing at the end of the function. This was fine for everything but the first pass through the function, but the first call to glPopMatrix would pop an empty stack. Adding a glPushMatrix call to the setup routine solved this problem. No other errors were detected.

Satisfied that no openGL errors could be degrading my performance, I was ready to identify the bottlenecks in the rendering engine. I was prepared to identify five potential bottlenecks: a framebuffer limitation, a texture limitation, a transform limitation, a transfer limitation, and a CPU limitation.

For this particular exercise, I tested on a laptop. The laptop was a Dell Latitude D810 with a Pentium 2.13GHz and 1GB of RAM. The video card was an ATI mobility Radeon X600.

First, I rendered a simple mesh of Venus twice, without instancing, to a 1900x1200 framebuffer to get a baseline framerate.



At the 1900x1200 resolution, about 40,000 vertices and 87,000 faces were rendered at 37.2 frames per second. I dropped the framebuffer size to 1280x800 and rendered identical geometry.



The framerate did not change. This suggested that either a geometric bottleneck or a application CPU bottleneck was occurring at this level of scene complexity.

Before I determined whether this was CPU (application) limited or geometry (transfer) limited, I wanted to find out where fill limitation (framebuffer) threshold was crossed.

Instead of rendering Venus, I created a course helix in Maya to allow a higher degree of mesh granularity.



At 1920x1200, the engine rendered 48,000 vertices and 49,000 faces; the framerate was 78.4.




Rendering the same scene at 1280x800 revealed a framerate of 78.4. The same geometric-limitation or CPU-limitation existed at this level.


I removed a column of helix and rendered again.




Now a framerate discrepancy was revealed. Rendering 42,000 faces at 1920x1200 offered a framerate at 85.8; while rendering to a 1280x800 framebuffer offered a framerate at 90.2. The fill-limitation of this engine running on the laptop threshold was crossed at ~45K faces.

Now I needed to go back and determine if the original bottleneck was due to my data structures (CPU-limitation) or if the graphics hardware was pushing all the geometry that was possible at the given framerate.

To accomplish this, I needed to be able push the same quantity of data from the engine to the OpenGL pipeline. I used the C preprocessor to substitute glVertex calls with glNormal calls. I created the following header file:


#define glVertex2d(x, y) glNormal3d(x, y, 0)
#define glVertex2f(x, y) glNormal3f(x, y, 0)
#define glVertex2i(x, y) glNormal3i(x, y, 0)
#define glVertex2s(x, y) glNormal3s(x, y, 0)
#define glVertex3d(x, y, z) glNormal3d(x, y, z)
#define glVertex3f(x, y, z) glNormal3f(x, y, z)
#define glVertex3i(x, y, z) glNormal3i(x, y, z)
#define glVertex3s(x, y, z) glNormal3s(x, y, z)
#define glVertex4d(x, y, z, w) glNormal3d(x, y, z)
#define glVertex4f(x, y, z, w) glNormal3f(x, y, z)
#define glVertex4i(x, y, z, w) glNormal3i(x, y, z)
#define glVertex4s(x, y, z, w) glNormal3s(x, y, z)

#define glVertex2dv(v) glNormal3d(v[0], v[1])
#define glVertex2fv(v) glNormal3f(v[0], v[1])
#define glVertex2iv(v) glNormal3i(v[0], v[1])
#define glVertex2sv(v) glNormal3s(v[0], v[1])
#define glVertex3dv(v) glNormal3dv(v)
#define glVertex3fv(v) glNormal3fv(v)
#define glVertex3iv(v) glNormal3iv(v)
#define glVertex3sv(v) glNormal3sv(v)
#define glVertex4dv(v) glNormal3dv(v)
#define glVertex4fv(v) glNormal3fv(v)
#define glVertex4iv(v) glNormal3iv(v)
#define glVertex4sv(v) glNormal3sv(v)










I now included this header file and rendered the mesh of Venus, exactly as I had done in the previous step. This was useful because the same quantity of data was being pushed to the openGL pipeline but no geometry was being rendered.




When rendering to the 1900x1200 framebuffer a framerate of 147.7 was noted - a 400% increase in performance. Similarly, using the 1280x800 framebuffer, 333 frames per second was observed - a 425% increase in performance.

This confirmed that when rendering at a scene complexity of about 45,000 faces on my laptop, the engine moves from a fill-limitation to a geometry limitation; and a CPU limitation is never observed.

To test for texturing limitations, I rendered a Poser model with 128,000 texture coordinates using a 3000x3000 resolution .png skin texture. I then resampled the .png to 128x128 and rendered again. The framerate stayed consistent regardless of texture size, thus revealing no texture limitations.

I was able to observe that no transform limitation existed by not using any visibility culling. I then observed the framerate while not moving through the scene and observed the framerate while moving through the scene. The framerate stayed consistent, thus revealing that no transform limitation existed.



Another area of interest was the object selection feedback algorithms. There was room for appropriate optimization here. For a baseline reading, I rendered a hellskull mesh to a 1900x1200 framebuffer.



The baseline revealed that rendering 14,000 faces to 1920x1200 framebuffer allowed the engine to maintain a 79.8 framerate.



When placing the mouse cursor over the skull and selecting it, the following result was observed:


While rendering the correct selection feedback silhouette, the framerate dropped to 38.4 - a 52% drop in performance.

To achieve the silhouette effect, I was doing the following:
1. Setting the stencil buffer to create the stencil
2. Rendering the entire mesh into the stencil buffer
3. Setting the stencil buffer to use the stencil
4. Setting line width
4. Rendering the entire object in wireframe mode

This worked well visually. The line width stayed consistent regardless of camera placement. But there were two things here that could be changed that might improve performance.

First, there was no reason to render the entire image (texture and normal calls included) to create the stencil. So I changed the code to create a display list that was as simple as possible to get the stencil. This revealed another problem in regards to the use of the glStencilOp state function. This was corrected.

Second, there was no need to render the entire wireframe when only the silhouette was needed. So I decided to render only the front and back facing edges.

This was not trivial, as it turned out. First, I had to go back into my original OBJ parsing code to create the edge list. I also had to move my camera class into the world class so I could have convenient access to the eye vector. Once this was accomplished, I had to decide what was a front facing polygon and what was a back facing polygon independent of viewpoint. I implemented this algorithm as a world class method as follows:

>
// This is an function that will render a silhouette around a selected object (if any) in the scene.
void myWorld::renderSelectedOutline(void) {

JRB tempedges; JRB myedges;
obj_edge *thisedge;
JRB tempfaces;
obj_face *thisface;
float eyeVector[3];
float newPoints[3];
float normVector[3];
float eyeDot[2];
GLfloat *tmat;

int i;

// if there is not a currently selected object, this function will return
if (selectedWorldObject == NULL) {
return;
}
else
{

myedges = selectedWorldObject->localobj->getEdgesJRB();
tmat = selectedWorldObject->getMatrix();

glPushMatrix();

glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
//glDisable(GL_DEPTH_TEST);

glPushMatrix();

// prepare stencil for rendering
glClearStencil(0x0);
glClear(GL_STENCIL_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
glStencilFunc (GL_ALWAYS, 0x1, 0x1);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

glMultMatrixf(selectedWorldObject->transMatrix);
glCallList(selectedWorldObject->stencillist);

// set stencil
glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);


glPopMatrix();

glLineWidth(3);

glMultMatrixf(selectedWorldObject->transMatrix);

jrb_traverse(tempedges, myedges)
{
thisedge = (obj_edge *)tempedges->val.v;


// This optimization is only concerned with edges that connect exactly two faces
if (thisedge->face_count == 2)
{


// The reference point used to calculate the eye vector is multiplied against the transformation matrix of this object
newPoints[0] = (tmat[0] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)]) +
(tmat[4] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+1]) +
(tmat[8] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+2]) + (tmat[12]);
newPoints[1] = (tmat[1] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)]) +
(tmat[5] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+1]) +
(tmat[9] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+2]) + (tmat[13]);
newPoints[2] = (tmat[2] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)]) +
(tmat[6] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+1]) +
(tmat[10] * selectedWorldObject->localobj->obj_vertex_array[(thisedge->edges[0]*3)+2]) + (tmat[14]);


// The eye vector is created
eyeVector[0] = -mycamera->cameraPosition[0] - newPoints[0];
eyeVector[1] = -mycamera->cameraPosition[1] - newPoints[1];
eyeVector[2] = -mycamera->cameraPosition[2] - newPoints[2];

i = 0;
jrb_traverse(tempfaces, thisedge->face_list)
{
thisface = (obj_face *)tempfaces->val.v;

// The surface normal of the face is multiplied against the rotation matrix. translations do not affect the normal.
normVector[0] = (tmat[0] * thisface->surface_normal[0]) + (tmat[4] * thisface->surface_normal[1]) + (tmat[8] * thisface->surface_normal[2]);
normVector[1] = (tmat[1] * thisface->surface_normal[0]) + (tmat[5] * thisface->surface_normal[1]) + (tmat[9] * thisface->surface_normal[2]);
normVector[2] = (tmat[2] * thisface->surface_normal[0]) + (tmat[6] * thisface->surface_normal[1]) + (tmat[10] * thisface->surface_normal[2]);


//calculate the dot product for this face
eyeDot = (eyeVector[0] * thisface->surface_normal[0]) +
(eyeVector[1] * thisface->surface_normal[1]) +
(eyeVector[2] * thisface->surface_normal[2]);


i++;

} // end of jrb face traverse inside edge class

// test for an edge that has both a front facing and back facing polygon
if ( (eyeDot[0] 0) && (eyeDot[1] >0) )
{
glBegin(GL_LINES);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[0]*3]);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[1]*3]);
glEnd();
}
else
if ( (eyeDot[0] > 0) && (eyeDot[1] 0) )
{
glBegin(GL_LINES);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[0]*3]);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[1]*3]);
glEnd();
}
} // end of face_count conditional

// else, this edge doesn't connect exactly two faces, so it will be rendered
else
{
glBegin(GL_LINES);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[0]*3]);
glVertex3fv(&selectedWorldObject->localobj->obj_vertex_array[thisedge->edges[1]*3]);
glEnd();
}

}



glEnable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);

glPopMatrix();

} // end of master else (if there was a selected object conditional)











Now I could achieve the same effect with the following results:



Now the engine enjoyed a 43.9 framerate when object selected was active. With this optimization, the framerate dropped 45% due to object selection, thus mitigating the expense by 7%.

It is important to note that in this particular scene, the object itself is the entire scene. This is an upper bound that would never be realistically presented to the engine in a production state. More commonly, an object that is selectable will be a low-polygon object that is only a small part of the entire scene.




Having coded the edge detection algorithm for the mesh, I decided it might be interesting to bypass the stencil buffer completely. I achieved the following results:



I was actually pleased with these results. Only a %15 drop in performance is observed when bypassing the stencil and the results are visually interesting. I included this code in the engine so that either choice can be made to inform the user what objects are selected in the world - either the stenciled silhouette or the complete edge detection highlights.Here is another example of the edge detection highlights option:






Related resources:

Shreiner, David; Performance OpenGL: Platform Independent Techniques Siggraph 2001 Course #3; April 27, 2001

Hart, Evan; OpenGL Performance Tuning; ATI; Game Developers Conference 2005

West, Mick; Practical Hash IDs - Using 32-bit CRC hash as a unique identifier for game resources; Game Developer Magazine; December 2005



jdaniel

jdaniel

 

Blending Eyeballs and Eyelashes

I spent the second half of the day coding. The first half of the day I took my wife and my dog to Cumberland Mountain State Park for a great day of hiking.

One of my issues today was properly rendering OBJ exports out of Poser. After tracking down and repairing the non-null terminated string bug that was seg faulting my vertex array and some minor format tweaks, I was able to get the follow up and running:



This was an exciting step, in the fact that it showed that my engine could take almost whatever OBJ format you throw at it and everything showed up correct. That is, except for the cold staring alien eyes.

The issue was that Poser does not use alpha channels and the textures were simple uncompressed jpegs.

I opened up the jpegs in Photoshop and tried to manipulate the alpha channels myself and export into png. I didn't have any immediate luck doing this, however... so time to roll up my sleeves and wade through the code.

I finally found one problem. It was the choice of data structure I was using to traverse the OBJ in the rendering loop. Basically, I was doing a straight traverse of a red-black tree that was keyed on materials. This traversal is linear just as a double-linked list but the red-black tree proved to save some computation cycles during data structure construction. The issue here, however, was the order that the materials were listed in the .mtl file was not the order that they were being traversed in the rendering loop due to the tree rebalancing that took place on the tree on an insertions.

So, since the tree was "almost in order" and n will probably never be that large, I did a simple merge sort into a double-linked list at the end of my OBJ data construction. This allowed me to render all faces in the order that the materials appear in the .mtl file.

And here is what I was rendering:



Nice eyelashes, but the blank alien eyes were not working for me. At this point I was enabling blending and setting glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

I changed the textures in Photoshop to see if this revealed anything. I changed the material properties. Disabled lighting. Nothing revealing so far. I noticed that one of the materials in the mtl was named EyeTrans. Hmmm, could the textures actually be sitting inside the eyeball? I changed the blending factor to glBlendFunc(GL_SRC_ONE, GL_SRC_ALPHA); to take a look:



Ah, that's it! The eye textures are actually set back slightly into the eyeball. It was downhill from here. A simple material check in the rendering loop let me blend the destination and source fragments correctly.



I think the texture offsets into the material looks really nice. It appears a lot more like a human eyeball and not as much like a sphere texture mapped into a skull socket mesh.

As you can see from the screenshot, I am getting around 12 frames per second on my old 2 Ghz Pentium 4 with a 9800XT. I can still push the bottleneck around the graphics hardware with some optimizations, but for over 100k polygons with hi-res textures I am happy with the results for the moment.

I have some interesting ideas on how to implement rapid eye-movement in a vertex shader that I will flesh out in the near future; pun intended.

Goodnight.


jdaniel

jdaniel

 

Happy Birthday to me

Today is my birthday and I have now been on the planet for 32 years.

For my birthday I received some great stuff. For one, I got the Game Programming Gems 1 - 5 books. This is some serious reading material and it is going to take me a few months to plow through it all. Some of it is rather intensive also, so I may have to narrow my scope as to what I want to learn rather than reading them from cover to cover.

On the gaming front, I received DarkWatch for the XBox and Shadow of the Colossus for the PS2. Both of these are great games and I highly recommend either.

Another I book I received the other day was Accelerated C++. I have been programming in C daily for a few months now, and thought it might be a good idea for a quick refresher in STL. I saw this book was highly recommended on the gamedev forums.

I read the first chapter of Game Design this week - PreVisualization. It is not a programming book, but it gives a nice high level overview of the development pipeline.

On the development side, I finished porting the engine over to SDL and tested it. I am not that impresses with SDL actually and I will probably go with something else. I have found occasionally that I will get dropped to the desktop without anything posting to stdout or stderr. It's possible that it is something in my code that is triggering this, but I do not experience the same thing when using glut.

I am tempted to just program directly into the win32 API, but I do not want to abandon LINUX support for several reasons. I also have no desire to program one X11 and one Win32 version. I will test out GLFW tomorrow. Drew Benton was nice enough to package up a GLFW Visual C++ 6.0 zip file for me to make life easier.

I have object picking working in the code, but the performance of the silhouette rendering is not acceptable at them moment. Basically the problem is that I am rendering the entire wireframe into a stencil buffer of the object with a fixed line width. The results look really cool, but I need to cull the amount of lines I am drawing into the stencil. Perhaps a simple edge-front/back facing test would do the trick. This is on my to-do list. Here's a couple of screenshots from the rendering engine - nothing that interesting at the moment.





I also have a basic idea of what I want to do for this game project. I have to keep things modest and realistic, especially if I may be the only one working on this. I'll write more on this later, but I think the idea that I have come up with is modulated in a way that I can add things incrementally.

I did find an annoying bug in my rendering code tonight that I plan on repairing tomorrow if possible. I tried exporting a Poser model into an obj and rendering with my engine. At first it rendered, but some of the normals looked corrupted. After a recompile it crashed without rendering. I've tracked this down to a corrupt normal index value.

[edit] I stayed up late and tracked down this bug. It was a string that was not being NULL terminated, but was being converted to an integer. Goodnight. [/edit]

jdaniel

jdaniel

 

SDL, Land of the Dead, and Ultimate Game Design

I spent this week reading the SDL library documentation and porting my engine from glut to SDL. I thought seriously about porting the code directly to the WinAPI and using OpenAL for sound, but finally decided to use SDL for various reasons. The most important being the portability to LINUX.

I have a fairly competent rendering engine at the moment and some rudimentary level editing tools in place. Basically, the code has full support for OBJ files, including textures in png format and materials.

The rendering loop consists of a nested red-black tree traversal. The top level tree holds material objects. Each of these objects contains the material properties and a pointer to another tree of faces. So the final rendering loop looks something like this:





myfaces = localobj->getFacesJRB();

// construct a texture JRB and initialize the appropriate textures
jrb_traverse(tempmaterials, mymaterials)
{
thismaterial = (obj_material *)tempmaterials->val.v;
if(thismaterial->texture_name != NULL)
{
if ((jrb_find_str(newtextures, thismaterial->texture_name)) == NULL)
{
localtexture = (GLuint *)malloc(sizeof(GLuint));
*localtexture = pngBind(thismaterial->texture_name,
PNG_NOMIPMAP, PNG_ALPHA,
&localpnginfo,
GL_REPEAT, GL_LINEAR, GL_NEAREST);
if (*localtexture == 0)
{
printf("Can't load texture file: %s\n", thismaterial->texture_name);
exit(1);
}
newtex = new Texture(*localtexture, strdup(thismaterial->texture_name));
jrb_insert_str(newtextures, strdup(thismaterial->texture_name),
new_jval_v((void *)newtex));
}
numTextures++;
} // end of texture name null conditional
} // end of jrb materials traverse



if (shadeMethod == GL_SMOOTH) {
// compile the display list
glNewList(*mydisplaylist, GL_COMPILE);

// traverse the materials jrb
jrb_traverse(tempmaterials, mymaterials)
{
thismaterial = (obj_material *)tempmaterials->val.v;
myfaces = thismaterial->faces;

// set material properties
glLightfv(GL_LIGHT0, GL_AMBIENT, thismaterial->Ka);
glLightfv(GL_LIGHT0, GL_DIFFUSE, thismaterial->Kd);
glLightfv(GL_LIGHT0, GL_SPECULAR,thismaterial->Ks);
glMaterialf(GL_FRONT, GL_SHININESS, thismaterial->Ns);

if (thismaterial->texture_name != NULL)
{
temptextures = jrb_find_str(newtextures, thismaterial->texture_name);
newtex = (Texture *)temptextures->val.v;
glBindTexture(GL_TEXTURE_2D, newtex->mytexture);
}
else glBindTexture(GL_TEXTURE_2D, 0);

// traverse the face jrb
jrb_traverse(tempfaces, myfaces)
{
thisface = (obj_face *)tempfaces->val.v;

switch(thisface->face_format)
{

case VERTEX:
case VERTEX_NORMAL:

glBegin(renderMethod);
for( j =0; jpolygonSize; j++)
{
glNormal3fv(&thisface->face_normal_array[(thisface->normal[j]*3)]);
glVertex3fv(&localobj->obj_vertex_array[(thisface->vertex[j]*3)]);
}
glEnd();
break;

case VERTEX_TEXTURE:
case VERTEX_TEXTURE_NORMAL:

glBegin(renderMethod);
for( j =0; jpolygonSize; j++)
{
glTexCoord2fv(&localobj->obj_texture_array[(thisface->texture[j]*2)]);
glNormal3fv(&thisface->face_normal_array[(thisface->normal[j]*3)]);
glVertex3fv(&localobj->obj_vertex_array[(thisface->vertex[j]*3)]);
}
glEnd();
break;

default: printf("\nlogic error in createDisplayList\n"); exit(1);

} //end of swtich

} //end of face traverse

} // end of material JRB render traverse

glEndList();
}







I've abstracted the OBJs into a WorldObject class and I've abstracted the "level" into a MyWorld class. I also have basic openGL picking enabled, so the mouse cursor can be switched from "cursor on the screen picking mode" or classic first person shooter mode... very similar to how World of Warcraft is setup.

I'm satisfied with the controls. Everything is responsive and fluid. No third person camera views are available yet, only first person with mouse-look.

I've read up on collision detection and have thought about the next step in code development.

I also got a hold of Ultimate Game Design - Building Game Worlds by Tom Meigs. It has nothing in depth mathematically or anything about programming but it appears to be a clear guide through the game publishing process and a high level tools overview. I believe I will benefit from reading it.

I may be receiving some of the Game Programming Gems books as a birthday present soon (Nov. 6). I also might be getting Dark Watch for the XBox from my lovely wife.

I also bought a copy of Land of the Dead for the PC this weekend. This game is a huge amount of fun and I would recommend it to anyone that is looking for a solid budget title ($19.99). It has something different to offer me. The first scene on the farm was awesome. The zombies move slowly, but there are a lot of them. And a .22 rifle that holds 4 bullets and a 6 shot revolver takes time to reload. And ammo is rare. You can run indoors and close the doors to slow them down, but they will slowly punch through. It is great fun and I think anyone that is interested showed go out and support these guys and buy a copy. It is the closest I've seen yet to replicating the classic zombie movie nightmare.

jdaniel

jdaniel

 

Surface Latent Heat Flux in the year 2095

heatflux_2095.mpg

I've uploaded a 320x240 movie of a recent openGL application that I put together. The data shown is taken from a possible 2095 scenario that is a "worst case" possibility of atmospheric CO2 concentrations that continue to increase at an unabated rate of 1% per year. The latent heat flux field is a key field in the surface energy and moisture transfer between the ocean and the atmosphere. The warm water of the Gulf Stream provides the poleward transport of heat through ocean currents. The movie shows short time scale and small spatial features of the climate simulations by observing these instantaneous weather states.

This will be running on our R&D powerwall that we are bringing to SuperComputing 2005 this year at 6400x3200 resolution.

jdaniel

jdaniel

 

success nVidia press release

http://www.nvidia.com/object/ornl_success.html

I just noticed nVidia issued a press release that involved some of my work. Must have been a slow press cycle.. :) There is a nice picture of some of the IPCC climate runs that I rendered onto the powerwall however.

jdaniel

jdaniel

Sign in to follow this  
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!