//-----------------------------------------------------------------------------
// 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 );
}