OpenGL Tutorial #3
-- "Lighting & Texture mapping" --

paradox/vivid

Introduction

Welcome! This is OpenGL tutorial number 3! This tutorial will first of all tell you how to set up a lighting system, then texture mapping, and then lighted texture mapping.

Lighting Objects

This is almost too easy, first we must declare some variables:

GLfloat AmbientLight[] = { 0.5f, 0.5f, 0.5f, 1.0f };

That variable holds the amount of ambient light, the colours are declared in the usual way the last parameter - the alpha parameter - should be left alone for now, if you're not too much of a beginner it's actually a blending parameter. If you don't know what ambient light is, go code a 3d engine, then learn OpenGL.

Now we will declare diffuse light:

GLfloat DiffuseLight[] = { 0.5f, 0.5f, 0.5f, 1.0f };

Again, simple, it just sets OpenGL the proportions of how much light is reflected from each pixel component and the alpha parameter.

Now we better stick a light into the scene, we need to tell OpenGL where the light is, so we'll store the position in a variable:

GLfloat LightPosition[] { 0.0f, 0.0f, 0.0f, 1.0f };

Those arguments are, x, y, z position of the light, followed by - no, not an alpha parameter. :) It is the w parameter, from homogeneous coordinate systems, you can just leave it at 1.0f.

This goes in our initialisation function:

glLightfv(GL_LIGHT1, GL_AMBIENT, AmbientLight);  // tell it how much ambient
glLightfv(GL_LIGHT1, GL_DIFFUSE, DiffuseLight);  // tell it how much diffuse
glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);// tell it where the light is
glEnable(GL_LIGHT1); // enable this light.
glEnable(GL_LIGHTING); // enable LIGHTING

We store all the information in GL_LIGHT1, then enable that, but no lighting will take place until we enable GL_LIGHTING.

We're still missing something, face normals, you should know what these are, otherwise, I have to reiterate, go code a 3d engine first, then learn OpenGL.

When we're specifying our object's vertices, we say

glNormal3f(x, y, z);

specifying the 3 components of the normal. For example, for the left face of a cube the normal is

glNormal3f(-1.0f, 0.0f, 0.0f);

Simple.

Btw, if you don't want to bother setting up all those parameters for a light, you can just do this:

glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);

GL_LIGHT0 is a predefined, OpenGL light at the origin and coloured white, with preset ambient and diffuse values. You could also do multiple light sources with lights 0 through 7 by adjusting GL_LIGHTi (0 <= i <= 7).

Oh yeah whenever you see a function in OpenGL with a 'v' in it it stands for values, and it means one of the parameters is an array, e.g. glLightfv.

Notes On This Section (i)

(i) This is easy enough stuff, a glimpse at the source code will help you.
(ii) Investigate this for yourself a bit.
(iii) Now that you have specified normals you can turn on back face culling by: glEnable(GL_CULL_FACE);

Texture mapping objects (i)

So now it's time to texture map. Texture mapping under OpenGL is really, really, easy, like everything else.

First we need a "texture id" variable, here it is:

GLuint TextureID;

glGenTextures(1, &TextureID); // give it an "id"
glBindTexture(GL_TEXTURE_2D, TextureID);

Then we make TextureID a 2d texture (there are only 1d and 2d textures in OpenGL).

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

Those lines are to do with the type of filtering which is preformed when the texture is shrunk or stretched. GL_NEAREST, says basically do what a standard texture mapper does (we all know what that is :)
GL_TEXTURE_MAG_FILTER is what happens when the texture gets bigger, and
GL_TEXTURE_MIN_FILTER is what happens when the texture gets smaller.

Let's talk about texture filtering for a moment, there are other options, GL_LINEAR and GL_LINEAR_MIPMAP_NEAREST.

GL_NEAREST is no filtering, it looks crappy.
GL_LINEAR does bilinear filtering (I think).

GL_LINEAR_MIPMAP_NEAREST, is the next step up from GL_NEAREST, some of you have probably coded a texture mapper that supports mipmapping before. After you pass that as an argument to glTexParameteri you need to tell OpenGL to build the mipmaps, so you do this:

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 256, 256, GL_RGB, GL_UNSIGNED_BYTE,
		  Texture);

The arguments are: we're using a 2d texture, with three colour components, it's dimensions are 256x256, the colour components are GL_RGB (standard rgb) and the data is byte format (GL_UNSIGNED_BYTE), finally comes the actual texture data.

GL_LINEAR_MIPMAP_NEAREST takes the nearest sizes mipmap given the magnification of the texture.

Ok, we have to finish setting up our texture mapping after specifying the filtering. We do this:

glTexImage2D(GL_TEXTURE_2D, 0, 3, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE,
	     Texture);

I can never remember the arguments to this function, here they are for you, we're using a 2D Texture, its level of detail is 0 (usually left at this). 3 is the number of colour components (rgb), its dimensions, 256x256, 0 is the border state (again usually left at 0), GL_UNSIGNED_BYTE tells OpenGL the format of the data, and Texture gives it that data.

That's it! Almost.

Notes On This Section (ii)

(i) This sets up your texture mapping stuff, take some time to look through the code.

(ii) There are other texture filters, like GL_LINEAR_MIPMAP_LINEAR.

Texture mapping objects (ii)

Now to texture map the object!

Like in all texture mappers you have to specify texture coordinates, i.e. where to interpolate between in the texture when drawing the polygon, but you know that. When we specify the object we do this,

	   glTexCoord2f(u, v);
      glVertex3f(x, y, z); // the most recently specified texture coordinate
			   // corresponds to this vertex

To render a triangle which contains half of the texture we do this:

	     glBegin(GL_TRIANGLES);
	    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
	    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
	    glTexCoord2f(0.5f, 0.0f); glVertex3f(0.5f, -1.0f, 0.0f);
	glEnd();

There it is! Here's something pretty important, the bottom left of a texture is 0.0f, 0.0f, the top left is, 0.0f, 1.0f. So it's different to screen indexing.

It's pretty straightforward to do a cube, with a texture on it, you can look at the source code, but I'd recommend coding it yourself. :>

Now we have a rotating, shaded, texture mapped, 3d object. btw, if you want to go inside the cube in the example code it's easy, just move it forward a bit, all clipping is done for you :E

If you want cool constant shaded textures like we had with quads in tut 2 you can just do the same, specify colours at each vertex, however, you'll have to glEnable(GL_COLOR_MATERIAL)

Fog

Ok, a bonus to make your stuff look really cool. This is just something a dashed into the keyboard.

This is nice and easy. All we need to do is add to our function that initialises OpenGL. We do this:

glEnable(GL_FOG); // so we can actually render it.
glFogi(GL_FOG_MODE, GL_LINEAR);

Now we have set GL_LINEAR fog, The other types are GL_EXP, and GL_EXP2, GL_EXP being the worse of the two, it's really simple how it calculates the fog on a surface, just blending with various equations, you can email me for an explanation.

glFogfv(GL_FOG_COLOR, FogColour);

This sets the colour of the fog, FogColour could be the following for white fog (another "values" function):

GLfloat FogColour[] = { 1.0f, 1.0f, 1.0f, 1.0f };

Next comes the fog density:

glFogf(GL_FOG_DENSITY, 0.8f);

Sets the density of the fog, a number between 0 and 1 specifies it where 0 is least-dense and 1 is densist.

Now we set where the fog begins and ends on the z-axis

glFogf(GL_FOG_START, -1.0f);   // whatever
glFogf(GL_FOG_END, 5.0f);      // whatever

And that's it! You have fog!

Conclusion

So now you have a rotating, fog engulfed, filtered texture mapped, shaded, zbuffered 3d object! Something pretty good.

paradox / vivid.