#include <stdlib.h>
#include <windows.h> #include <stdio.h> #include <gl\gl.h> #include <gl\glu.h> #include <gl\glaux.h> HDC hDC=NULL; HGLRC hRC=NULL; HWND hWnd=NULL; HINSTANCE hInstance; bool keys[256]; bool active=TRUE; bool fullscreen=TRUE; GLfloat xrot; GLfloat yrot; GLfloat zrot; GLuint texture[1]; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
/* Now immediately after the above code, and before ReSizeGLScene(), we want to add the following section of code.
The job of this code is to load in a bitmap file. If the file doesn't exist NULL is sent back meaning the texture
couldn't be loaded. Before I start explaining the code there are a few VERY important things you need to know
about the images you plan to use as textures. The image height and width MUST be a power of 2. The width and
height must be at least 64 pixels, and for compatibility reasons, shouldn't be more than 256 pixels.
If the image you want to use is not 64, 128 or 256 pixels on the width or height, resize it in an art program.
There are ways around this limitation, but for now we'll just stick to standard texture sizes.
First thing we do is create a file handle. A handle is a value used to identify a resource so that our program
can access it. We set the handle to NULL to start off. */
AUX_RGBImageRec *LoadBMP(char *Filename) {
FILE *File=NULL
/* Next we check to make sure that a filename was actually given. The person may have used LoadBMP() without
specifying the file to load, so we have to check for this. We don't want to try loading nothing :)
*/ if (!Filename) {
return NULL; }
// If a filename was given, we check to see if the file exists. The line below tries to open the file.
File=fopen(Filename,"r"); // If we were able to open the file it obviously exists. We close the file with fclose(File) then we return the image
// data. auxDIBImageLoad(Filename) reads in the data.
if (File) {
fclose(File); return auxDIBImageLoad(Filename); }
// If we were unable to open the file we'll return NULL. which means the file couldn't be loaded.
// Later we'll check to see if the file was loaded and quit the program with an error message if it wasn't.
return NULL; }
// This is the section of code that loads the bitmap (calling the code above) and converts it into a texture.
int LoadGLTextures() {
// We'll set up a variable called Status. We'll use this variable to keep track of whether or not we were able to load
// the bitmap and build a texture. We set Status to FALSE (meaning nothing has been loaded or built) by default.
int Status=FALSE; Now we create an image record that we can store our bitmap in. The record will hold the bitmap width, height, and data. AUX_RGBImageRec *TextureImage[1]; // We clear the image record just to make sure it's empty.
memset(TextureImage,0,sizeof(void *)*1); // Now we load the bitmap and convert it to a texture. TextureImage[0]=LoadBMP("Data/NeHe.bmp") will jump to our
// LoadBMP() code. The file named NeHe.bmp in the Data directory will be loaded. If everything goes well, the image data
// is stored in TextureImage[0], Status is set to TRUE, and we start to build our texture. if (TextureImage[0]=LoadBMP("Data/NeHe.bmp"))
{
Status=TRUE; /* Now that we've loaded the image data into TextureImage[0], we will build a texture using this data. The first line
glGenTextures(1, &texture[0]) tells OpenGL we want to generate one texture name (increase the number if you load more
than one texture). Remember at the very beginning of this tutorial we created room for one texture with the line
GLuint texture[1]. Although you'd think the first texture would be stored at &texture[1] instead of &texture[0],
it's not. The first actual storage area is 0. If we wanted two textures we would use GLuint texture[2] and the second
texture would be stored at texture[1].
The second line glBindTexture(GL_TEXTURE_2D, texture[0]) tells OpenGL to bind the named texture texture[0] to a texture
target. 2D textures have both height (on the Y axes) and width (on the X axes). The main function of glBindTexture is
to assign a texture name to texture data. In this case we're telling OpenGL there is memory available at &texture[0].
When we create the texture, it will be stored in the memory that &texture[0] references. */ glGenTextures(1, &texture[0]); glBindTexture(GL_TEXTURE_2D, texture[0]);
/* Next we create the actual texture. The following line tells OpenGL the texture will be a 2D texture (GL_TEXTURE_2D).
Zero represents the images level of detail, this is usually left at zero. Three is the number of data components.
Because the image is made up of red data, green data and blue data, there are three components.
TextureImage[0]->sizeX is the width of the texture. If you know the width, you can put it here, but it's easier to let
the computer figure it out for you. TextureImage[0]->sizey is the height of the texture. zero is the border.
It's usually left at zero. GL_RGB tells OpenGL the image data we are using is made up of red, green and blue data in
that order. GL_UNSIGNED_BYTE means the data that makes up the image is made up of unsigned bytes,
and finally... TextureImage[0]->data tells OpenGL where to get the texture data from. In this case it points to the
data stored in the TextureImage[0] record. */
glTexImage2D(GL_TEXTURE_2D, 0, 3,
TextureImage[0]->sizeX,
TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
/* The next two lines tell OpenGL what type of filtering to use when the image is larger (GL_TEXTURE_MAG_FILTER) or
stretched on the screen than the original texture, or when it's smaller (GL_TEXTURE_MIN_FILTER) on the screen than the
actual texture. I usually use GL_LINEAR for both. This makes the texture look smooth way in the distance, and when it's
up close to the screen. Using GL_LINEAR requires alot of work from the processor/video card, so if your system is slow,
you might want to use GL_NEAREST. A texture that's filtered with GL_NEAREST will appear blocky when it's stretched.
You can also try a combination of both. Make it filter things up close, but not things in the distance. */
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}
/* Now we free up any ram that we may have used to store the bitmap data. We check to see if the bitmap data was stored
in TextureImage[0]. If it was we check to see if the data has been stored. If data was stored, we erase it. Then we
free the image structure making sure any used memory is freed up. */
if (TextureImage[0]) { if (TextureImage[0]->data) {
free(TextureImage[0]->data); }
free(TextureImage[0]); }
return Status; }
// Finally we return the status. If everything went OK, the variable Status will be TRUE. If anything went wrong,
// Status will be FALSE.
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) {
if (height==0) {
height=1; }
glViewport(0,0,width,height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW); glLoadIdentity(); }
/* I've added a few lines of code to InitGL. I'll repost the entire section of code, so it's easy to see the lines that
I've added, and where they go in the code. The first line if (!LoadGLTextures()) jumps to the routine above which loads
the bitmap and makes a texture from it. If LoadGLTextures() fails for any reason, the next line of code will return FALSE.
If everything went OK, and the texture was created, we enable 2D texture mapping. If you forget to enable texture mapping
your object will usually appear solid white, which is definitely not good. */
int InitGL(GLvoid) {
if (!LoadGLTextures()) {
return FALSE; }
glEnable(GL_TEXTURE_2D); glShadeModel(GL_SMOOTH); glClearColor(0.0f, 0.0f, 0.0f, 0.5f); glClearDepth(1.0f); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); return TRUE; }
/* Now we draw the textured cube. You can replace the DrawGLScene code with the code below, or you can add the new code to
the original lesson one code. This section will be heavily commented so it's easy to understand. The first two lines of
code glClear() and glLoadIdentity() are in the original lesson one code. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
will clear the screen to the color we selected in InitGL(). In this case, the screen will be cleared to black. The depth
buffer will also be cleared. The view will then be reset with glLoadIdentity(). */
int DrawGLScene(GLvoid) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(0.0f,0.0f,-5.0f);
/* The following three lines of code will rotate the cube on the x axis, then the y axis, and finally the z axis.
How much it rotates on each axis will depend on the value stored in xrot, yrot and zrot. */
glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);
glRotatef(zrot,0.0f,0.0f,1.0f);
/* The next line of code selects which texture we want to use. If there was more than one texture you wanted to use
in your scene, you would select the texture using glBindTexture(GL_TEXTURE_2D, texture[number of texture to use]).
If you wanted to change textures, you would bind to the new texture. One thing to note is that you can NOT bind a
texture inside glBegin() and glEnd(), you have to do it before or after glBegin(). Notice how we use glBindTextures
to specify which texture to create and to select a specific texture. */
glBindTexture(GL_TEXTURE_2D, texture[0]);
/* To properly map a texture onto a quad, you have to make sure the top right of the texture is mapped to the top right
of the quad. The top left of the texture is mapped to the top left of the quad, the bottom right of the texture is mapped
to the bottom right of the quad, and finally, the bottom left of the texture is mapped to the bottom left of the quad.
If the corners of the texture do not match the same corners of the quad, the image may appear upside down, sideways,
or not at all.
The first value of glTexCoord2f is the X coordinate. 0.0f is the left side of the texture. 0.5f is the middle of the
texture, and 1.0f is the right side of the texture. The second value of glTexCoord2f is the Y coordinate. 0.0f is the
bottom of the texture. 0.5f is the middle of the texture, and 1.0f is the top of the texture.
So now we know the top left coordinate of a texture is 0.0f on X and 1.0f on Y, and the top left vertex of a quad is
-1.0f on X, and 1.0f on Y. Now all you have to do is match the other three texture coordinates up with the remaining
three corners of the quad.
Try playing around with the x and y values of glTexCoord2f. Changing 1.0f to 0.5f will only draw the left half of a
texture from 0.0f (left) to 0.5f (middle of the texture). Changing 0.0f to 0.5f will only draw the right half of a
texture from 0.5f (middle) to 1.0f (right). */
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd();
/* Now we increase the value of xrot, yrot and zrot. Try changing the number each variable increases by to make the
cube spin faster or slower, or try changing a + to a - to make the cube spin the other direction. */
xrot+=0.3f;
yrot+=0.2f;
zrot+=0.4f;
return TRUE; }
GLvoid KillGLWindow(GLvoid) {
if (fullscreen) {
ChangeDisplaySettings(NULL,0); ShowCursor(TRUE); }
if (hRC) {
if (!wglMakeCurrent(NULL,NULL)) {
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
if (!wglDeleteContext(hRC)) {
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; }
if (hDC && !ReleaseDC(hWnd,hDC)) {
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL; }
if (hWnd && !DestroyWindow(hWnd)) {
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; }
if (!UnregisterClass("OpenGL",hInstance)) {
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; }
}
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
GLuint PixelFormat; WNDCLASS wc; DWORD dwExStyle; DWORD dwStyle; RECT WindowRect; WindowRect.left=(long)0; WindowRect.right=(long)width; WindowRect.top=(long)0; WindowRect.bottom=(long)height; fullscreen=fullscreenflag; hInstance = GetModuleHandle(NULL); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "OpenGL"; if (!RegisterClass(&wc)) {
MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; }
if (fullscreen) {
DEVMODE dmScreenSettings; memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); dmScreenSettings.dmSize=sizeof(dmScreenSettings); dmScreenSettings.dmPelsWidth = width; dmScreenSettings.dmPelsHeight = height; dmScreenSettings.dmBitsPerPel = bits; dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
fullscreen=FALSE; }
else
{
MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
return FALSE; }
}
}
if (fullscreen) {
dwExStyle=WS_EX_APPWINDOW; dwStyle=WS_POPUP; ShowCursor(FALSE); }
else
{
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; dwStyle=WS_OVERLAPPEDWINDOW; }
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); if (!(hWnd=CreateWindowEx( dwExStyle, "OpenGL", title, dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0, 0, WindowRect.right-WindowRect.left, WindowRect.bottom-WindowRect.top, NULL, NULL, hInstance, NULL))) {
KillGLWindow(); MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; }
static PIXELFORMATDESCRIPTOR pfd= {
sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, bits, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 };
if (!(hDC=GetDC(hWnd))) {
KillGLWindow(); MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; }
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) {
KillGLWindow(); MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; }
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) {
KillGLWindow(); MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; }
if (!(hRC=wglCreateContext(hDC))) {
KillGLWindow(); MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; }
if(!wglMakeCurrent(hDC,hRC)) {
KillGLWindow(); MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; }
ShowWindow(hWnd,SW_SHOW); SetForegroundWindow(hWnd); SetFocus(hWnd); ReSizeGLScene(width, height); if (!InitGL()) {
KillGLWindow(); MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; }
return TRUE; }
LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_ACTIVATE: {
if (!HIWORD(wParam)) {
active=TRUE; }
else
{
active=FALSE; }
return 0; }
case WM_SYSCOMMAND: {
switch (wParam) {
case SC_SCREENSAVE: case SC_MONITORPOWER: return 0; }
break; }
case WM_CLOSE: {
PostQuitMessage(0); return 0; }
case WM_KEYDOWN: {
keys[wParam] = TRUE; return 0; }
case WM_KEYUP: {
keys[wParam] = FALSE; return 0; }
case WM_SIZE: {
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); return 0; }
}
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MSG msg; BOOL done=FALSE; if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; }
if (!CreateGLWindow("NeHe's Texture Mapping Tutorial",640,480,16,fullscreen))
{
return 0; }
while(!done) {
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
if (msg.message==WM_QUIT) {
done=TRUE; }
else {
TranslateMessage(&msg); DispatchMessage(&msg); }
}
else {
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) {
done=TRUE; }
else {
SwapBuffers(hDC); }
if (keys[VK_F1]) {
keys[VK_F1]=FALSE; KillGLWindow(); fullscreen=!fullscreen; if (!CreateGLWindow("NeHe's Texture Mapping Tutorial",640,480,16,fullscreen))
{
return 0; }
}
}
}
KillGLWindow(); return (msg.wParam); }
|