//----------------------------------------------------------------------------- // Name: ogl_dot3_bump_mapping.cpp // Author: Kevin Harris // Last Modified: 04/21/05 // Description: This sample demonstrates how to perform Dot3 per-pixel bump // mapping using a normal map and the GL_DOT3_RGB_EXT // texture-blending operation. This technique is sometimes // referred to as per-pixel lighting or per-pixel attenuation, // but Dot3 per-pixel bump-mapping is what most programmers // know it as. // // This sample also demonstrates how to create tangent, // binormal, and normal vectors, for each vertex of our test // geometry (a simple cube). These vectors are used during // rendering to define an inverse tangent matrix for each // vertex. This has to be done because a normal-map stores its // normals in tangent-space. Therefore, we need an inverse // tangent matrix so we can transform our scene's light vector // from object-space into tangent-space. Once transformed, we // then encode this new light vector as a RGB color and pass // it into the texture blending stage as the vertex's Diffuse // color. // // Additional Notes: // // The phrase "Dot3" comes form the mathematical operation that // combines a light vector with a surface normal. // // The phrase "Per-pixel" comes from the fact that for every // pixel in the base texture map, we store a unique surface // normal to light it. These surface normals are passed into the // texture blending stage via a normal-map. A normal-map is a // texture where normals (x,y,z) have been encoded and stored // as (r,g,b). // // Control Keys: d/D - Toggle Dot3 bump mapping // l/L - Toggle the usage of regular lighting in addition // to the per-pixel lighting effect of bump mapping. // m/M - Toggle motion of point light // Up Arrow - Move the test cube closer // Down Arrow - Move the test cube away //----------------------------------------------------------------------------- #define STRICT #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <mmsystem.h> // Do this to access M_PI, which is not officially part of the C/C++ standard. #define _USE_MATH_DEFINES #include <math.h> #include <stdlib.h> #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> #include "geometry.h" #include "matrix4x4f.h" #include "vector3f.h" #include "resource.h" //----------------------------------------------------------------------------- // FUNCTION POINTERS FOR OPENGL EXTENSIONS //----------------------------------------------------------------------------- // For convenience, this project ships with its own "glext.h" extension header // file. If you have trouble running this sample, it may be that this "glext.h" // file is defining something that your hardware doesn抰 actually support. // Try recompiling the sample using your own local, vendor-specific "glext.h" // header file. #include "glext.h" // Sample's header file //#include <GL/glext.h> // Your local header file PFNGLACTIVETEXTUREPROC glActiveTexture = NULL; PFNGLMULTITEXCOORD2FPROC glMultiTexCoord2f = NULL; PFNGLCLIENTACTIVETEXTUREPROC glClientActiveTexture = NULL; //----------------------------------------------------------------------------- // GLOBALS //----------------------------------------------------------------------------- HDC g_hDC = NULL; HGLRC g_hRC = NULL; HWND g_hWnd = NULL; GLuint g_textureID = -1; GLuint g_normalmapTextureID = -1; bool g_bDoDot3BumpMapping = true; bool g_bMoveLightAbout = true; bool g_bToggleRegularLighting = false; GLfloat g_lightPosition[] = { 0.0f, 0.0f, 0.0f, 1.0f }; // Point Light's position float g_fDistance = -5.0f; float g_fSpinX = 0.0f; float g_fSpinY = 0.0f; float g_fElpasedTime; double g_dCurTime; double g_dLastTime; struct Vertex { float tu, tv; float r, g, b, a; float nx, ny, nz; float x, y, z; }; const int NUM_VERTICES = 24; Vertex g_cubeVertices[NUM_VERTICES] = { // tu tv r g b a nx ny nz x y z // Front face { 0.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 0.0f, 1.0f, -1.0f,-1.0f, 1.0f }, { 1.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 0.0f, 1.0f, 1.0f,-1.0f, 1.0f }, { 1.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 1.0f }, // Back face { 1.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 0.0f,-1.0f, -1.0f,-1.0f,-1.0f }, { 1.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 0.0f,-1.0f, -1.0f, 1.0f,-1.0f }, { 0.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 0.0f,-1.0f, 1.0f, 1.0f,-1.0f }, { 0.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 0.0f,-1.0f, 1.0f,-1.0f,-1.0f }, // Top face { 0.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f,-1.0f }, { 0.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, 1.0f }, { 1.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f }, { 1.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,-1.0f }, // Bottom face { 1.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f,-1.0f, 0.0f, -1.0f,-1.0f,-1.0f }, { 0.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f,-1.0f, 0.0f, 1.0f,-1.0f,-1.0f }, { 0.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f,-1.0f, 0.0f, 1.0f,-1.0f, 1.0f }, { 1.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 0.0f,-1.0f, 0.0f, -1.0f,-1.0f, 1.0f }, // Right face { 1.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 1.0f, 0.0f, 0.0f, 1.0f,-1.0f,-1.0f }, { 1.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,-1.0f }, { 0.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f }, { 0.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, 1.0f, 0.0f, 0.0f, 1.0f,-1.0f, 1.0f }, // Left face { 0.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, -1.0f, 0.0f, 0.0f, -1.0f,-1.0f,-1.0f }, { 1.0f,0.0f, 1.0f,1.0f,1.0f,1.0f, -1.0f, 0.0f, 0.0f, -1.0f,-1.0f, 1.0f }, { 1.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f }, { 0.0f,1.0f, 1.0f,1.0f,1.0f,1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f,-1.0f } }; // For each vertex defined above, we'll need to create a Tangent, BiNormal, and // Normal vector, which together define a tangent matrix for Dot3 bump mapping. vector3f g_vTangents[NUM_VERTICES]; vector3f g_vBiNormals[NUM_VERTICES]; vector3f g_vNormals[NUM_VERTICES]; //----------------------------------------------------------------------------- // PROTOTYPES //----------------------------------------------------------------------------- int WINAPI WinMain(HINSTANCE g_hInstance,HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow); LRESULT CALLBACK WindowProc(HWND g_hWnd, UINT msg, WPARAM wParam, LPARAM lParam); void loadTexture(void); void init(void); void render(void); void shutDown(void); vector3f scaleAndBiasVectorAsRGBColor(vector3f* vVector); void computeTangentsMatricesForEachVertex(void); void createTangentSpaceVectors( vector3f *v1, vector3f *v2, vector3f *v3, float v1u, float v1v, float v2u, float v2v, float v3u, float v3v, vector3f *vTangent, vector3f *vBiNormal, vector3f *vNormal ); //----------------------------------------------------------------------------- // Name: WinMain() // Desc: The application's entry point //----------------------------------------------------------------------------- int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { WNDCLASSEX winClass; MSG uMsg; memset(&uMsg,0,sizeof(uMsg)); winClass.lpszClassName = "MY_WINDOWS_CLASS"; winClass.cbSize = sizeof(WNDCLASSEX); winClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; winClass.lpfnWndProc = WindowProc; winClass.hInstance = hInstance; winClass.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_OPENGL_ICON); winClass.hIconSm = LoadIcon(hInstance, (LPCTSTR)IDI_OPENGL_ICON); winClass.hCursor = LoadCursor(NULL, IDC_ARROW); winClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); winClass.lpszMenuName = NULL; winClass.cbClsExtra = 0; winClass.cbWndExtra = 0; if( !RegisterClassEx(&winClass) ) return E_FAIL; g_hWnd = CreateWindowEx( NULL,"MY_WINDOWS_CLASS", "OpenGL - Dot3 Per-Pixel Bump Mapping", WS_OVERLAPPEDWINDOW, 0,0, 640,480, NULL, NULL, hInstance, NULL ); if( g_hWnd == NULL ) return E_FAIL; ShowWindow( g_hWnd, nCmdShow ); UpdateWindow( g_hWnd ); init(); while( uMsg.message != WM_QUIT ) { if( PeekMessage( &uMsg, NULL, 0, 0, PM_REMOVE ) ) { TranslateMessage( &uMsg ); DispatchMessage( &uMsg ); } else { g_dCurTime = timeGetTime(); g_fElpasedTime = (float)((g_dCurTime - g_dLastTime) * 0.001); g_dLastTime = g_dCurTime; render(); } } shutDown(); UnregisterClass( "MY_WINDOWS_CLASS", hInstance ); return uMsg.wParam; } //----------------------------------------------------------------------------- // Name: WindowProc() // Desc: The window's message handler //----------------------------------------------------------------------------- LRESULT CALLBACK WindowProc( HWND g_hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { static POINT ptLastMousePosit; static POINT ptCurrentMousePosit; static bool bMousing; switch( msg ) { case WM_CHAR: { switch( wParam ) { case 'd': case 'D': g_bDoDot3BumpMapping = !g_bDoDot3BumpMapping; break; case 'l': case 'L': g_bToggleRegularLighting = !g_bToggleRegularLighting; break; case 'm': case 'M': g_bMoveLightAbout = !g_bMoveLightAbout; break; } } break; case WM_KEYDOWN: { switch( wParam ) { case VK_ESCAPE: PostQuitMessage(0); break; case 38: // Up Arrow Key g_fDistance += 0.1f; break; case 40: // Down Arrow Key g_fDistance -= 0.1f; break; } } break; case WM_LBUTTONDOWN: { ptLastMousePosit.x = ptCurrentMousePosit.x = LOWORD (lParam); ptLastMousePosit.y = ptCurrentMousePosit.y = HIWORD (lParam); bMousing = true; } break; case WM_LBUTTONUP: { bMousing = false; } break; case WM_MOUSEMOVE: { ptCurrentMousePosit.x = LOWORD (lParam); ptCurrentMousePosit.y = HIWORD (lParam); if( bMousing ) { g_fSpinX -= (ptCurrentMousePosit.x - ptLastMousePosit.x); g_fSpinY -= (ptCurrentMousePosit.y - ptLastMousePosit.y); } ptLastMousePosit.x = ptCurrentMousePosit.x; ptLastMousePosit.y = ptCurrentMousePosit.y; } break; case WM_SIZE: { int nWidth = LOWORD(lParam); int nHeight = HIWORD(lParam); glViewport(0, 0, nWidth, nHeight); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluPerspective( 45.0, (GLdouble)nWidth / (GLdouble)nHeight, 0.1, 100.0); } break; case WM_CLOSE: { PostQuitMessage(0); } case WM_DESTROY: { PostQuitMessage(0); } break; default: { return DefWindowProc( g_hWnd, msg, wParam, lParam ); } break; } return 0; } //----------------------------------------------------------------------------- // Name: loadTexture() // Desc: //----------------------------------------------------------------------------- void loadTexture(void) { // // Load the normal map... // AUX_RGBImageRec *pTextureImage = auxDIBImageLoad( ".\\stone_wall_normal_map.bmp" ); //AUX_RGBImageRec *pTextureImage = auxDIBImageLoad( ".\\test_normal_map.bmp" ); if( pTextureImage != NULL ) { glGenTextures( 1, &g_normalmapTextureID ); glBindTexture( GL_TEXTURE_2D, g_normalmapTextureID ); glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, pTextureImage->sizeX, pTextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, pTextureImage->data ); } if( pTextureImage ) { if( pTextureImage->data ) free( pTextureImage->data ); free( pTextureImage ); } // // Load the base texture // pTextureImage = auxDIBImageLoad( ".\\stone_wall.bmp" ); //pTextureImage = auxDIBImageLoad( ".\\checker_with_numbers.bmp" ); if( pTextureImage != NULL ) { glGenTextures( 1, &g_textureID ); glBindTexture( GL_TEXTURE_2D, g_textureID ); glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, pTextureImage->sizeX, pTextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, pTextureImage->data ); } if( pTextureImage ) { if( pTextureImage->data ) free( pTextureImage->data ); free( pTextureImage ); } } //----------------------------------------------------------------------------- // Name: init() // Desc: //----------------------------------------------------------------------------- void init( void ) { GLuint PixelFormat; PIXELFORMATDESCRIPTOR pfd; memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 16; pfd.cDepthBits = 16; g_hDC = GetDC( g_hWnd ); PixelFormat = ChoosePixelFormat( g_hDC, &pfd ); SetPixelFormat( g_hDC, PixelFormat, &pfd); g_hRC = wglCreateContext( g_hDC ); wglMakeCurrent( g_hDC, g_hRC ); glClearColor( 0.35f, 0.53f, 0.7f, 1.0f ); glEnable( GL_TEXTURE_2D ); glEnable( GL_DEPTH_TEST ); //glShadeModel( GL_FLAT ); glShadeModel( GL_SMOOTH ); // Supposedly this is important to set when doing Dot3, but I notice no difference. glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluPerspective( 45.0f, 640.0f / 480.0f, 0.1f, 100.0f); // Set up the view matrix glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); // For lighting, pull our material colors from the vertex color... glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE ); glEnable( GL_COLOR_MATERIAL ); // Set light 0 to be a pure white point light GLfloat diffuse_light0[] = { 1.0f, 1.0f, 1.0f, 1.0f }; glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse_light0 ); glLightfv( GL_LIGHT0, GL_POSITION, g_lightPosition ); glEnable( GL_LIGHTING ); glEnable( GL_LIGHT0 ); // Enable some dim, gray ambient lighting so objects that are not lit // by the other lights are not completely black. GLfloat ambient_lightModel[] = { 0.2f, 0.2f, 0.2f, 0.2f }; glLightModelfv( GL_LIGHT_MODEL_AMBIENT, ambient_lightModel ); loadTexture(); // // If the required extension is present, get the addresses of its // functions that we wish to use... // // // GL_ARB_multitexture // char *ext = (char*)glGetString( GL_EXTENSIONS ); if( strstr( ext, "GL_ARB_multitexture" ) == NULL ) { MessageBox(NULL,"GL_ARB_multitexture extension was not found", "ERROR",MB_OK|MB_ICONEXCLAMATION); return; } else { glActiveTexture = (PFNGLCLIENTACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture"); glMultiTexCoord2f = (PFNGLMULTITEXCOORD2FPROC)wglGetProcAddress("glMultiTexCoord2f"); glClientActiveTexture = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTexture"); if( !glActiveTexture || !glMultiTexCoord2f || !glClientActiveTexture ) { MessageBox(NULL,"One or more GL_ARB_multitexture functions were not found", "ERROR",MB_OK|MB_ICONEXCLAMATION); return; } } // // GL_EXT_texture_env_combine // if( strstr( ext, "GL_EXT_texture_env_combine" ) == NULL ) { MessageBox(NULL,"GL_EXT_texture_env_combine extension was not found", "ERROR",MB_OK|MB_ICONEXCLAMATION); return; } // // For each vertex, create a tangent vector, binormal, and normal // // Initialize the inverse tangent matrix for each vertex to the identity // matrix before we get started. for( int i = 0; i < NUM_VERTICES; ++i ) { g_vTangents[i] = vector3f( 1.0f, 0.0f, 0.0f ); g_vBiNormals[i] = vector3f( 0.0f, 1.0f, 0.0f ); g_vNormals[i] = vector3f( 0.0f, 0.0f, 1.0f ); } computeTangentsMatricesForEachVertex(); } //----------------------------------------------------------------------------- // Name: shutDown() // Desc: //----------------------------------------------------------------------------- void shutDown(void) { glDeleteTextures( 1, &g_textureID ); glDeleteTextures( 1, &g_normalmapTextureID ); if( g_hRC != NULL ) { wglMakeCurrent( NULL, NULL ); wglDeleteContext( g_hRC ); g_hRC = NULL; } if( g_hDC != NULL ) { ReleaseDC( g_hWnd, g_hDC ); g_hDC = NULL; } } //----------------------------------------------------------------------------- // Name: computeTangentsMatricesForEachVertex() // 对每一个顶点计算切线矩阵 // Desc: //----------------------------------------------------------------------------- void computeTangentsMatricesForEachVertex(void) { vector3f v1; vector3f v2; vector3f v3; vector3f vTangent; vector3f vBiNormal; vector3f vNormal; // // For each cube face defined in the vertex array, compute a tangent matrix // for each of the four vertices that define it. // for( int i = 0; i < NUM_VERTICES; i += 4 ) // Use += 4 to process 1 face at a time { // // Vertex 0 of current cube face... // // v2 // 3----2 // | | // | | // 0----1 // v1 v3 // v1 = vector3f(g_cubeVertices[i].x,g_cubeVertices[i].y,g_cubeVertices[i].z); v2 = vector3f(g_cubeVertices[i+3].x,g_cubeVertices[i+3].y,g_cubeVertices[i+3].z); v3 = vector3f(g_cubeVertices[i+1].x,g_cubeVertices[i+1].y,g_cubeVertices[i+1].z); createTangentSpaceVectors( &v1,&v2,&v3, g_cubeVertices[i].tu, g_cubeVertices[i].tv, g_cubeVertices[i+3].tu, g_cubeVertices[i+3].tv, g_cubeVertices[i+1].tu, g_cubeVertices[i+1].tv, &vTangent, &vBiNormal, &vNormal ); g_vTangents[i] = vTangent; g_vBiNormals[i] = vBiNormal; g_vNormals[i] = vNormal; // // Vertex 1 of current cube face... // // v3 // 3----2 // | | // | | // 0----1 // v2 v1 // v1 = vector3f(g_cubeVertices[i+1].x,g_cubeVertices[i+1].y,g_cubeVertices[i+1].z); v2 = vector3f(g_cubeVertices[i].x,g_cubeVertices[i].y,g_cubeVertices[i].z); v3 = vector3f(g_cubeVertices[i+2].x,g_cubeVertices[i+2].y,g_cubeVertices[i+2].z); createTangentSpaceVectors( &v1,&v2,&v3, g_cubeVertices[i+1].tu, g_cubeVertices[i+1].tv, g_cubeVertices[i].tu, g_cubeVertices[i].tv, g_cubeVertices[i+2].tu, g_cubeVertices[i+2].tv, &vTangent, &vBiNormal, &vNormal ); g_vTangents[i+1] = vTangent; g_vBiNormals[i+1] = vBiNormal; g_vNormals[i+1] = vNormal; // // Vertex 2 of current cube face... // // v3 v1 // 3----2 // | | // | | // 0----1 // v2 // v1 = vector3f(g_cubeVertices[i+2].x,g_cubeVertices[i+2].y,g_cubeVertices[i+2].z); v2 = vector3f(g_cubeVertices[i+1].x,g_cubeVertices[i+1].y,g_cubeVertices[i+1].z); v3 = vector3f(g_cubeVertices[i+3].x,g_cubeVertices[i+3].y,g_cubeVertices[i+3].z); createTangentSpaceVectors( &v1,&v2,&v3, g_cubeVertices[i+2].tu, g_cubeVertices[i+2].tv, g_cubeVertices[i+1].tu, g_cubeVertices[i+1].tv, g_cubeVertices[i+3].tu, g_cubeVertices[i+3].tv, &vTangent, &vBiNormal, &vNormal ); g_vTangents[i+2] = vTangent; g_vBiNormals[i+2] = vBiNormal; g_vNormals[i+2] = vNormal; // // Vertex 3 of current cube face... // // v1 v2 // 3----2 // | | // | | // 0----1 // v3 // v1 = vector3f(g_cubeVertices[i+3].x,g_cubeVertices[i+3].y,g_cubeVertices[i+3].z); v2 = vector3f(g_cubeVertices[i+2].x,g_cubeVertices[i+2].y,g_cubeVertices[i+2].z); v3 = vector3f(g_cubeVertices[i].x,g_cubeVertices[i].y,g_cubeVertices[i].z); createTangentSpaceVectors( &v1,&v2,&v3, g_cubeVertices[i+3].tu, g_cubeVertices[i+3].tv, g_cubeVertices[i+2].tu, g_cubeVertices[i+2].tv, g_cubeVertices[i].tu, g_cubeVertices[i].tv, &vTangent, &vBiNormal, &vNormal ); g_vTangents[i+3] = vTangent; g_vBiNormals[i+3] = vBiNormal; g_vNormals[i+3] = vNormal; } } //----------------------------------------------------------------------------- // Name: createTangentSpaceVectors() // Desc: Given a vertex (v1) and two other vertices (v2 & v3) which define a // triangle, this function will return Tangent, BiNormal, and Normal, // vectors which can be used to define the tangent matrix for the first // vertex's position (v1). // // Args: v1 - vertex 1 // v2 - vertex 2 // v3 - vertex 3 // v1u, v1v - texture-coordinates of vertex 1 // v2u, v2v - texture-coordinates of vertex 2 // v3u, v3v - texture-coordinates of vertex 3 // vTangent - When the function returns, this will be set as the tangent vector // vBiNormal - When the function returns, this will be set as the binormal vector // vNormal - When the function returns, this will be set as the normal vector // // Note: This function is based on an article by By Jakob Gath and Sbren Dreijer. // http://www.blacksmith-studios.dk/projects/downloads/tangent_matrix_derivation.php //------------------------------------------------------------------------------ void createTangentSpaceVectors( vector3f *v1, vector3f *v2, vector3f *v3, float v1u, float v1v, float v2u, float v2v, float v3u, float v3v, vector3f *vTangent, vector3f *vBiNormal, vector3f *vNormal ) { // Create edge vectors from vertex 1 to vectors 2 and 3. vector3f vDirVec_v2_to_v1 = *v2 - *v1; vector3f vDirVec_v3_to_v1 = *v3 - *v1; // Create edge vectors from the texture coordinates of vertex 1 to vector 2. float vDirVec_v2u_to_v1u = v2u - v1u; float vDirVec_v2v_to_v1v = v2v - v1v; // Create edge vectors from the texture coordinates of vertex 1 to vector 3. float vDirVec_v3u_to_v1u = v3u - v1u; float vDirVec_v3v_to_v1v = v3v - v1v; float fDenominator = vDirVec_v2u_to_v1u * vDirVec_v3v_to_v1v - vDirVec_v3u_to_v1u * vDirVec_v2v_to_v1v; if( fDenominator < 0.0001f && fDenominator > -0.0001f ) { // We're too close to zero and we're at risk of a divide-by-zero! // Set the tangent matrix to the identity matrix and do nothing. *vTangent = vector3f( 1.0f, 0.0f, 0.0f ); *vBiNormal = vector3f( 0.0f, 1.0f, 0.0f ); *vNormal = vector3f( 0.0f, 0.0f, 1.0f ); } else { // Calculate and cache the reciprocal value float fScale1 = 1.0f / fDenominator; vector3f T; vector3f B; vector3f N; T = vector3f((vDirVec_v3v_to_v1v * vDirVec_v2_to_v1.x - vDirVec_v2v_to_v1v * vDirVec_v3_to_v1.x) * fScale1, (vDirVec_v3v_to_v1v * vDirVec_v2_to_v1.y - vDirVec_v2v_to_v1v * vDirVec_v3_to_v1.y) * fScale1, (vDirVec_v3v_to_v1v * vDirVec_v2_to_v1.z - vDirVec_v2v_to_v1v * vDirVec_v3_to_v1.z) * fScale1); B = vector3f((-vDirVec_v3u_to_v1u * vDirVec_v2_to_v1.x + vDirVec_v2u_to_v1u * vDirVec_v3_to_v1.x) * fScale1, (-vDirVec_v3u_to_v1u * vDirVec_v2_to_v1.y + vDirVec_v2u_to_v1u * vDirVec_v3_to_v1.y) * fScale1, (-vDirVec_v3u_to_v1u * vDirVec_v2_to_v1.z + vDirVec_v2u_to_v1u * vDirVec_v3_to_v1.z) * fScale1); // The normal N is calculated as the cross product between T and B N = crossProduct( T, B ); // Calculate and cache the reciprocal value float fScale2 = 1.0f / ((T.x * B.y * N.z - T.z * B.y * N.x) + (B.x * N.y * T.z - B.z * N.y * T.x) + (N.x * T.y * B.z - N.z * T.y * B.x)); // // Use the temporary T (Tangent), (B) Binormal, and N (Normal) vectors // to calculate the inverse of the tangent matrix that they represent. // The inverse of the tangent matrix is what we want since we need that // to transform the light's vector into tangent-space. // (*vTangent).x = crossProduct( B, N ).x * fScale2; (*vTangent).y = -(crossProduct( N, T ).x * fScale2); (*vTangent).z = crossProduct( T, B ).x * fScale2; (*vTangent).normalize(); (*vBiNormal).x = -(crossProduct( B, N ).y * fScale2); (*vBiNormal).y = crossProduct( N, T ).y * fScale2; (*vBiNormal).z = -(crossProduct( T, B ).y * fScale2); (*vBiNormal).normalize(); (*vNormal).x = crossProduct( B, N ).z * fScale2; (*vNormal).y = -(crossProduct( N, T ).z * fScale2); (*vNormal).z = crossProduct( T, B ).z * fScale2; (*vNormal).normalize(); // // NOTE: Since the texture-space of Direct3D and OpenGL are laid-out // differently, a single normal map can't look right in both // unless you make some adjustments somewhere. // // You can adjust or fix this problem in three ways: // // 1. Create two normal maps: one for OpenGL and one for Direct3D. // 2. Flip the normal map image over as you load it into a texture // object. // 3. Flip the binormal over when computing the tangent-space // matrix. // // Since the normal map used by this sample was created for Direct3D, // I've decided to simply flip the binormal. // *vBiNormal = *vBiNormal * -1.0f; } } //----------------------------------------------------------------------------- // Name: scaleAndBiasVectorAsRGBColor() // Desc: //----------------------------------------------------------------------------- vector3f scaleAndBiasVectorAsRGBColor( vector3f* vVector ) { vector3f vScaledAndBiasedVector; vScaledAndBiasedVector.x = ((*vVector).x * 0.5f) + 0.5f; vScaledAndBiasedVector.y = ((*vVector).y * 0.5f) + 0.5f; vScaledAndBiasedVector.z = ((*vVector).z * 0.5f) + 0.5f; return vScaledAndBiasedVector; } //----------------------------------------------------------------------------- // Name: render() // Desc: //----------------------------------------------------------------------------- void render( void ) { // Clear the screen and the depth buffer glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); // Translate our view back a bit glTranslatef( 0.0f, 0.0f, g_fDistance ); if( g_bMoveLightAbout == true ) { // // Spin our point light around the cube... // static float fAngle = 0; fAngle += 60 * g_fElpasedTime; // Wrap it around, if it gets too big while(fAngle > 360.0f) fAngle -= 360.0f; while(fAngle < 0.0f) fAngle += 360.0f; float x = sinf( DEGTORAD(fAngle) ); float y = cosf( DEGTORAD(fAngle) ); // The call to glLightfv is just like calling glTranslatef on our // light's position. g_lightPosition[0] = 1.2f * x; g_lightPosition[1] = 1.2f * y; g_lightPosition[2] = 2.0f; glLightfv( GL_LIGHT0, GL_POSITION, g_lightPosition ); } // Spin the cube via mouse input. glRotatef( -g_fSpinY, 1.0f, 0.0f, 0.0f ); glRotatef( -g_fSpinX, 0.0f, 1.0f, 0.0f ); // // Set texture units to some known state... // glActiveTexture(GL_TEXTURE0); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glDisable(GL_TEXTURE_3D_EXT); glDisable(GL_TEXTURE_CUBE_MAP_ARB); glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glActiveTexture(GL_TEXTURE1); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glDisable(GL_TEXTURE_3D_EXT); glDisable(GL_TEXTURE_CUBE_MAP_ARB); glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); if( g_bToggleRegularLighting == true ) glEnable( GL_LIGHTING ); else glDisable( GL_LIGHTING ); if( g_bDoDot3BumpMapping == true ) { // // Render a Dot3 bump mapped cube... // // // STAGE 0 // // Use GL_DOT3_RGB_EXT to find the dot-product of (N.L), where N is // stored in the normal map, and L is passed in as the PRIMARY_COLOR // using the standard glColor3f call. // glActiveTexture(GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, g_normalmapTextureID); glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); // Perform a Dot3 operation... glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_DOT3_RGB_EXT); glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); // between the N (of N.L) which is stored in a normal map texture... glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR); glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); // with the L (of N.L) which is stored in the vertex's diffuse color. glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR); // // STAGE 1 // // Modulate the base texture by N.L calculated in STAGE 0. // glActiveTexture(GL_TEXTURE1); glBindTexture (GL_TEXTURE_2D, g_textureID); glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); // Modulate... glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT); glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR); // the color argument passed down from the previous stage (stage 0) with... glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE); glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR); // the texture for this stage with. // // Transform the light's position from eye-space to object-space // vector3f vLightPosES; // Light position (in eye-space) vector3f vLightPosOS; // Light position (in object-space) vector3f vVertToLightOS; // L vector of N.L (in object-space) vector3f vVertToLightTS; // L vector of N.L (in tangent-space) // Get the light's current position, which is in eye-space. float fLightsPosition[4]; glGetLightfv( GL_LIGHT0, GL_POSITION, fLightsPosition ); vLightPosES.x = fLightsPosition[0]; vLightPosES.y = fLightsPosition[1]; vLightPosES.z = fLightsPosition[2]; // Transform the light's position from eye-space into object-space matrix4x4f modelViewMatrix; matrix4x4f modelViewMatrixInverse; glGetFloatv(GL_MODELVIEW_MATRIX, &modelViewMatrix.m[0] ); modelViewMatrixInverse = matrix4x4f::invertMatrix( &modelViewMatrix ); vLightPosOS = vLightPosES; modelViewMatrixInverse.transformPoint( &vLightPosOS ); // // Now, render our textured test cube, which consists of 6 quads... // vector3f vCurrentVertex; glBegin( GL_QUADS ); { for( int i = 0; i < NUM_VERTICES; ++i ) { vCurrentVertex.x = g_cubeVertices[i].x; vCurrentVertex.y = g_cubeVertices[i].y; vCurrentVertex.z = g_cubeVertices[i].z; glMultiTexCoord2f( GL_TEXTURE0, g_cubeVertices[i].tu, g_cubeVertices[i].tv ); glMultiTexCoord2f( GL_TEXTURE1, g_cubeVertices[i].tu, g_cubeVertices[i].tv ); // // For each vertex, rotate L (of N.L) into tangent-space and // pass it into OpenGL's texture blending system by packing it // into the glVertex3f for that vertex. // vVertToLightOS = vLightPosOS - vCurrentVertex; vVertToLightOS.normalize(); // // Build up an inverse tangent-space matrix using the Tangent, // Binormal, and Normal calculated for the current vertex and // then use it to transform our L vector (of N.L), which is in // object-space, into tangent-space. // // A tangent matrix is of the form: // // |Tx Bx Nx 0| // |Ty By Ny 0| // |Tz Bz Nz 0| // |0 0 0 1| // // Note: Our vectors have already been inverted, so there is no // need to invert our tangent matrix once we build it up. // // Tangent Binormal Normal matrix4x4f invTangentMatrix( g_vTangents[i].x, g_vBiNormals[i].x, g_vNormals[i].x, 0.0f, g_vTangents[i].y, g_vBiNormals[i].y, g_vNormals[i].y, 0.0f, g_vTangents[i].z, g_vBiNormals[i].z, g_vNormals[i].z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ); vVertToLightTS = vVertToLightOS; invTangentMatrix.transformVector( &vVertToLightTS ); // // Last but not least, we must scale and bias our L vector // before passing it as a vertex color since colors have a // clamped range of [0,1]. If we don't do this any negative // components of our L vector will get clamped and the vector // will be wrong. Of course, the hardware assumes that we are // going to do this, so it will simply decode the original // vector back out by reversing the scale and bias we've // performed here. // vector3f vVertToLightTS_scaledAndBiased = scaleAndBiasVectorAsRGBColor( &vVertToLightTS ); glColor3f( vVertToLightTS_scaledAndBiased.x, vVertToLightTS_scaledAndBiased.y, vVertToLightTS_scaledAndBiased.z ); //glColor3f( 0.8, // 0.6, // 0.5 ); glNormal3f( g_cubeVertices[i].nx, g_cubeVertices[i].ny, g_cubeVertices[i].nz ); glVertex3f( vCurrentVertex.x, vCurrentVertex.y, vCurrentVertex.z ); } } glEnd(); ///////////////////////////////////////////////////////////////////////////////////////////// // glTranslatef(1,0,0); glDisable(GL_LIGHTING); #if 1 glDisable(GL_DEPTH_TEST); glCullFace(GL_BACK); glEnable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_ONE,GL_ONE); glActiveTexture(GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, g_textureID); glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glActiveTexture(GL_TEXTURE1); glDisable(GL_TEXTURE_2D); glBegin( GL_QUADS ); { for( int i = 0; i < NUM_VERTICES; ++i ) { glMultiTexCoord2f( GL_TEXTURE0, g_cubeVertices[i].tu, g_cubeVertices[i].tv ); glMultiTexCoord2f( GL_TEXTURE1, g_cubeVertices[i].tu, g_cubeVertices[i].tv ); //glColor3f( g_cubeVertices[i].r, g_cubeVertices[i].g, g_cubeVertices[i].b ); //glNormal3f( g_cubeVertices[i].nx, g_cubeVertices[i].ny, g_cubeVertices[i].nz ); glVertex3f( g_cubeVertices[i].x, g_cubeVertices[i].y, g_cubeVertices[i].z ); } } glEnd(); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); #endif } else { // // Render a regular textured cube with no Dot3 bump mapping... // // // STAGE 0 // glActiveTexture(GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, g_textureID); glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE); glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR); // // STAGE 1 // glActiveTexture(GL_TEXTURE1); glDisable(GL_TEXTURE_2D); glBegin( GL_QUADS ); { for( int i = 0; i < NUM_VERTICES; ++i ) { glMultiTexCoord2f( GL_TEXTURE0, g_cubeVertices[i].tu, g_cubeVertices[i].tv ); glMultiTexCoord2f( GL_TEXTURE1, g_cubeVertices[i].tu, g_cubeVertices[i].tv ); glColor3f( g_cubeVertices[i].r, g_cubeVertices[i].g, g_cubeVertices[i].b ); glNormal3f( g_cubeVertices[i].nx, g_cubeVertices[i].ny, g_cubeVertices[i].nz ); glVertex3f( g_cubeVertices[i].x, g_cubeVertices[i].y, g_cubeVertices[i].z ); } } glEnd(); } // // Reset texture unit state // glActiveTexture(GL_TEXTURE0); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glDisable(GL_TEXTURE_3D_EXT); glDisable(GL_TEXTURE_CUBE_MAP_ARB); glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glActiveTexture(GL_TEXTURE1); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glDisable(GL_TEXTURE_3D_EXT); glDisable(GL_TEXTURE_CUBE_MAP_ARB); glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // // Render a small white sphere to mark the point light's position... // glDisable( GL_LIGHTING ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glTranslatef( 0.0f, 0.0f, g_fDistance ); glTranslatef( g_lightPosition[0], g_lightPosition[1], g_lightPosition[2] ); glColor3f( 1.0f, 1.0f, 1.0f ); renderSolidSphere( 0.05f, 8, 8 ); glEnable( GL_LIGHTING ); SwapBuffers( g_hDC ); }