{
In this lesson I'll teach you how to use three different texture filtering methods.
Teach you how to use the keyboard to move objects in the scene, and also teach you how to apply simple lighting in an OpenGL scene.
This lesson contains a lot of content. If you have questions about the previous lessons, go back and review them first.
Before getting into the code behind it, it's important to have a good understanding of the basics.
We still modify the code from the first lesson.
The difference from before is that whenever there is any big change, I will write out the entire code.
First we have to add the SysUtils unit and Glaux unit.
}
Uses
SysUtils,
opengl,
windows,
Messages,
Glaux In '../../GLAUX/Glaux.pas';
//The following lines add new variables.
//We add three Boolean variables.
// The light variable tracks whether the light is on.
//Variables lp and fp are used to store whether the 'L' and 'F' keys are pressed.
//I will explain the importance of these variables later. For now, put that aside.
light : Boolean; // light source on/off
lp : Boolean; // Is the L key pressed?
fp : Boolean; // Is the F key pressed?
//Now set 5 variables to control the step size of the rotation angle around the x-axis and y-axis,
//And the rotation speed around the x-axis and y-axis.
//In addition, a z variable is created to control the distance into the depth of the screen.
xrot : GLfloat; // X rotation
yrot : GLfloat; // Y rotation
xspeed : GLfloat; // X rotation speed
yspeed : GLfloat; // Y rotation speed
z : GLfloat = -5.0 f; // Distance deep into the screen
//Then set the array used to create the light source.
//We will use two different lights.
//The first one is called ambient light. Ambient light comes from all directions.
//All objects in the scene are illuminated by ambient light.
//The second type of light source is called diffuse light.
//Diffuse light is generated by a specific light source and creates reflections on the surfaces of objects in your scene.
//Any object surface directly illuminated by diffuse light becomes very bright,
//The areas that are barely illuminated appear darker.
//This will produce a very good shadow effect on the edges of the wooden crate we created.
//The process of creating a light source is exactly the same as creating a color.
//The first three parameters are the RGB three-color components, and the last one is the alpha channel parameter.
//Therefore, in the following code we get half-bright (0.5f) white ambient light.
//If there is no ambient light, areas not hit by diffuse light will become very dark.
LightAmbient: Array[0..3] Of GLfloat = (0.5, 0.5, 0.5, 1.0); //Ambient light parameters (new)
//The next line of code we generate the brightest diffuse light.
//All parameter values are taken to the maximum value of 1.0f.
//It will shine on the front of our wooden crate and look good.
LightDiffuse: Array[0..3] Of GLfloat = (1.0, 1.0, 1.0, 1.0); // Diffuse light parameters (new)
//Finally we save the position of the light source.
//The first three parameters are the same as in glTranslate.
//The displacements on the XYZ axis are respectively.
//Since we want the light to shine directly on the front of the wooden box, the displacements on the XY axis are both 0.0.
//The third value is the displacement on the Z axis.
//In order to ensure that the light is always in front of the wooden box,
//So we move the light source away from the screen towards the observer (which is you.).
//We usually call the position of the screen, that is, the screen glass of the monitor, the 0.0 point of the Z-axis.
//So the displacement on the Z axis is finally set to 2.0.
//If you can see the light source, it floats in front of your monitor.
//Of course, you can't see the box if it's not behind the monitor's screen glass.
//『Translator’s Note: I appreciate NeHe’s patience.
//To be honest, sometimes I get annoyed. Why does he talk so nonsense about such a simple thing?
//But if everything was clear, would you still flip through pages like this and read endlessly? 』
//The last parameter is taken as 1.0f.
//This will tell OpenGL that the coordinates specified here are the positions of the light source. I will explain more in future tutorials.
LightPosition: Array[0..3] Of GLfloat = (0.0, 0.0, 2.0, 1.0); // Light source position (new)
//The filter variable keeps track of the texture type used when displaying.
//The first texture (texture 0) is constructed using gl_nearest (non-smooth) filtering method.
//The second texture (texture 1) uses gl_linear (linear filtering) method,
//The image closer to the screen looks smoother.
//The third texture (texture 2) uses mipmapped filtering method,
//This will create a very nice looking texture.
//Depending on our usage type, the value of the filter variable is equal to 0, 1 or 2 respectively.
//Let's start with the first texture.
//texture allocates storage space for three different textures.
//They are located in texture[0], texture[1] and texture[2] respectively.
filter : GLuint; // Filter type
texture : Array[0..2] Of GLuint; // Storage space for 3 textures
PRocedure glGenTextures(n: GLsizei; Var textures: GLuint); stdcall; external
opengl32;
Procedure glBindTexture(target: GLenum; texture: GLuint); stdcall; external
opengl32;
Function gluBuild2DMipmaps(target: GLenum; components, width, height: GLint;
format, atype: GLenum; data: Pointer): Integer; stdcall; external glu32 name
'gluBuild2DMipmaps';
{
Now load a bitmap and use it to create three different textures.
This lesson uses the glaux auxiliary library to load bitmaps.
Therefore you should confirm whether the glaux library is included when compiling.
I know that both Delphi and VC++ include the glaux library, but other languages are not guaranteed to have it.
"Translator's Note: glaux is an OpenGL auxiliary library. According to the cross-platform characteristics of OpenGL,
Code should be common across all platforms. But the auxiliary library is not the official OpenGL standard library.
Not available on all platforms. But it happens to be available on the Win32 platform.
Haha, of course BCB is no problem either. 』Here I only annotate the newly added code.
If you have questions about a certain line of code, please check out Tutorial 6.
That lesson explains loading and creating textures in great detail.
After the previous piece of code and before glResizeWnd (),
We added the following code. This is almost identical to the code used to load the bitmap in Lesson 6.
}
Function LoadBmp(filename: pchar): PTAUX_RGBImageRec;
Var
BitmapFile : Thandle; // file handle
Begin
If Filename = '' Then // Ensure filename is provided.
result := Nil; // If not provided, return NULL
BitmapFile := FileOpen(Filename, fmOpenWrite); //Try to open the file
If BitmapFile > 0 Then // Does the file exist?
Begin
FileClose(BitmapFile); //Close handle
result := auxDIBImageLoadA(filename); //Load the bitmap and return the pointer
End
Else
result := Nil; // If loading fails, return NiL.
End;
Function LoadTexture: boolean; //Load the bitmap and convert it into a texture
Var
Status : boolean; // Status indicator
TextureImage : Array[0..1] Of PTAUX_RGBImageRec; // Create texture storage space
Begin
Status := false;
ZeroMemory(@TextureImage, sizeof(TextureImage)); // Set pointer to NULL
TextureImage[0] := LoadBMP('Walls.bmp');
If TextureImage[0] <> Nil Then
Begin
Status := TRUE; // Set Status to TRUE
glGenTextures(1, texture[0]); // Create texture
//In Lesson 6 we used linear filtered texture mapping.
//This requires a fairly high amount of processing power from the machine, but they look pretty good.
//In this lesson, the first texture we are going to create uses the GL_NEAREST method.
//In principle, this method does not actually perform filtering.
//It takes up very little processing power and looks poor.
//The only advantage is that our project can run normally on both fast and slow machines.
//You will notice that we use GL_NEAREST for both MIN and MAG,
//You can mix GL_NEAREST and GL_LINEAR.
//The texture will look better, but we care more about speed, so we use all low-quality maps.
//MIN_FILTER is used when the image is drawn smaller than the original size of the texture.
//MAG_FILTER is used when the image is drawn larger than the original size of the texture.
// Create Nearest filter map
glBindTexture(GL_TEXTURE_2D, texture[0]);
// Generate texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // (new)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // (new)
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0].sizeX,
TextureImage[0].sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
TextureImage[0].data);
//The next texture is the same as the one in Lesson 6, linear filtering. The only difference is that this time it is placed
//texture[1]. Because this is the second texture. If placed
//texture[0], it will overwrite the previously created GL_NEAREST texture.
glBindTexture(GL_TEXTURE_2D, texture[1]); //Use a typical texture generated from bitmap data
// Generate texture
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0].sizeX,
TextureImage[0].sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
TextureImage[0].data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Linear filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Linear filtering
//The following is a new way to create textures. Mipmapping!
//『Translator’s Note: I can’t translate this word in Chinese, but it doesn’t matter. After reading this paragraph, you will know that the meaning is most important. 』
//You may notice that when the image becomes very small on the screen, a lot of detail is lost.
//The pattern that looked good just now has become ugly. When you tell OpenGL to create a mipmapped texture,
//OpenGL will try to create high quality textures of different sizes. When you draw a mipmapped texture to the screen,
//OpenGL will choose the best looking texture (with more detail) it has created to draw on,
//Rather than just scaling the original image (which will result in loss of detail).
//I once said that there are ways to get around the limitations OpenGL places on texture width and height - 64, 128, 256, etc.
//The solution is gluBuild2DMipmaps. From what I've found, you can use arbitrary bitmaps to create textures.
//OpenGL will automatically scale it to normal size.
//Because it is the third texture, we save it to texture[2]. In this way, all three textures in this lesson have been created.
//Create MipMapped texture
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_NEAREST); // (new)
//The following line generates mipmapped texture.
//We use three colors (red, green, blue) to generate a 2D texture.
//TextureImage[0].sizeX is the bitmap width,
//TextureImage[0].sizeY is the bitmap height,
//(====For some reason, this function under Delphi does not have the height parameter,
//But there is it in the help. I don’t know what Delphi will do anymore, which makes me depressed...
//Finally, I wrote a gluBuild2DMipmaps myself earlier,
//To load the gluBuild2DMipmaps function in glu32.dll =====)
//GL_RGB means we use RGB colors in turn.
//GL_UNSIGNED_BYTE means that the unit of texture data is bytes.
//TextureImage[0].data points to the bitmap we use to create the texture.
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0].sizeX,
TextureImage[0].sizey, GL_RGB, GL_UNSIGNED_BYTE,
TextureImage[0].data); //(new) }
End;
If assigned(TextureImage[0]) Then // Whether the texture exists
If assigned(TextureImage[0].data) Then // Whether the texture image exists
TextureImage[0].data := Nil; // Release the memory occupied by the texture image
TextureImage[0] := Nil; // Release the image structure
result := Status; // Return Status
End;
//Then it's time to load the texture and initialize the OpenGL settings.
//The first line of the GLInit function uses the above code to load the texture.
//After creating the texture, we call glEnable(GL_TEXTURE_2D) to enable 2D texture mapping.
//The shadow mode is set to smooth shading (smooth shading).
//The background color is set to black, we enable depth testing, and then we enable optimized perspective calculations.
Procedure glInit(); // Start all settings for OpenGL here
Begin
If (Not LoadTexture) Then // Call the texture loading subroutine
exit; // If failed to load, exit
glEnable(GL_TEXTURE_2D); // Enable texture mapping
glShadeModel(GL_SMOOTH); // Enable shadow smoothing
glClearColor(0.0, 0.0, 0.0, 0.0); // black background
glClearDepth(1.0); //Set the depth buffer
glEnable(GL_DEPTH_TEST); // Enable depth testing
glDepthFunc(GL_LESS); // Type of depth test done
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //Highly optimized perspective projection calculation
//Now start setting up the light source. The next line below sets the amount of ambient light emitted,
//Light source light1 starts to emit light.
//At the beginning of this lesson, we store the amount of ambient light in the LightAmbient array.
//Now we will use this array (half-brightness ambient light).
glLightfv(GL_LIGHT1, GL_AMBIENT, @LightAmbient[0]); // Set the ambient light
//Next we set the amount of diffuse light. It is stored in the LightDiffuse array (full brightness white light).
glLightfv(GL_LIGHT1, GL_DIFFUSE, @LightDiffuse[0]); // Set diffuse light
//Then set the position of the light source.
//The position is stored in the LightPosition array
//(exactly located in the center of the front of the wooden box, X-0.0, Y-0.0, moved 2 units toward the observer in the Z direction <outside the screen>).
glLightfv(GL_LIGHT1, GL_POSITION, @LightPosition); // Light source position
//Finally, we enable light source number one. We haven't enabled GL_LIGHTING yet,
//So you can't see any light.
//Remember: just setting up, positioning, or even enabling the light source will not work.
//Unless we enable GL_LIGHTING.
glEnable(GL_LIGHT1); // Enable light source No. 1
End;
//The next piece of code draws the textured cube. I only annotate the new code.
//If you have questions about code without annotations, go back to Lesson 6.
Procedure glDraw(); // All drawing starts from here
Begin
glClear(GL_COLOR_BUFFER_BIT Or GL_DEPTH_BUFFER_BIT); // Clear screen and depth buffer
glLoadIdentity(); //Reset the current model observation matrix
//The next three lines of code place and rotate the texture cube.
//glTranslatef(0.0,0.0,z) moves the cube Z units along the Z axis.
//glRotatef(xrot,1.0f,0.0f,0.0f) rotates the cube around the X axis xrot.
//glRotatef(yrot,0.0f,1.0f,0.0f) rotates the cube yrot around the Y axis.
glTranslatef(0.0, 0.0, z); // Move in/out of the screen z units
glRotatef(xrot, 1.0, 0.0, 0.0); // Rotate around the X axis
glRotatef(yrot, 0.0, 1.0, 0.0); // Rotate around the Y axis
//The next line is similar to what we did in Lesson 6.
//The difference is that this time the texture we bind is texture[filter],
//Instead of texture[0] in the previous lesson.
//Anytime we press the F key, the value of filter will increase.
//If this value is greater than 2, the variable filter will be reset to 0.
//When the program is initialized, the value of the variable filter will also be set to 0.
//Using the variable filter we can select any of the three textures.
glBindTexture(GL_TEXTURE_2D, texture[filter]); //Select the texture determined by filter
glBegin(GL_QUADS); // Start drawing quadrilaterals
//glNormal3f is new to this lesson. Normal means normal.
//The so-called normal refers to a straight line that passes through a point on a surface (polygon) and is perpendicular to this surface (polygon).
//When using a light source, a normal must be specified. The normal tells OpenGL the orientation of the polygon and indicates the front and back sides of the polygon.
//If normals are not specified, strange things may happen: surfaces that should not be illuminated are illuminated, and the back side of the polygon is also illuminated....
//By the way, the normal should point to the outside of the polygon. Looking at the front of the box you will notice that the normal is in the same direction as the positive Z axis.
//This means the normal is pointing towards the observer - yourself. This is exactly what we hope for.
//For the back of the wooden box, just as we want, the normal is facing away from the viewer.
//If the cube is rotated 180 degrees along the X or Y axis, the normal on the front side is still facing the observer, and the normal on the back side is still facing away from the observer.
//In other words, no matter which surface it is, as long as it faces the observer, the normal of this surface points to the observer.
//Since the light source is immediately adjacent to the observer, any time the normal is facing the observer, this surface will be illuminated.
//And the closer the normal is toward the light source, the brighter it will appear.
//If you place the observation point inside the cube, you will get darkness inside the normal.
//Because the normal points outward. If there is no light source inside the cube, of course it will be pitch black.
// Front
glNormal3f(0.0, 0.0, 1.0); // normal points to the observer
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0, -1.0, 1.0); // Texture and lower left of the quad
glTexCoord2f(1.0, 0.0);
glVertex3f(1.0, -1.0, 1.0); // Texture and lower right of the quad
glTexCoord2f(1.0, 1.0);
glVertex3f(1.0, 1.0, 1.0); //Texture and upper right of the quad
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, 1.0); //Texture and top left of the quad
// later
glNormal3f(0.0, 0.0, -1.0); // Normal faces away from the viewer
glTexCoord2f(1.0, 0.0);
glVertex3f(-1.0, -1.0, -1.0); // Texture and lower right of the quad
glTexCoord2f(1.0, 1.0);
glVertex3f(-1.0, 1.0, -1.0); //Texture and upper right of the quad
glTexCoord2f(0.0, 1.0);
glVertex3f(1.0, 1.0, -1.0); //Texture and upper left of the quad
glTexCoord2f(0.0, 0.0);
glVertex3f(1.0, -1.0, -1.0); // Texture and lower left of the quad
// top surface
glNormal3f(0.0, 1.0, 0.0); // normal upward
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, -1.0); //Texture and top left of the quad
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0, 1.0, 1.0); // Texture and lower left of the quad
glTexCoord2f(1.0, 0.0);
glVertex3f(1.0, 1.0, 1.0); // Texture and lower right of the quad
glTexCoord2f(1.0, 1.0);
glVertex3f(1.0, 1.0, -1.0); //Texture and upper right of the quad
// Bottom
glNormal3f(0.0, -1.0, 0.0); // normal facing down
glTexCoord2f(1.0, 1.0);
glVertex3f(-1.0, -1.0, -1.0); // Texture and upper right of the quad
glTexCoord2f(0.0, 1.0);
glVertex3f(1.0, -1.0, -1.0); //Texture and top left of the quad
glTexCoord2f(0.0, 0.0);
glVertex3f(1.0, -1.0, 1.0); // Texture and lower left of the quad
glTexCoord2f(1.0, 0.0);
glVertex3f(-1.0, -1.0, 1.0); // Texture and lower right of the quad
// right
glNormal3f(1.0, 0.0, 0.0); // normal to the right
glTexCoord2f(1.0, 0.0);
glVertex3f(1.0, -1.0, -1.0); // Texture and lower right of the quad
glTexCoord2f(1.0, 1.0);
glVertex3f(1.0, 1.0, -1.0); //Texture and upper right of the quad
glTexCoord2f(0.0, 1.0);
glVertex3f(1.0, 1.0, 1.0); //Texture and upper left of the quad
glTexCoord2f(0.0, 0.0);
glVertex3f(1.0, -1.0, 1.0); // Texture and lower left of the quad
// left
glNormal3f(-1.0, 0.0, 0.0); // normal to the left
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0, -1.0, -1.0); // Texture and lower left of quad
glTexCoord2f(1.0, 0.0);
glVertex3f(-1.0, -1.0, 1.0); // Texture and lower right of the quad
glTexCoord2f(1.0, 1.0);
glVertex3f(-1.0, 1.0, 1.0); //Texture and upper right of the quad
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, -1.0); //Texture and top left of the quad
glEnd();
xrot := xrot + xspeed; // xrot increases xspeed units
yrot := Yrot + yspeed; // yrot increases yspeed unit
End;
//Now go to the WinMain() main function.
//We will add control code here to switch the light source on and off, rotate the wooden box, switch filtering methods, and move the wooden box closer and further away.
// Near the end of the WinMain() function you will see the SwapBuffers(hDC) line of code.
//Then add the following code after this line.
//The code will check whether the L key has been pressed.
//If the L key has been pressed, but the value of lp is not false, it means that the L key has not been released, and nothing will happen at this time.
SwapBuffers(h_DC); // Swap buffer (double buffer)
If (keys[ord('L')] And Not lp) Then
Begin
//If the value of lp is false,
//Meaning that the L key has not been pressed yet, or has been released, then lp will be set to TRUE.
//The reason for checking these two conditions at the same time is to prevent the L key from being pressed.
//This code is executed repeatedly and causes the form to flash continuously.
//After lp is set to true, the computer will know that the L key has been pressed.
//We can switch the light source on/off accordingly: the Boolean variable light controls the light source on/off.
lp := true; // lp is set to TRUE
light := Not light; // Switch the light source to TRUE/FALSE
If Not light Then // If there is no light source
glDisable(GL_LIGHTING) //Disable light source
Else // Others
glEnable(GL_LIGHTING); //Enable light source
End;
If Not keys[ord('L')] Then //Is the L key released?
lp := FALSE; // If yes, set lp to FALSE
//Then do a similar check for the "F" key.
//If the "F" key is pressed and the "F" key is not pressed or it has never been pressed,
//Set variable fp to true. This means that the key is being pressed.
//Then add one to the filter variable. If the filter variable is greater than 2
//(Because the array we use here is texture[3], textures larger than 2 do not exist),
//We reset the filter variable to 0.
If (keys[ord('F')] And Not fp) Then // Is the F key pressed?
Begin
fp := TRUE; // fp is set to TRUE
inc(filter); // Add one to the value of filter
If filter > 2 Then // Is it greater than 2?
filter := 0; // If reset to 0
End;
If Not keys[ord('F')] Then //Has the F key been released?
fp := FALSE; // If fp is set to FALSE
//These four lines check whether the PageUp key is pressed. If so, decrease the value of the z variable. This way the call to glTranslatef(0.0f,0.0f,z) included in the DrawGLScene function will move the wooden box further away from the viewer.
If keys[VK_PRIOR] Then //PageUp is pressed?
z := z - 0.02; // If pressed, move the wooden box towards the inside of the screen.
//The next four lines check whether the PageDown key is pressed. If so, increase the value of the z variable. In this way, the glTranslatef(0.0f,0.0f,z) call included in the DrawGLScene function will move the wooden box closer to the observer.
If keys[VK_NEXT] Then // Is PageDown pressed?
z := z + 0.02; //If pressed, move the wooden box towards the observer.
//Now check the arrow keys. Press the left and right direction keys to decrease or increase xspeed accordingly.
//Press the up and down direction keys to decrease or increase yspeed accordingly.
//Remember that in future tutorials if the values of xspeed and yspeed are increased, the cube will rotate faster.
//If you keep pressing a certain direction key, the cube will turn faster in that direction.
If keys[VK_UP] Then // Is the Up direction key pressed?
xspeed := xspeed - 0.01; //If yes, reduce xspeed
If keys[VK_DOWN] Then //Has the Down direction key been pressed?
xspeed := xspeed + 0.01; //If yes, increase xspeed
If keys[VK_RIGHT] Then //Is the Right direction key pressed?
yspeed := yspeed + 0.01; //If yes, increase yspeed
If keys[VK_LEFT] Then //Is the Left direction key pressed?
yspeed := yspeed - 0.01; //If yes, reduce yspeed
If (keys[VK_ESCAPE]) Then // If the ESC key is pressed
finished := True
Run it and see the effect