NEHE的教程第10课This tutorial was created by Lionel Brits (ßetelgeuse). This lesson only explains the sections of code that have been added. By adding just the lines below, the program will not run. If you're interested to know where each of the lines of code below go, download the source code, and follow through it, as you read the tutorial. Welcome to the infamous Tutorial 10. By now you have a spinning cube or a couple of stars, and you have the basic feel for 3D programming. But wait! Don't run off and start to code Quake IV just yet. Spinning cubes just aren't going to make cool deathmatch opponents :-) These days you need a large, complicated and dynamic 3D world with 6 degrees of freedom and fancy effects like mirrors, portals, warping and of course, high framerates. This tutorial explains the basic "structure" of a 3D world, and also how to move around in it. Data structure While it is perfectly alright to code a 3D environment as a long series of numbers, it becomes increasingly hard as the complexity of the environment goes up. For this reason, we must catagorize our data into a more workable fashion. At the top of our list is the sector. Each 3D world is basically a collection of sectors. A sector can be a room, a cube, or any enclosed volume. typedef struct tagSECTOR // Build Our Sector Structure { int numtriangles; // Number Of Triangles In Sector TRIANGLE* triangle; // Pointer To Array Of Triangles } SECTOR; // Call It SECTOR A sector holds a series of polygons, so the next catagory will be the triangle (we will stick to triangles for now, as they are alot easier to code.) typedef struct tagTRIANGLE // Build Our Triangle Structure { VERTEX vertex[3]; // Array Of Three Vertices } TRIANGLE; // Call It TRIANGLE The triangle is basically a polygon made up of vertices (plural of vertex), which brings us to our last catagory. The vertex holds the real data that OpenGL is interested in. We define each point on the triangle with it's position in 3D space (x, y, z) as well as it's texture coordinates (u, v). typedef struct tagVERTEX // Build Our Vertex Structure { float x, y, z; // 3D Coordinates float u, v; // Texture Coordinates } VERTEX; // Call It VERTEX Loading files Storing our world data inside our program makes our program quite static and boring. Loading worlds from disk, however, gives us much more flexibility as we can test different worlds without having to recompile our program. Another advantage is that the user can interchange worlds and modify them without having to know the in's and out's of our program. The type of data file we are going to be using will be text. This makes for easy editing, and less code. We will leave binary files for a later date. The question is, how do we get our data from our file. First, we create a new function called SetupWorld(). We define our file as filein, and we open it for read-only access. We must also close our file when we are done. Let us take a look at the code so far: Lesson 10 – Loading And Moving Through A 3D World Neon Helium Productions  Jeff Molofee NeHe 42 // Previous Declaration: char* worldfile = "data\\world.txt"; void SetupWorld() // Setup Our World { FILE *filein; // File To Work With filein = fopen(worldfile, "rt"); // Open Our File ... (read our data) ... fclose(filein); // Close Our File return; // Jump Back } Our next challenge is to read each individual line of text into a variable. This can be done in a number of ways. One problem is that not all lines in the file will contain meaningful information. Blank lines and comments shouldn't be read. Let us create a function called readstr(). This function will read one meaningful line of text into an initialised string. Here's the code: void readstr(FILE *f,char *string) // Read In A String { do // Start A Loop { fgets(string, 255, f); // Read One Line } while ((string[0] == '/') || (string[0] == '\n')); // See If It Is Worthy Of Processing return; // Jump Back } Next, we must read in the sector data. This lesson will deal with one sector only, but it is easy to implement a multi-sector engine. Let us turn back to SetupWorld().Our program must know how many triangles are in our sector. In our data file, we will define the number of triangles as follows: NUMPOLLIES n Here's the code to read the number of triangles: int numtriangles; // Number Of Triangles In Sector char oneline[255]; // String To Store Data In ... readstr(filein,oneline); // Get Single Line Of Data sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles); // Read In Number Of Triangles The rest of our world-loading process will use the same process. Next, we initialize our sector and read some data into it: // Previous Declaration: SECTOR sector1; char oneline[255]; // String To Store Data In int numtriangles; // Number Of Triangles In Sector float x, y, z, u, v; // 3D And Texture Coordinates ... sector1.triangle = new TRIANGLE[numtriangles]; // Allocate Memory For numtriangles And Set Pointer sector1.numtriangles = numtriangles; // Define The Number Of Triangles In Sector 1 // Step Through Each Triangle In Sector for (int triloop = 0; triloop < numtriangles; triloop++) // Loop Through All The Triangles { // Step Through Each Vertex In Triangle for (int vertloop = 0; vertloop < 3; vertloop++) // Loop Through All The Vertices { readstr(filein,oneline); // Read String To Work With // Read Data Into Respective Vertex Values sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v); // Store Values Into Respective Vertices sector1.triangle[triloop].vertex[vertloop].x = x; // Sector 1, Triangle triloop, Vertice vertloop, x Value=x sector1.triangle[triloop].vertex[vertloop].y = y; // Sector 1, Triangle triloop, Vertice vertloop, y Value=y sector1.triangle[triloop].vertex[vertloop].z = z; // Sector 1, Triangle triloop, Vertice vertloop, z Value=z sector1.triangle[triloop].vertex[vertloop].u = u; // Sector 1, Triangle triloop, Vertice vertloop, u Value=u sector1.triangle[triloop].vertex[vertloop].v = v; // Sector 1, Triangle triloop, Vertice vertloop, v Value=v } }
Each triangle in our data file is declared as follows: X1 Y1 Z1 U1 V1 X2 Y2 Z2 U2 V2 X3 Y3 Z3 U3 V3 Displaying Worlds Lesson 10 – Loading And Moving Through A 3D World Neon Helium Productions  Jeff Molofee NeHe 43 Now that we can load our sector into memory, we need to display it on screen. So far we have done some minor rotations and translations, but our camera was always centered at the origin (0,0,0). Any good 3D engine would have the user be able to walk around and explore the world, and so will ours. One way of doing this is to move the camera around and draw the 3D environment relative to the camera position. This is slow and hard to code. What we will do is this: 1. Rotate and translate the camera position according to user commands 2. Rotate the world around the origin in the opposite direction of the camera rotation (giving the illusion that the camera has been rotated) 3. Translate the world in the opposite manner that the camera has been translated (again, giving the illusion that the camera has moved) This is pretty simple to implement. Let's start with the first stage (Rotation and translation of the camera). if (keys[VK_RIGHT]) // Is The Right Arrow Being Pressed? { yrot -= 1.5f; // Rotate The Scene To The Left } if (keys[VK_LEFT]) // Is The Left Arrow Being Pressed? { yrot += 1.5f; // Rotate The Scene To The Right } if (keys[VK_UP]) // Is The Up Arrow Being Pressed? { xpos -= (float)sin(heading*piover180) * 0.05f; // Move On The X-Plane Based On Player Direction zpos -= (float)cos(heading*piover180) * 0.05f; // Move On The Z-Plane Based On Player Direction if (walkbiasangle >= 359.0f) // Is walkbiasangle>=359? { walkbiasangle = 0.0f; // Make walkbiasangle Equal 0 } else // Otherwise { walkbiasangle+= 10; // If walkbiasangle < 359 Increase It By 10 } walkbias = (float)sin(walkbiasangle * piover180)/20.0f; // Causes The Player To Bounce } if (keys[VK_DOWN]) // Is The Down Arrow Being Pressed? { xpos += (float)sin(heading*piover180) * 0.05f; // Move On The X-Plane Based On Player Direction zpos += (float)cos(heading*piover180) * 0.05f; // Move On The Z-Plane Based On Player Direction if (walkbiasangle <= 1.0f) // Is walkbiasangle<=1? { walkbiasangle = 359.0f; // Make walkbiasangle Equal 359 } else // Otherwise { walkbiasangle-= 10; // If walkbiasangle > 1 Decrease It By 10 } walkbias = (float)sin(walkbiasangle * piover180)/20.0f; // Causes The Player To Bounce } That was fairly simple. When either the left or right cursor key is pressed, the rotation variable yrot is incremented or decremented appropriatly. When the forward or backwards cursor key is pressed, a new location for the camera is calculated using the sine and cosine calculations (some trigonometry required :-). Piover180 is simply a conversion factor for converting between degrees and radians. Next you ask me: What is this walkbias? It's a word I invented :-) It's basically an offset that occurs when a person walks around (head bobbing up and down like a buoy. It simply adjusts the camera's Y position with a sine wave. I had to put this in, as simply moving forwards and backwards didn't look to great. Now that we have these variables down, we can proceed with steps two and three. This will be done in the display loop, as our program isn't complicated enough to merit a seperate function. int DrawGLScene(GLvoid) // Draw The OpenGL Scene { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer glLoadIdentity(); // Reset The Current Matrix GLfloat x_m, y_m, z_m, u_m, v_m; // Floating Point For Temp X, Y, Z, U And V Vertices GLfloat xtrans = -xpos; // Used For Player Translation On The X Axis GLfloat ztrans = -zpos; // Used For Player Translation On The Z Axis GLfloat ytrans = -walkbias-0.25f; // Used For Bouncing Motion Up And Down GLfloat sceneroty = 360.0f - yrot; // 360 Degree Angle For Player Direction int numtriangles; // Integer To Hold The Number Of Triangles glRotatef(lookupdown,1.0f,0,0); // Rotate Up And Down To Look Up And Down glRotatef(sceneroty,0,1.0f,0); // Rotate Depending On Direction Player Is Facing Lesson 10 – Loading And Moving Through A 3D World Neon Helium Productions  Jeff Molofee NeHe 44 glTranslatef(xtrans, ytrans, ztrans); // Translate The Scene Based On Player Position glBindTexture(GL_TEXTURE_2D, texture[filter]); // Select A Texture Based On filter numtriangles = sector1.numtriangles; // Get The Number Of Triangles In Sector 1 // Process Each Triangle for (int loop_m = 0; loop_m < numtriangles; loop_m++) // Loop Through All The Triangles { glBegin(GL_TRIANGLES); // Start Drawing Triangles glNormal3f( 0.0f, 0.0f, 1.0f); // Normal Pointing Forward x_m = sector1.triangle[loop_m].vertex[0].x; // X Vertex Of 1st Point y_m = sector1.triangle[loop_m].vertex[0].y; // Y Vertex Of 1st Point z_m = sector1.triangle[loop_m].vertex[0].z; // Z Vertex Of 1st Point u_m = sector1.triangle[loop_m].vertex[0].u; // U Texture Coord Of 1st Point v_m = sector1.triangle[loop_m].vertex[0].v; // V Texture Coord Of 1st Point glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // Set The TexCoord And Vertice x_m = sector1.triangle[loop_m].vertex[1].x; // X Vertex Of 2nd Point y_m = sector1.triangle[loop_m].vertex[1].y; // Y Vertex Of 2nd Point z_m = sector1.triangle[loop_m].vertex[1].z; // Z Vertex Of 2nd Point u_m = sector1.triangle[loop_m].vertex[1].u; // U Texture Coord Of 2nd Point v_m = sector1.triangle[loop_m].vertex[1].v; // V Texture Coord Of 2nd Point glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // Set The TexCoord And Vertice x_m = sector1.triangle[loop_m].vertex[2].x; // X Vertex Of 3rd Point y_m = sector1.triangle[loop_m].vertex[2].y; // Y Vertex Of 3rd Point z_m = sector1.triangle[loop_m].vertex[2].z; // Z Vertex Of 3rd Point u_m = sector1.triangle[loop_m].vertex[2].u; // U Texture Coord Of 3rd Point v_m = sector1.triangle[loop_m].vertex[2].v; // V Texture Coord Of 3rd Point glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // Set The TexCoord And Vertice glEnd(); // Done Drawing Triangles } return TRUE; // Jump Back } And voila! We have drawn our first frame. This isn't exactly Quake but hey, we aren't exactly Carmack's or Abrash's. While running the program, you may want to press F, B, PgUp and PgDown to see added effects. PgUp/Down simply tilts the camera up and down (the same process as panning from side to side.) The texture included is simply a mud texture with a bumpmap of my school ID picture; that is, if NeHe decided to keep it :-). So now you're probably thinking where to go next. Don't even consider using this code to make a full-blown 3D engine, since that's not what it's designed for. You'll probably want more than one sector in your game, especially if you're going to implement portals. You'll also want to have polygons with more than 3 vertices, again, essential for portal engines. My current implementation of this code allows for multiple sector loading and does backface culling (not drawing polygons that face away from the camera). I'll write a tutorial on that soon, but as it uses alot of math, I'm going to write a tutorial on matrices first. NeHe (05/01/00): I've added FULL comments to each of the lines listed in this tutorial. Hopefully things make more sense now. Only a few of the lines had comments after them, now they all do :) Please, if you have any problems with the code/tutorial (this is my first tutorial, so my explanations are a little vague), don't hesitate to email me mailto:[email protected] Until next time... Lionel Brits (ßetelgeuse) Jeff Molofee (NeHe)
adding just the lines below, the program will not run. If you're interested to know where each of the lines of code below go,
download the source code, and follow through it, as you read the tutorial.
Welcome to the infamous Tutorial 10. By now you have a spinning cube or a couple of stars, and you have the basic feel for 3D
programming. But wait! Don't run off and start to code Quake IV just yet. Spinning cubes just aren't going to make cool
deathmatch opponents :-) These days you need a large, complicated and dynamic 3D world with 6 degrees of freedom and fancy
effects like mirrors, portals, warping and of course, high framerates. This tutorial explains the basic "structure" of a 3D world, and
also how to move around in it.
Data structure
While it is perfectly alright to code a 3D environment as a long series of numbers, it becomes increasingly hard as the complexity
of the environment goes up. For this reason, we must catagorize our data into a more workable fashion. At the top of our list is the
sector. Each 3D world is basically a collection of sectors. A sector can be a room, a cube, or any enclosed volume.
typedef struct tagSECTOR // Build Our Sector Structure
{
int numtriangles; // Number Of Triangles In Sector
TRIANGLE* triangle; // Pointer To Array Of Triangles
} SECTOR; // Call It SECTOR
A sector holds a series of polygons, so the next catagory will be the triangle (we will stick to triangles for now, as they are alot
easier to code.)
typedef struct tagTRIANGLE // Build Our Triangle Structure
{
VERTEX vertex[3]; // Array Of Three Vertices
} TRIANGLE; // Call It TRIANGLE
The triangle is basically a polygon made up of vertices (plural of vertex), which brings us to our last catagory. The vertex holds the
real data that OpenGL is interested in. We define each point on the triangle with it's position in 3D space (x, y, z) as well as it's
texture coordinates (u, v).
typedef struct tagVERTEX // Build Our Vertex Structure
{
float x, y, z; // 3D Coordinates
float u, v; // Texture Coordinates
} VERTEX; // Call It VERTEX
Loading files
Storing our world data inside our program makes our program quite static and boring. Loading worlds from disk, however, gives us
much more flexibility as we can test different worlds without having to recompile our program. Another advantage is that the user
can interchange worlds and modify them without having to know the in's and out's of our program. The type of data file we are
going to be using will be text. This makes for easy editing, and less code. We will leave binary files for a later date.
The question is, how do we get our data from our file. First, we create a new function called SetupWorld(). We define our file as
filein, and we open it for read-only access. We must also close our file when we are done. Let us take a look at the code so far:
Lesson 10 – Loading And Moving Through A 3D World
Neon Helium Productions  Jeff Molofee NeHe
42
// Previous Declaration: char* worldfile = "data\\world.txt";
void SetupWorld() // Setup Our World
{
FILE *filein; // File To Work With
filein = fopen(worldfile, "rt"); // Open Our File
...
(read our data)
...
fclose(filein); // Close Our File
return; // Jump Back
}
Our next challenge is to read each individual line of text into a variable. This can be done in a number of ways. One problem is
that not all lines in the file will contain meaningful information. Blank lines and comments shouldn't be read. Let us create a
function called readstr(). This function will read one meaningful line of text into an initialised string. Here's the code:
void readstr(FILE *f,char *string) // Read In A String
{
do // Start A Loop
{
fgets(string, 255, f); // Read One Line
} while ((string[0] == '/') || (string[0] == '\n')); // See If It Is Worthy Of Processing
return; // Jump Back
}
Next, we must read in the sector data. This lesson will deal with one sector only, but it is easy to implement a multi-sector engine.
Let us turn back to SetupWorld().Our program must know how many triangles are in our sector. In our data file, we will define the
number of triangles as follows:
NUMPOLLIES n
Here's the code to read the number of triangles:
int numtriangles; // Number Of Triangles In Sector
char oneline[255]; // String To Store Data In
...
readstr(filein,oneline); // Get Single Line Of Data
sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles); // Read In Number Of Triangles
The rest of our world-loading process will use the same process. Next, we initialize our sector and read some data into it:
// Previous Declaration: SECTOR sector1;
char oneline[255]; // String To Store Data In
int numtriangles; // Number Of Triangles In Sector
float x, y, z, u, v; // 3D And Texture Coordinates
...
sector1.triangle = new TRIANGLE[numtriangles]; // Allocate Memory For numtriangles And Set
Pointer
sector1.numtriangles = numtriangles; // Define The Number Of Triangles In Sector 1
// Step Through Each Triangle In Sector
for (int triloop = 0; triloop < numtriangles; triloop++) // Loop Through All The Triangles
{
// Step Through Each Vertex In Triangle
for (int vertloop = 0; vertloop < 3; vertloop++) // Loop Through All The Vertices
{
readstr(filein,oneline); // Read String To Work With
// Read Data Into Respective Vertex Values
sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
// Store Values Into Respective Vertices
sector1.triangle[triloop].vertex[vertloop].x = x; // Sector 1, Triangle triloop, Vertice
vertloop, x Value=x
sector1.triangle[triloop].vertex[vertloop].y = y; // Sector 1, Triangle triloop, Vertice
vertloop, y Value=y
sector1.triangle[triloop].vertex[vertloop].z = z; // Sector 1, Triangle triloop, Vertice
vertloop, z Value=z
sector1.triangle[triloop].vertex[vertloop].u = u; // Sector 1, Triangle triloop, Vertice
vertloop, u Value=u
sector1.triangle[triloop].vertex[vertloop].v = v; // Sector 1, Triangle triloop, Vertice
vertloop, v Value=v
}
}
X1 Y1 Z1 U1 V1
X2 Y2 Z2 U2 V2
X3 Y3 Z3 U3 V3
Displaying Worlds
Lesson 10 – Loading And Moving Through A 3D World
Neon Helium Productions  Jeff Molofee NeHe
43
Now that we can load our sector into memory, we need to display it on screen. So far we have done some minor rotations and
translations, but our camera was always centered at the origin (0,0,0). Any good 3D engine would have the user be able to walk
around and explore the world, and so will ours. One way of doing this is to move the camera around and draw the 3D environment
relative to the camera position. This is slow and hard to code. What we will do is this:
1. Rotate and translate the camera position according to user commands
2. Rotate the world around the origin in the opposite direction of the camera rotation (giving the illusion that the camera
has been rotated)
3. Translate the world in the opposite manner that the camera has been translated (again, giving the illusion that the
camera has moved)
This is pretty simple to implement. Let's start with the first stage (Rotation and translation of the camera).
if (keys[VK_RIGHT]) // Is The Right Arrow Being Pressed?
{
yrot -= 1.5f; // Rotate The Scene To The Left
}
if (keys[VK_LEFT]) // Is The Left Arrow Being Pressed?
{
yrot += 1.5f; // Rotate The Scene To The Right
}
if (keys[VK_UP]) // Is The Up Arrow Being Pressed?
{
xpos -= (float)sin(heading*piover180) * 0.05f; // Move On The X-Plane Based On Player Direction
zpos -= (float)cos(heading*piover180) * 0.05f; // Move On The Z-Plane Based On Player Direction
if (walkbiasangle >= 359.0f) // Is walkbiasangle>=359?
{
walkbiasangle = 0.0f; // Make walkbiasangle Equal 0
}
else // Otherwise
{
walkbiasangle+= 10; // If walkbiasangle < 359 Increase It By 10
}
walkbias = (float)sin(walkbiasangle * piover180)/20.0f; // Causes The Player To Bounce
}
if (keys[VK_DOWN]) // Is The Down Arrow Being Pressed?
{
xpos += (float)sin(heading*piover180) * 0.05f; // Move On The X-Plane Based On Player Direction
zpos += (float)cos(heading*piover180) * 0.05f; // Move On The Z-Plane Based On Player Direction
if (walkbiasangle <= 1.0f) // Is walkbiasangle<=1?
{
walkbiasangle = 359.0f; // Make walkbiasangle Equal 359
}
else // Otherwise
{
walkbiasangle-= 10; // If walkbiasangle > 1 Decrease It By 10
}
walkbias = (float)sin(walkbiasangle * piover180)/20.0f; // Causes The Player To Bounce
}
That was fairly simple. When either the left or right cursor key is pressed, the rotation variable yrot is incremented or decremented
appropriatly. When the forward or backwards cursor key is pressed, a new location for the camera is calculated using the sine and
cosine calculations (some trigonometry required :-). Piover180 is simply a conversion factor for converting between degrees and
radians.
Next you ask me: What is this walkbias? It's a word I invented :-) It's basically an offset that occurs when a person walks around
(head bobbing up and down like a buoy. It simply adjusts the camera's Y position with a sine wave. I had to put this in, as simply
moving forwards and backwards didn't look to great.
Now that we have these variables down, we can proceed with steps two and three. This will be done in the display loop, as our
program isn't complicated enough to merit a seperate function.
int DrawGLScene(GLvoid) // Draw The OpenGL Scene
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer
glLoadIdentity(); // Reset The Current Matrix
GLfloat x_m, y_m, z_m, u_m, v_m; // Floating Point For Temp X, Y, Z, U And V Vertices
GLfloat xtrans = -xpos; // Used For Player Translation On The X Axis
GLfloat ztrans = -zpos; // Used For Player Translation On The Z Axis
GLfloat ytrans = -walkbias-0.25f; // Used For Bouncing Motion Up And Down
GLfloat sceneroty = 360.0f - yrot; // 360 Degree Angle For Player Direction
int numtriangles; // Integer To Hold The Number Of Triangles
glRotatef(lookupdown,1.0f,0,0); // Rotate Up And Down To Look Up And Down
glRotatef(sceneroty,0,1.0f,0); // Rotate Depending On Direction Player Is Facing
Lesson 10 – Loading And Moving Through A 3D World
Neon Helium Productions  Jeff Molofee NeHe
44
glTranslatef(xtrans, ytrans, ztrans); // Translate The Scene Based On Player Position
glBindTexture(GL_TEXTURE_2D, texture[filter]); // Select A Texture Based On filter
numtriangles = sector1.numtriangles; // Get The Number Of Triangles In Sector 1
// Process Each Triangle
for (int loop_m = 0; loop_m < numtriangles; loop_m++) // Loop Through All The Triangles
{
glBegin(GL_TRIANGLES); // Start Drawing Triangles
glNormal3f( 0.0f, 0.0f, 1.0f); // Normal Pointing Forward
x_m = sector1.triangle[loop_m].vertex[0].x; // X Vertex Of 1st Point
y_m = sector1.triangle[loop_m].vertex[0].y; // Y Vertex Of 1st Point
z_m = sector1.triangle[loop_m].vertex[0].z; // Z Vertex Of 1st Point
u_m = sector1.triangle[loop_m].vertex[0].u; // U Texture Coord Of 1st Point
v_m = sector1.triangle[loop_m].vertex[0].v; // V Texture Coord Of 1st Point
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // Set The TexCoord And Vertice
x_m = sector1.triangle[loop_m].vertex[1].x; // X Vertex Of 2nd Point
y_m = sector1.triangle[loop_m].vertex[1].y; // Y Vertex Of 2nd Point
z_m = sector1.triangle[loop_m].vertex[1].z; // Z Vertex Of 2nd Point
u_m = sector1.triangle[loop_m].vertex[1].u; // U Texture Coord Of 2nd Point
v_m = sector1.triangle[loop_m].vertex[1].v; // V Texture Coord Of 2nd Point
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // Set The TexCoord And Vertice
x_m = sector1.triangle[loop_m].vertex[2].x; // X Vertex Of 3rd Point
y_m = sector1.triangle[loop_m].vertex[2].y; // Y Vertex Of 3rd Point
z_m = sector1.triangle[loop_m].vertex[2].z; // Z Vertex Of 3rd Point
u_m = sector1.triangle[loop_m].vertex[2].u; // U Texture Coord Of 3rd Point
v_m = sector1.triangle[loop_m].vertex[2].v; // V Texture Coord Of 3rd Point
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // Set The TexCoord And Vertice
glEnd(); // Done Drawing Triangles
}
return TRUE; // Jump Back
}
And voila! We have drawn our first frame. This isn't exactly Quake but hey, we aren't exactly Carmack's or Abrash's. While running
the program, you may want to press F, B, PgUp and PgDown to see added effects. PgUp/Down simply tilts the camera up and
down (the same process as panning from side to side.) The texture included is simply a mud texture with a bumpmap of my school
ID picture; that is, if NeHe decided to keep it :-).
So now you're probably thinking where to go next. Don't even consider using this code to make a full-blown 3D engine, since
that's not what it's designed for. You'll probably want more than one sector in your game, especially if you're going to implement
portals. You'll also want to have polygons with more than 3 vertices, again, essential for portal engines. My current implementation
of this code allows for multiple sector loading and does backface culling (not drawing polygons that face away from the camera).
I'll write a tutorial on that soon, but as it uses alot of math, I'm going to write a tutorial on matrices first.
NeHe (05/01/00):
I've added FULL comments to each of the lines listed in this tutorial. Hopefully things make more sense now. Only a few of the
lines had comments after them, now they all do :)
Please, if you have any problems with the code/tutorial (this is my first tutorial, so my explanations are a little vague), don't
hesitate to email me mailto:[email protected] Until next time...
Lionel Brits (ßetelgeuse)
Jeff Molofee (NeHe)