Advertisment

Advanced Graphics in OpenGL

author-image
PCQ Bureau
New Update

In May this year, we started a series on game development using OpenGL. We talked about the basics (Basics of Game Development, page 74) and how to develop a simple game (Develop Games with OpenGL, page 76). This month, we'll get into advanced graphics and deal in two techniques: curves and surfaces, and texture mapping. 

Advertisment

Curves and surfaces



In mathematics, curves can be roughly classified into Hermetic curves, Beziers and Splines. These are defined by third-degree parametric equations, where the curve's shape is influenced by the position of control points. The other class of surfaces is quadrics, which is used to define objects such as spheres, cylinders and

paraboloids.

There's a lot of mathematical complexity in drawing curves in computer graphics, but OpenGL makes this a relatively simple task, and provides support for Beziers and Splines. Splines are implemented as NURBS (Non Uniform Rational B — Splines), and are more controllable than Beziers. In a NURBS surface, however, only the control points, that are local, influence a particular section of the curve. We'll look at NURBS in this article. 

Direct

Hit!
Applies

to:
Game developers
USP:

Simplifies mathematical complexities of game development
Advertisment

Drawing NURBS



Let's start the coding and draw an irregular surface with the usual lighting to get a feel of drawing surfaces using the functions provided by OpenGL in the header file . Open the source code and executables in the Developer's section in the OpenGL programming folder on this month's PCQ Essential CD. Then, open the file main3.C file and look for the following code. 

//Control Points array



GLfloat nurbs<4><4><3>={0.0f,0.0f,0.0f, //u=0,v=0


0.0f,5.0f,-5.0f, //v=1


.


.


10.0f,0.0f,0.0f, //u=1 v=0


.


.


30.0f,15.0f,0.0f};//u=3 v=3






One way to understand how a NURBS surface works is to compare it to a sheet of rubber. In the above code, the control points could be envisioned as places where the rubber sheet is pulled, and so it deforms accordingly. This analogy is also helpful in designing surfaces. Next, look for the following display function in the same file.

Advertisment

gluBeginSurface(nurb);



gluNurbsSurface(nurb,8,knots,8,knots,12,3,&nurbs<0><0><0>,4,4,GL_MAP2_VERTEX_3); 


gluEndSurface(nurb);

The constant GL_MAP2_VERTEX_3 tells OpenGL to generate 3D coordinates for points on the curve. Other options are also available and are a subject for further reading, as it is not possible to detail those here.

Drawing Quadrics



This is relatively simpler as OpenGL provides standard functions in the header file glu.h, which can be used to create a variety of shapes such as spheres, cylinders and cones. It also lets us control the

tessellation of the quadric. Tessellating a surface means breaking it into polygons, and this is done to improve the

application's speed as most graphics accelerators can handle triangle drawing in hardware. The function calls and the shapes they draw are

listed below.

Advertisment

Sphere — gluSphere (QuadricObj, radius, slices, stacks)



Cylinder — gluCylinder (QuadricObj, BaseRadius, TopRadius, Height, slices, stacks)


Disk — gluDisk (QuadricObj, InnerRadius, OuterRadius, Slices, Rings)


Partial disk- gluPartialDisk (QuadricObj, InnerRadius, OuterRadius, Slices, Rings, StartAngle, SweepAngle)


Drawing attributes — gluQuadricStyle (QuadricObj, GLU_FILL/GLU_LINE)


The attributes Slices and Stacks control the tessellation of the quadric, which affects both the speed and the detail of the displayed graphics. Hence a moderate value has to be chosen. 

As in NURBS surfaces, we have to create a quadric object and use it for drawing all the needed shapes, and delete it after its work is done. The code for drawing a cylinder with a sphere on one side and a disk on the other is as shown below. One factor we have to keep in mind is that all quadrics are drawn at the

origin and hence have to be translated out to their desired places.

Advertisment

GLUnurbsObj *nurb; //nurbs object



quad=gluNewQuadric();


.


.


glPushMatrix();


glTranslatef(-10.0f,0.0f,-20.0f);


gluSphere(quad,10,20,20); 


gluCylinder(quad,10,10,30,20,2);


glTranslatef(0.0f,0.0f,30.0f);


gluDisk(quad,2,10,20,20);


glPopMatrix();








Texture Mapping



This is a useful technique in lending visual realism to an otherwise dull scene. Texture mapping is the operation of taking a picture and wrapping it all around a 3D object, effectively skinning it. 

The process of texture mapping itself starts with the generation of a texture, which is defined in memory as an array of RGB and alpha values. OpenGL does not provide any tools to draw textures, so we have to use an image editor. The choice of image editors is a subject of debate but a few choices would be: the simple and not so efficient Paintbrush, the extremely powerful Adobe Photoshop and the free yet powerful Gimp program for Linux. The file format you choose for your textures has to be raw, that is not compressed, because we want to avoid the overhead of decompressing each time a texture has to be loaded into memory. We chose a relatively unknown but useful format called the .gbr, supported by GIMP. It's a raw format and has 4 bytes per pixel.

Advertisment

After creating the textures, we have to load them into memory. This part has nothing to do with OpenGL or computer graphics and is pure C coding. So, if you are implementing your game on a platform other than C/C++, you will have to recode this entire section. For this, check out the file texture.h, which seeks to the beginning of the image data in the file and copies it into main memory. 

Next we tell OpenGL that these bits and bytes of memory represent a texture and to convert it into an internal format that can be texture mapped. The texture is then indexed, as we have to have multiple textures in memory at any point of time in our game. We can also prioritize textures so that frequently used textures don't get swapped onto disk when memory goes low. Once textures are loaded into memory, it takes a few calls to enable texture mapping and to texture map all objects drawn following the call with that texture. 

In this context we come across another term called texture coordinates. A texture needs reference coordinates on the 3D surface so that it can be mapped in the way that we want it. These are usually represented as u and v coordinates, which are floating point values that range between 0.0 and 1.0. Only once we define texture coordinates, will the object be texture mapped. Each class of geometric figure has its own way of defining texture coordinates in OpenGL. The first of these is the polygon. To define texture coordinates for a polygon, we use the function call glTexCoord2f(u,v);. The code used to define this is as follows. The texture coordinate 0,0 corresponds to the top left corner of the texture. 

Advertisment

glBindTexture(GL_TEXTURE_2D,Texture<0>);



glBegin(GL_QUADS);


glTexCoord2f(0.0f,0.0f);


glVertex3f(0.0f,0.0f,0.0f);


glTexCoord2f(0.0f,1.0f);


glVertex3f(0.0f,10.0f,0.0f);


glTexCoord2f(1.0f,1.0f);


glVertex3f(10.0f,10.0f,0.0f);


glTexCoord2f(1.0f,0.0f);


glVertex3f(10.0f,0.0f,0.0f);


glEnd();








Next up are quadrics. Texture coordinates cannot be user defined as the coordinates are defined as a part of the quadric itself. All we have to do is to switch on texture mapping. This is accomplished with the following code which is added to the init() function.

gluQuadricTexture(quadric object, GL_TRUE);

Last up are NURBS. This is a little complex as compared to the other two, in the way that we have to define another surface of control points, which actually reflect texture coordinates. This concept is better explained with code.

gluBeginSurface(nurb); gluNurbsSurface(nurb,8,knots,6,wknots,9,3,&texwing2<0><0><0>,4,3,GL_MAP2_TEXTURE_COORD_2);



gluNurbsSurface(nurb,8,knots,6,wknots,9,3,&wing<0><0><0>,4,3,GL_MAP2_VERTEX_3);


gluEndSurface(nurb);

That's all there is to texture mapping, but with a little creativity, texture mapping can be used to our advantage. One example is a scrolling texture on an object. If we are to make a scrolling background in a game, we could define the texture coordinates with respect to a variable, possibly in a loop, which will give us the desired effect. You can use texture mapping to reduce geometry. This would be best illustrated if we had to draw a cylinder with jagged edges. The normal approach would be to actually draw the geometry, which proves very tedious. Instead, we could just define a texture onto the cylinder such that the cut-out parts have an alpha value zero, making them transparent. Visually this produces the same effect, and involves less computation, because texture mapping is also implemented in hardware on most accelerator boards.

Designing the Game



To design the craft used in the game we use one NURBS surface, one polygon, four quadrics and three textures. Detail can be left out considering the size of the craft. Also, we add a starscape in the background, which is a big rectangle with a texture on it. This part of the code is a design decision and has not much impact on the game other than visual appeal, which is in itself a major goal, but not one that we can sacrifice speed for. Also, we texture the actual path and the background to lend some visual realism to our game. The craft is drawn in the center of the screen above

the track, for now, until we get about altering its position using user input. Showing the actual code here is impossible due to the sheer number of lines, so we have shown only a section of it. The entire code is available in a ready to compile form in the main2-game.C file. 

void DrawRacer(float x,float y, float z)



{


static int temp;


temp=z;


.


.


.


gluNurbsSurface(nurb,8,knots,8,knots,12,3,&nurbt1<0><0><0>,4,4,GL_MAP2_TEXTURE_COORD_2);


gluNurbsSurface(nurb,8,knots,8,knots,12,3,&nurb1<0><0><0>,4,4,GL_MAP2_VERTEX_3); 


gluEndSurface(nurb);


glPopMatrix();


}





void Draw()


{


static int speed=2,x=0,z=-100;


int z1=z;


glClear(GL_COLOR_BUFFER_BIT);


glClear(GL_DEPTH_BUFFER_BIT);


//assume a track length of 1000 units


glPushMatrix();


DrawRacer(x,-1,z);


.


.


.


glutSwapBuffers();


z+=speed;


if(z==1000)


exit(0);





}




























To summarize, our game has the basic visual necessities in place. Further improvement is entirely up to the developer. If you wish to read further

about the mathematics behind curves and surfaces then we recommend books such as Computer Graphics by Hearn & Baker or Computer Graphics by Foley. They deal with curves on a very complex mathematical level and do not have any relevance to

OpenGL functions. 

Till now, the game is extremely silent and does not respond to the user, that is, no input is taken. In the next article we will deal with this. Once that is done, the only part of the game left will be the physics and soon we will have our very own basic 3D game.

Rakesh Iyer

Advertisment