Basic OpenGL for the Newbie, Part 1

BlitzMax Forums/BlitzMax Tutorials/Basic OpenGL for the Newbie, Part 1

PaulJG(Posted 2005) [#1]
OpenGL Games Programming with Blitzmax

These tutorials where originally written for C++ programming as part of the GamecodingUK website many years ago - They have now been rewritten to include basic support with the Blitzmax language. Hopefully newcomers to OpenGL can find a use for them

What is OpenGL ?, Well.. you could say its an interface that connects directly to your graphics card - In english this means... a set of commands that can be used together to produce 2D and 3D programs.
OpenGL can bring the Games Programmer alot of good things, but I think one of the most important aspects has to be that you can use that nice shiny 3d card that is sat in your computer to give you silky smooth animation and state-of-the-art graphics.

So you want to program in OpenGL?

Well it's not as hard as you might think, OpenGL is quite a friendly beast and can help out with a lot of the complicated maths routines you need to use when coding a 3D game or app.
Thanks to Blitzmax, all the once complicated process of setting up a C++ compiler to use OpenGL is gone !. No more realms of w32 code needed - (believe me you could write 20-30 lines of code just to setup a blank screen !) - Blitzmax has inbuilt commands that can handle all this for us, life couldn't be easier !!.

But why bother yourself with OpenGL programming, when you can use a readymade 3d engine ??

Lots of reasons really, but here's a few off the top of my head..

You can get an understanding of the processes involved.

As you gradually build up your knowledge, you can start to introduce cutting edge features that can be found in today's games. (such as normal mapping a'la Halflife 2 or Doom 3)

Tons of tutorials and info can be found for free on the net !.

Its Fun !!!

Sooo... lets get going with a quick look through some of the basic features OpenGL has to offer !.


PaulJG(Posted 2005) [#2]
Setting up an OpenGL window

Type (or paste) the following code into the editor
bglCreateContext(800,600,32,0,BGL_BACKBUFFER|BGL_DEPTHBUFFER|BGL_FULLSCREEN)
init()

While Not KeyDown(KEY_ESCAPE)
display()
bglSwapBuffers
Wend 
End

Function init()
glClearColor(0.0, 0.0, 0.0, 0.0)
glColor3f(0.0, 0.0, 1.0)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45.0, 800/600, 0.1, 100.0)
End Function

Function display()
glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glBegin(GL_TRIANGLES)
	glVertex3f( 0.0, 1.0,-10.0)
	glVertex3f(-1.0,-1.0,-10.0)
	glVertex3f( 1.0,-1.0,-10.0)
glEnd()
End Function

Now compile and run it, hopefully you should see a small window containing a blue triangle. It may not do much at the moment but we have just made our first OpenGL program. You can close the window down by pressing the ESCAPE key.

Looking through the first few lines of code, you should be able to get a general idea of how we set everything up.

bglCreateContext(800,600,32,0,BGL_BACKBUFFER|BGL_DEPTHBUFFER|BGL_FULLSCREEN)

This is the all important line, it resets our screen to a resolution of 800 by 600 pixels - with a colour depth of 32 bits. Notice the 3 flag statements I've used 'BGL_BACKBUFFER, BGL DEPTHBUFFER and lastly BGL_FULLSCREEN - I'm sure you can guess what the last one does !. The first 2 inform our graphics card that we want to use a double buffered screen for a smoother display, the Depthbuffer is also initialised - this is an area of memory that is set aside by our graphics card for working out the all important 'distance' maths calculations - dont worry too much about what these commands do at this stage.

Have a play with the resolutions and colour depth variable to see the difference a higher res makes to the image displayed.
BUT you will also have to change this line of code...

gluPerspective(45.0, 800/600, 0.1, 100.0)

This line sets up the 3d display on the screen, making sure that program uses the correct perspective. Notice how it currently passes a value of 1.333 ( 800 / 600 ), This is the offset distance ratio between the with and height. Make sure you change this to be equal to the width and height of your required screen resolution. It is possible to distort the screen by playing with this value.. widescreen, fish eyes.. whatever !.


PaulJG(Posted 2005) [#3]
The Display

Lets go back through the previous listing and take a look at the display commands:

bglCreateContext(800,600,32,0,BGL_BACKBUFFER|BGL_DEPTHBUFFER|BGL_FULLSCREEN)

We already know that this is the all important screen setting command.

glClearColor(0.0, 0.0, 0.0, 0.0)

This sets the background colour to black ( RGB 0, 0, 0), values can range from 0.0 to 1.0. '0' being black and '1' being the brightest (white). As you've no doubt guessed, the values go in the format of RGB (red, green & blue) - playing around with the values will change the colour. The fourth 0.0 value is an alpha transparency colour which will not be used just yet.

glColor3f(0.0, 0.0, 1.0);

This sets the foreground colour, in this case the triangle to blue. You can use this command anywhere you want in the program, usually its before a side of an object (which means that each side or object would have a different colour, as we shall see later on).

glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
This line clears the buffers that we previously setup, we use this before we put any fresh data on the screen.
Think of it as flushing the page clean before we use it again !.

bglSwapBuffers

Lastly, this line swaps the display on our screen to that of our off-screen buffer.
(By using double buffers, we can draw on a hidden screen - while the previous screen is still showing. This cuts down on flickering, and can smooth out the overall program framerate.)

As you can see using Blitzmax with OpenGL, the whole process is quite painless.
Incase your wondering... if we where to have programmed the same little proggie in proper C++ with directx, it would be around 50 lines long !


PaulJG(Posted 2005) [#4]
The Screen Objects

Ok, there is still alot of the commands in the above program that I have yet to explain - but I think for now we will take a look at just what OpenGL is capable of. We will have to get back to the boring stuff later!

In the example I used (hopefully) you saw a triangle, these are the all important lines that defined the shape:

glBegin(GL_TRIANGLES);
glVertex3f( 0.0, 1.0, -10.0);
glVertex3f(-1.0,-1.0, -10.0);
glVertex3f( 1.0,-1.0, -10.0);
glEnd();

The glBegin(); command informs OpenGL that we are about to define a shape, similarly the glEnd(); command will close the shape. Every shape or object that we want to use must be defined within the 2 commands. You can have as many shapes as you wish, the only obstacles being speed and memory.

As you can see inside the glBegin command is the GL_TRIANGLES label - as you can guess, this is because I wanted to use a 3 sided shape.

Of course there are many different labels to use depending on the amount of points the object will have:

GL_POINTS
individual points
GL_LINES
pairs of vertices interpreted as individual line segments
GL_POLYGON
simple convex polygon
GL_TRIANGLES
triples of vertices interpreted as triangles
GL_QUADS
four sided polygons
GL_LINE_STRIP
series of connected line segments
GL_LINE_LOOP
same as above but connects last line to first

Following the glBegin is the 3 vertex co-ordinates that define the shape in 3d space, The '3' in the glVertex3f means that the shape will have 3 sets of co-ordinates (X,Y & Z). It is possible to use glVertex2f but that means we are restricted to 2 plane movement (X and Y - Left & Right, Up & Down), But of course its 3d that interests us.

As I've previously mentioned the coordinates for the command must be in the format of X, Y & Z. Whatever values you use, please remember that they are not pixel distances but units that GL uses. (So a 10 in the X axis, is not that same as 10 pixels across.)

In case you're wondering, here's the axis in relation to the co-ords:

As you can see any X co-ords that are to the left of 0 would be negative.. Any Y co-ords below 0 are also negative..

Any co-ords that go into the screen on the Z axis are negative..

Lets have a bit of fun, find the start of the triangle code (glBegin) and add the 3 lines of colour command code to it: (the glColor lines)
glBegin(GL_TRIANGLES)
glColor3f(0.0,1.0,0.0)'          // Add me
glVertex3f( 0.0, 1.0, -10.0)
glColor3f(0.0, 0.0, 1.0)'        // Add me
glVertex3f(-1.0,-1.0, -10.0)
glColor3f(1.0, 0.0, 0.0)'        // Add me
glVertex3f( 1.0,-1.0, -10.0)
glEnd();

Upon running the program you should see a multicoloured triangle, As I've previously mentioned the glColor3f command can be inserted anywhere. What is happening here is that we are informing GL that every point of the shape is to be a different colour, GL then tries to smooth the changes for us - making a very pretty job of it.

Let's try the same thing but this time with a square, just exchange the entire code in the glBegin section of code to:
glBegin(GL_QUADS);
glColor3f(1.0, 1.0, 0.0)
glVertex3f( 1.0, 1.0, -10.0)
glColor3f(0.0, 1.0, 1.0)
glVertex3f(-1.0, 1.0, -10.0)
glColor3f(1.0, 0.0, 1.0)
glVertex3f(-1.0,-1.0, -10.0)
glColor3f(0.0, 0.5, 1.0)
glVertex3f( 1.0,-1.0, -10.0)
glEnd()

As you can see all the Z co-ordinates are set to -10.0, this is so that we have a depth of 10 units into the screen, If we set these as '0' then we would not be able to see the shape as it would be closer to us then the depth of the window can take. (in other words anything positive past '-1' will not be shown)

Now that most of the boring foundation work is out of the way lets get stuck in and start to piece together a decent project. I have chosen to write a simple Tank Demo, the user will be able to crash about with a toy tank in a 3rd perspective mode. (you will see the tank on the screen infront of you)

Of course we are along way off from the finished demo and we have much to learn including movement and control. Unfortunately for now its back to the cubes, but believe me, better things are on the horizon.

Let's start off by looking at movement:


PaulJG(Posted 2005) [#5]
Rotation

In the last tutorial we used the example of a simple square displayed in 2d, this time I want to extend what we have learnt to include a solid 3d cube.

Enter the following code:
Global width:Int=800
Global height:Int=600
Global angle:Float=0

bglCreateContext(width,height,32,0,BGL_BACKBUFFER|BGL_DEPTHBUFFER|BGL_FULLSCREEN)
init()

While Not KeyDown(KEY_ESCAPE)
display()
idle_function()
bglSwapBuffers
Wend 
End

Function init()
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glEnable(GL_CULL_FACE)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45.0, Float(width)/Float(height), 0.1, 100.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
End Function

Function display()
glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
glTranslatef(0.0, 0.0, -5.0)
glRotatef(angle, 1.0, 0.0, 0.0)
glBegin(GL_QUADS)
glColor3f(1.0, 1.0, 0.0)
glVertex3f( -1.0, 1.0, -1.0)
glVertex3f(1.0, 1.0, -1.0)
glVertex3f(1.0, -1.0, -1.0)
glVertex3f(-1.0, -1.0, -1.0)
glEnd()
End Function

Function idle_function()
angle:+0.05
If angle>360.0 angle=0.0
End Function

Dont be alarmed just yet !!, its a one sided cube :)

With most 3d programming you would need to develop routines that hide the sides of the shape that cant be seen, (Hidden Line Removal) - but the ever friendly OpenGL will do all this for us. All we have to do is inform OGL that we wish it to take control of the task, we do this by using the glEnable(GL_CULL_FACE); command. This sets up a simple test that OGL performs on each side of the shape, namely - it looks for sides that are facing away from the screen (backfaces) and does not allow them to be displayed.


So how does it do that?
Well, it uses a special calculation based on the co-ordinates - I wont go into it here as its not needed, but if you are planning on advanced 3d programming - you will have to lookup something called 'Dot Product'.

Luckily we can stay well away from all the nasties, BUT there is a golden rule that MUST be obeyed:

All your points (co-ordinates) for a shape/side/object must run in an anti-clockwise order. If you follow the example shape you will see the first point of the square is top-left then bottom-left, followed by bottom-right and lastly top-right. We will look into other methods of hidden face removal, but this is the fastest method currently available. It will not crash the program if you make a mistake but may make the face of the shape appear the opposite way round. (That is it will only show when facing away from you.)

Did you have any problems with the example?

If it ran too fast for you, change the angle value (0.05) to a lower amount.

If it ran too slow then try lowering the value.

If you set it as Fullscreen but it looked distorted, that's because the values in the gluPerspective command need to match that of the screen resolution.

One thing to remember when designing shapes is to take into consideration the point of origin. In other words - the point 0,0,0 should be slap-bang inside the middle of the object/shape. This is so that when we want to rotate the shape we want it to pivot evenly. At the moment the example is spinning the square around the pivot point as we have yet to define the other sides.

Let's take a look at some of the other parts of the code that have been added:

Global width:Int=800
Global height:Int=600
Global angle:Float=0

Makes changes easier, using variables rather than hard coding the values. So from now on the width and screen height will be set from here.
The ANGLE variable is used to hold the angle of the shape.

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45.0, Float(width)/Float(height), 0.1, 100.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()

The dreaded MatrixMode commands, how I hate this section of code. I cant go into too much detail as I haven't really understood everything about it myself. Basically we have 2 modes to play around with, a world mode (GL_PROJECTION) and the object mode (GL_MODELVIEW). We are suppose to use the world mode to move around like a sort of camera, BUT it can really cause some very serious errors in our little program if it is not done right. Alot of OGL users prefer to use the object mode and move the objects around the camera, as it really makes life alot easier for the programmer and less prone to mistakes. It seems that this little trick is very widely used - so if its good enough for them, its good enough for us.

Let's just say the section of code works and leave it as that for now!

Function idle_function()
angle:+0.05
If angle>360.0 angle=0.0
End Function

The idle function is another new procedure that we have not needed before, the opengl window is updated with the refresh rate of the screen when it can keep up. Every refresh this procedure will be called into play. We can use it to control the general housekeeping of the game, in this case all it does is to add 0.05 degrees to the variable angle. If it goes over 360 then reset it as 0. If the program ran too fast or slow try changing the value slightly.

glLoadIdentity();
glTranslatef(0.0, 0.0, -5.0);
glRotatef(angle, 0.0, 1.0, 0.0);

The glLoadIdentity() command resets the internal values and angles from the last time the display was updated, glTranslatef(0.0, 0.0, -5.0) moves the camera away from the object alittle giving us a view of the spinning square. I shall go more into detail about it at a later time.

Finally the glRotatef(angle, 0.0, 1.0, 0.0) command, its taken along time for us to get to it, but at last I can explain how to rotate the shape.

The first perimeter that opengl expects in this command is the amount of degrees that you want the shape to rotate. In this case I have set the variable 'angle' to hold the info, as you have seen it is slowly increased until it hits 360 (a full circle). The remaining 3 perimeters set which plane of rotation you want to move, its only a case of putting a 1.0 value in the required position.

To move in the X plane - ( Horizontal ) - use glRotatef(angle,1.0 ,0.0, 0.0);

For the Y plane - ( Vertical ) - use glRotatef(angle, 0.0, 1.0, 0.0);

For the Z plane - ( Inwards/depth) - use glRotatef(angle, 0.0, 0.0, 1.0);

Of course you can set any combination, maybe even using all 3 planes of movement. To set different amounts you would just use 3 different glRotatef commands. Easy isn't it!

Let's put the remaining sides on the cube and add a different colour to each, then we will get it to rotate by different amounts in all 3 planes.

NOTE: Remember that we are designing the shape so that the sides are on the outside, think of how the cube will look. The back face (which is facing away from you) - will need to be designed so that the vertex's are in a clockwise order. (instead of anticlockwise like the front facing surface)
Global width:Int=800
Global height:Int=600
Global anglex:Float=0
Global angley:Float=0
Global anglez:Float=0

bglCreateContext(width,height,32,0,BGL_BACKBUFFER|BGL_DEPTHBUFFER|BGL_FULLSCREEN)
init()

While Not KeyDown(KEY_ESCAPE)
	display()
	idle_function()
	bglSwapBuffers
Wend
End

Function init()
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glEnable(GL_CULL_FACE)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45.0, Float(width)/Float(height), 0.1, 100.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
End Function

Function display()
glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glLoadIdentity()

glTranslatef(0,0,-5.0)
glRotatef(anglex,1.0,0.0,0.0)
glRotatef(angley,0.0,1.0,0.0)
glRotatef(anglez,0.0,0.0,1.0)

glBegin(GL_QUADS)

glColor3f(1.0,1.0,0.0) '// Front Face
glVertex3f(-1.0, -1.0, 1.0)
glVertex3f( 1.0, -1.0, 1.0)
glVertex3f( 1.0, 1.0, 1.0)
glVertex3f(-1.0, 1.0, 1.0)

glColor3f(1.0,0.0,0.4) ' // Back Face
glVertex3f(-1.0, -1.0, -1.0)
glVertex3f(-1.0, 1.0, -1.0)
glVertex3f( 1.0, 1.0, -1.0)
glVertex3f( 1.0, -1.0, -1.0)

glColor3f(1.0,0.2,1.0); '// Top Face
glVertex3f(-1.0, 1.0, -1.0)
glVertex3f(-1.0, 1.0, 1.0)
glVertex3f( 1.0, 1.0, 1.0)
glVertex3f( 1.0, 1.0, -1.0)
glColor3f(0.0,1.0,1.0) '// Bottom Face
glVertex3f(-1.0, -1.0, -1.0)
glVertex3f( 1.0, -1.0, -1.0)
glVertex3f( 1.0, -1.0, 1.0)
glVertex3f(-1.0, -1.0, 1.0)

glColor3f(1.0,0.5,1.0) '// Right face
glVertex3f( 1.0, -1.0, -1.0)
glVertex3f( 1.0, 1.0, -1.0)
glVertex3f( 1.0, 1.0, 1.0)
glVertex3f( 1.0, -1.0, 1.0)

glColor3f(0.5,1.0,1.0); '// Left Face
glVertex3f(-1.0, -1.0, -1.0)
glVertex3f(-1.0, -1.0, 1.0)
glVertex3f(-1.0, 1.0, 1.0)
glVertex3f(-1.0, 1.0, -1.0)
glEnd()
End Function

Function idle_function()
anglex:+0.01; If (anglex>360) anglex=0.0;
angley:+0.02; If (angley>360) angley=0.0;
anglez:+0.03; If (anglez>360) anglez=0.0;
End Function



PaulJG(Posted 2005) [#6]
Translation

So now we know how to rotate a shape how about next we try and move it around the screen.

Remember the translate command we used in the above example, let's take a look again:

glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();

glTranslatef(0,0,-5.0);

glRotatef(anglex,1.0,0.0,0.0);
glRotatef(angley,0.0,1.0,0.0);

It's even easier to use then the rotate command, just enter the values you want the screen or shape to move. In this case it is moving the shape away from the screen by adding -5.0 units to its position. Later on in the series we will study camera techniques, but for now let's stick with moving the shape around the camera.

The parameters are in the same order as the rotate command, that is: X, Y & Z

Remember that adding a negative number will move it further into the screen - if the shape moves more then 1 unit (positive), the face-culling with automatically cut in and the shape face will disappear.

Try adjusting the values to move the shape around.

If you would like to code a quick example then follow the below mini tutorial to produce a pulsating cube based on the code from the previous example.

Include the following 2 variables somewhere in the top of your program:

global moveabout:float=-5.0
global direction:float=0

In the IDLE function add the following:

if (direction==0) moveabout=moveabout-0.01;
if (moveabout<=-9.0) direction=1;
if (direction==1) moveabout=moveabout+0.01;
if (moveabout>=-4.0) direction=0;

Lastly find the TRANSLATE command in the DISPLAY function, and change it to:

glTranslatef(0,0,moveabout);

Voila !, a spinning, moving cube...

If the cube moves to quickly or slowly change the variables in the IDLE function. (0.01)

I think its time we put everything we have learnt into practice, how about we start to design a toy tank object. This will be made up of 3 parts: the main body, the turret and lastly the gun barrel - of course it will only be a basic shape, but in time we will learn to get the camera to follow it around and finally texture it.

I will slowly build up the model one stage at a time so that you can see the process take shape.

Let's start with the body:

You can insert the code below into the listing from the previous example, just remember to change the lines that I suggest or things may look a little weird.

Find: glTranslatef(0,0,-5.0);
Change it to: glTranslatef(0,0,-15.0);

We need to move the object further into the screen, away from the window - as the shape will be pretty big.

Find: glRotatef(anglez,0.0,0.0,1.0);
Change it to: 'glRotatef(anglez,0.0,0.0,1.0);

We won't need the rotation around the Z axis for now as this will cause the shape to spin too much in odd directions. (you could delete the entire line out if you want)

Now find the cube object that we defined (between the glBegin and glEnd) - delete all the necessary lines of code. insert the following instead of it:
' ** Tank Body

glBegin(GL_QUADS);

glColor3(0.3,0.8,0.2);        ' Back Face
glVertex3f(4.0, 1.0, -2.0)
glVertex3f(3.0, -1.0, -2.0)
glVertex3f(-3.0, -1.0, -2.0)
glVertex3f(-4.0, 1.0, -2.0)

glColor3f(0.3,0.8,0.2);        ' Front Face
glVertex3f(-4.0, 1.0, 2.0)
glVertex3f(-3.0, -1.0, 2.0)
glVertex3f(3.0, -1.0, 2.0)
glVertex3f(4.0, 1.0, 2.0)

glColor3f(0.3, 0.9,0.3);       ' Left Face
glVertex3f(-4.0, 1.0, -2.0)
glVertex3f(-3.0, -1.0, -2.0)
glVertex3f(-3.0, -1.0, 2.0)
glVertex3f(-4.0, 1.0, 2.0)

glColor3f(0.3, 0.9,0.3);       ' Right Face
glVertex3f(4.0, 1.0, 2.0)
glVertex3f(3.0, -1.0, 2.0)
glVertex3f(3.0, -1.0, -2.0)
glVertex3f(4.0, 1.0, -2.0)

glColor3f(0.3, 1.0,0.2);       ' Top Face
glVertex3f(-4.0, 1.0, -2.0)
glVertex3f(-4.0, 1.0, 2.0)
glVertex3f(4.0, 1.0, 2.0)
glVertex3f(4.0, 1.0, -2.0)

glColor3f(0.3, 1.0,0.2);       ' Bottom Face
glVertex3f(-3.0, -1.0, 2.0)
glVertex3f(-3.0, -1.0, -2.0)
glVertex3f(3.0, -1.0, -2.0)
glVertex3f(3.0, -1.0, 2.0)

glEnd()

Done that? Hopefully you saw the bottom section of the tank happily rotating about. If not the re-check your code for errors..

Time to define the next section, the turret.

This will be a separate object from the first part, so instead of editing the existing code we will just be ADDING it. After the end of the first shape (glEnd) - insert the following new code:
' Turret Section

glBegin(GL_QUADS);

glColor3f(0.3,0.8,0.2);         ' Back Face
glVertex3f(-4.0, 1.0, -2.0);
glVertex3f(-2.5, 2.0, -1.5);
glVertex3f(0.5, 2.0, -1.5);
glVertex3f(2.5, 1.0, -2.0);

glColor3f(0.3,0.8,0.2);         ' Front Face
glVertex3f(2.5, 1.0, 2.0);
glVertex3f(0.5, 2.0, 1.5);
glVertex3f(-2.5, 2.0, 1.5);
glVertex3f(-4.0, 1.0, 2.0);

glColor3f(0.3, 0.9,0.3);         ' Left Face
glVertex3f(-4.0, 1.0, 2.0);
glVertex3f(-2.5, 2.0, 1.5);
glVertex3f(-2.5, 2.0, -1.5);
glVertex3f(-4.0, 1.0, -2.0);

glColor3f(0.3, 0.9,0.3);         ' right Face
glVertex3f(0.5, 2.0, 1.5);
glVertex3f(2.5, 1.0, 2.0);
glVertex3f(2.5, 1.0, -2.0);
glVertex3f(0.5, 2.0, -1.5);

glColor3f(0.3, 1.0,0.1);         ' top Face
glVertex3f(0.5, 2.0, -1.5);
glVertex3f(-2.5, 2.0, -1.5);
glVertex3f(-2.5, 2.0, 1.5);
glVertex3f(0.5, 2.0, 1.5);
glEnd();

Now for the last section, the gun itself. As before it will be a separate object so the code below will need to be added after the previous code.
' Gun

glBegin(GL_QUADS);

glColor3f(0.3,0.8,0.2);         ' Back Face
glVertex3f(0.0, 1.2, -0.5)
glVertex3f(0.0, 1.7, -0.5)
glVertex3f(4.0, 1.7, -0.5)
glVertex3f(4.5, 1.2, -0.5)

glColor3f(0.3,0.8,0.2);         ' Front Face
glVertex3f(4.5, 1.2, 0.5)
glVertex3f(4.0, 1.7, 0.5)
glVertex3f(0.0, 1.7, 0.5)
glVertex3f(0.0, 1.2, 0.5)

glColor3f(0.3,0.7,0.3);         ' Top Face
glVertex3f(4.0, 1.7, 0.5)
glVertex3f(4.0, 1.7, -0.5)
glVertex3f(0.0, 1.7, -0.5)
glVertex3f(0.0, 1.7, 0.5)

glColor3f(0.3,0.7,0.2);         ' Bottom Face
glVertex3f(0.0, 1.2, 0.5)
glVertex3f(0.0, 1.2, -0.5)
glVertex3f(4.5, 1.2, -0.5)
glVertex3f(4.5, 1.2, 0.5)

glEnd();

We now have a complete tank !. But as you can see we have a small problem, the sides are being drawn correctly but they are not being drawn in any order. Parts of the tank is being drawn over sections that should be displayed last. The way to combat this is to use a procedure known as Depth Testing, but that's another story for another tutorial....


PaulJG(Posted 2005) [#7]
Depth Sorting

So the tank looked kind of strange didn't it? Never mind, let's sort it out...

OpenGL has the facilities to use Depth Sorting, something that has been unheard of on home PC computers until the arrival of fast processors with plenty of ram on graphic card. Think of it as a large grid that all the objects are placed on, the computer with then look at each horizontal line going through the whole table bit by bit. finally displaying all the objects it found starting from the back to the front. Once again its not hard to do, but can really take its toll on low processor computers.

Back to Depth Sorting. Hopefully it won't slow up things too much. ;)

Go through the INIT function until you find the lines:

glClearDepth(1.0)
glEnable(GL_CULL_FACE)

Insert the following 2 lines of code below the above instructions:

glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)

All these 2 lines of code do is to enable the depth test and set what sort of algorithm we should use.

Now find the DISPLAY function and change the 'glClear(GL_COLOR_BUFFER_BIT)' instruction to:

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

As you can tell this clears the depth buffer as well as the normal display buffer.

That's it! Try running the demo again - now you should see the tank in all its glory, spinning around its X and Y axis perfectly without any problems with the 3 separate shapes combined.


PaulJG(Posted 2005) [#8]
Linked Lists

Wouldn't it be nice to control each section of the tank as though they were separate sections and be able to have better control over the camera positioning?. Well, seeing as we have defined 3 objects that make up the tank - lets do just that.

Instead of going through the whole code listing line by line, I will only be explaining the new or changed lines of code - Most of this tutorial will deal with general coding rather than any OpenGL commands so feel free to change my code if you think you can program any faster routines.

Firstly we define a structure called 'CAM' - that will hold all the data to control the camera.

ROTX = Rotation angle for the X plane
ROTY = Rotation angle for the Y plane
ROTZ = Rotation angle for the Z plane

TRANX = Hold the Translation value for the X movement
TRANY = Hold the Translation value for the Y movement
TRANZ = Hold the Translation value for the Z movement
Type camera_data
    Field rotx:Float
    Field roty:Float
    Field rotz:Float
    Field tranx:Float
    Field trany:Float
    Field tranz:Float
End Type

Global cam:camera_data=New camera_data

Another structure this time called 'OBJECT', This time pointing to an array with enough space defined to hold the data for 5 shapes.

Notice that I've included some extra variables for scaling and offsetting the position of a shape. (We will use these in later tuts.)
Type object_data
  Field rotx:Float
  Field roty:Float
  Field rotz:Float
  Field tranx:Float
  Field trany:Float
  Field tranz:Float
  Field scalex:Float
  Field scaley:Float
  Field scalez:Float
  Field offsetx:Float
  Field offsety:Float
  Field offsetz:Float
End Type

Global obj:object_data[5]; For loop = 0 To 4
obj[loop] = New object_data; Next

Now that we have a structure for the camera, lets set up the camera to point to a good view of the object.

cam.tranz=-12
cam.rotx=20
cam.trany=-1

Over in the IDLE FUNCTION, I've added two lines of code:

These will increase the Y ROTATION values of both object 0 & 1, in other words the body and the turret. Seeing as we shall not be moving the gun itself separately from the other 2 parts of the shape, we do not need to change any of the rotation values for it. (Because it will take them from object 1 - the turret).

obj[0].roty+=0.01;
if obj[0].roty>360 obj[0].roty:-360.0;
obj[1].roty+=0.03;
if obj[1].roty>360 obj[1].roty:-360.0;

Now we come to the DISPLAY function, firstly I've added the code that will update our position within the world based on the cameras co-ordinates. This will move and rotate the entire world including ALL shapes and objects.

glTranslatef(cam.tranx,cam.trany,cam.tranz);
glRotatef(cam.rotx,1.0f,0.0f,0.0f);
glRotatef(cam.roty,0.0f,1.0f,0.0f);
glRotatef(cam.rotz,0.0f,0.0f,1.0f);

We now encase shape 0 (the body) in the two commands glPushMatrix() and glPopMatrix().

glPushMatrix() - this will save the current settings that were made when we moved/rotated the camera around the world to the new position. We do this so that we can then move certain shapes around without effecting the position of shapes drawn after it.

glPopMatrix() - restores the values we saved using the glPushMatrix command, overwriting any current values. if we didn't enclose it with these instructions all shapes that will be displayed will be rotated or translated by the previous shape. (Think of it as a knock-on effect)

Notice that I've only done it for the body section and not all 3 parts of the tank, this is because I don't want the gun to move independently to the turret. Maybe later on I will want to do this - but not for now. As the turret and the gun are the last 2 sections we are going to draw there is no need to store the world co-ordinates. (if there were more shapes to the world then YES, we would have to save the world co-ords with the Matrix commands)

After the glPushMatrix() instruction comes the commands that move and rotate the shape to its new position relative to the world. As you can see I'm adding an offset value to the translation, this is because later I may not wish to define a shape around the middle (the pivot). Using the offset I would be able to position the shape anywhere I wanted. (for example: The gun - later I could redesign it so that the pivot point is at the end rather than the middle and move it into place on the turret section using the offset values. )

// Body Section

glPushMatrix();
glTranslatef(obj[0].tranx+obj[0].offsetx, obj[0].trany+obj[0].offsety, obj[0].tranz+obj[0].offsetz)

glRotatef(obj[0].rotx, 1.0, 0, 0)
glRotatef(obj[0].roty, 0, 1.0, 0)
glRotatef(obj[0].rotz, 0, 0, 1.0)

glBegin(GL_QUADS);
glColor3f(0.3f,0.8f,0.2f); // Back Face
glVertex3f(4.0f, 1.0f, -2.0f);
......
glVertex3f(3.0f, -1.0f, 2.0f);

glEnd();
glPopMatrix();

// Turret Section

glPushMatrix()
glTranslatef(obj[1].tranx+obj[1].offsetx, obj[1].trany+obj[1].offsety, obj[1].tranz+obj[1].offsetz)

glRotatef(obj[1].rotx, 1.0, 0, 0)
glRotatef(obj[1].roty, 0, 1.0, 0)
glRotatef(obj[1].rotz, 0, 0, 1.0)

glColor3f(0.3,0.8,0.2); ' Back Face
......

Hopefully you have also noticed that the turret section is using the translate co-ordinates from shape 0 (the body) - this is because we want the the turret and gun to move with the body, so it's easier just to use the same co-ords.

You should by now be getting the general gist of it, just the gun section to go... this I leave up to you to complete ! ;)

So by now you should see a multi-jointed tank, rotating around the screen - Here's the complete listing incase you've had problems:



There's room for quite a few improvements with the code above, but its a certainly a good start for us.

(and before you mention it, YES.. there is a poly missing from the front of the gun turret - thats your job !) ;)


ftbass(Posted 2005) [#9]
Really intesting topic, very well explained for the beginner I am. (some syntaxic mistakes in functions calls : I had to remove the 'f')

Keep up the good work :)


AaronK(Posted 2005) [#10]
PaulJG, would you be interested in placing these tutorials up on BlitzWiki? www.blitzwiki.org - It'd be a great reference for everyone.

Cheers
Aaron


bradford6(Posted 2005) [#11]
Thanks PaulJG.

Here is what I threw together in about 10 minutes after reading your tutorial. (using your code as a template of course :) )

very nice tutorial. thanks




wedoe(Posted 2005) [#12]
I'm a newbie to GL, you made it easier Paul, thanks :)
(The nehe-stuff seemd just too much to handle
right now, but this is perfect....)


Wiebo(Posted 2005) [#13]
Me too, and this is just great. Keep it up!


PaulJG(Posted 2005) [#14]
@AaronK: Sure, I'd like to get them all up and running first though. ;)

@Everyone:

Thanks guys.. they where sitting here on my harddrive getting dusty - so at least someones getting some use out of em. :)

Theres 13 tuts in total, the last few go quite a way into building a proper 3d engine. I'll keep posting them up as/and when I've got some spare time on my hands.
(the blitz community has certainly helped me out enough times over the last few years !)

It seems the PC OpenGL needs the 'f's on the end of the commands. Some maybe its a compatibility thingy !?. I'll put a note at the top of the first tut.


Ole JR(Posted 2005) [#15]
@PaulJG: I think ftbass is talking about the function arguments in your last example..
Example:
glColor3f(1.0f,0.2f,1.0f); // Top Face
There's lots of them..

Isn't it so that the GL functions say something about how many arguments, and what value they take??
glColor3f() is 3 float parameters, that's where the 3f comes in..?
So I understand it like this:
The number is how many parameters, and the last char is the 'type' of the parameters, ie f = float b = byte and so on..
Or I'm I totaly lost here...?


PaulJG(Posted 2005) [#16]
I see what ya mean now.. the last snippet shouldnt work at all - just yet, its still in C++/Glut format.

I did post up a warning
***NOTE NOT FINISHED JUST YET !!!! - NOT BLITZMAX COMPATIBLE


Still.. if you guys are able to compile it with a bit of working around - save me a job ;)

@Ole JR: Yeap !, you got it.. there are other commands that take pointers to arrays, as you'll see ;)


tonyg(Posted 2005) [#17]
Hi PaulJG,
Thanks for the tutorials... very useful.
What people are trying to say is you have left the 'f' on some parms which cause problems in bmx.
Check 'Setting up an OpenGl window' where the
glVertex3f( 0.0, 1.0,-10.0) is OK (no 'f's)
but later, such as 'The Screen Objects', you suggest adding...
glColor3f(0.0f,1.0f,0.0f); // Add me
glVertex3f( 0.0f, 1.0f, -10.0f);
which causes problems.


PaulJG(Posted 2005) [#18]
whoops.. Looks like I havent changed the code descriptions over to blitzmax format (although the main full listings should be alright)- sorry guys, I'll fix it.

Thanks for pointing it out.


Akira(Posted 2005) [#19]
Excellent tutorials, just what a beginner like me needs.

BTW, you also need to change the C++ style comments as they cause problems in Blitz too.


jungle1971(Posted 2005) [#20]
WELL DONE PaulJG !!!

as a bmx/opengl rookie i want to tell you:
keep it up and please find the time to complete the
tutorials stuff.

i want to understand the nehe stuff in future.
this looks like as a good start for me.
i want to see the proper 3dengine :)

thank you very much for sharing with us
jGL


PaulJG(Posted 2005) [#21]
3 new tutorials posted - and hopefully all existing bugs squashed !.

Note that the last tut wont run without some blitmax converting..

6 more tuts to come !.

I have to admit, I started playing with the 3dengine source thats building up with the later tuts - and ended up writing a full blown proper engine !!. (but I will find the time to get these tuts finished) ;)


jungle1971(Posted 2005) [#22]
@PaulJG

> You can download the source code and precompiled exe here.

there is no download possible, please fix the link.
thanks in advance

jGL


PaulJG(Posted 2005) [#23]
Sorry jungle, forgot to take that bit out - its left over from the original webpages..

Unfortunately I wont be posting the source or exe's now my website is gone.


LeisureSuitLurie(Posted 2005) [#24]
If you need a host, email me.


PaulJG(Posted 2005) [#25]
Updated.. and added final tut for this part.

Part 2 coming soon...

Texture Mapping
Advanced Texturing
Advanced Camera Movement
The Workings of a 3d Engine


Afrohorse(Posted 2005) [#26]
Keep up the good work Paul - look forward to seeing the rest of the tutorials :)


MadMunky(Posted 2005) [#27]
Any Chance in making these guides into PDF's so its a little easyer to read and store :) grate tutorial anyways though


Clyde(Posted 2005) [#28]
What a marvelous tutorial indeed, a real top effort and excellent for us mere newbies.

Excellent Work PaulJG!
Any chance of some more!? :)


jungle1971(Posted 2005) [#29]
hello pauljg

still alive ? :)
jGL


PaulJG(Posted 2005) [#30]
Still alive - just in a different forum.. ;)

Work on my 3d engine is painfully slow, and holding me up - but at least I can load an X format object/level now.

Guess your after the remaining tuts ? - ok, I'll start putting them up in the next few days.

btw guys.. sorry, there will be NO PDF's, zips or downloads - I want to get them off my harddrive !. ;)


jungle1971(Posted 2005) [#31]
hello pauljg

how it is going ? are you so much busy :(
i dont want to notice that the ´OpenGL for Newbie, Part1´ tutorial is dead!
please pauljg, please take the time it tooks and finish the
story, thanks you!
cheers
jGL


CASO(Posted 2006) [#32]
Compile Error
Identifier 'bglCreateContext' not found

I'm running on version 1.20
What's the problem?


Yan(Posted 2006) [#33]
BlitzGL no longer exists!

From 'release.doc'...
* BlitzGL (ie: the 'bgl' commands) has GONE! Instead, use the following:

bglCreateContext -> GLGraphics
bglTexFromPixmap -> GLTexFromPixmap
bglAdjustTexSize -> GLAdjustTexSize
bglDrawText -> GLDrawText
bglSwapBuffers -> Flip



Will(Posted 2006) [#34]
but OpenGL in blitzmax still works. Just change bglcreateContext for GLGraphics(width, height, depth) and bglSwapBuffers for flip.