www.pudn.com > tomohide_fur.03.13.02.zip > DynamicFur.cpp
//----------------------------------------------------------------------------- // File: DynamicFur.cpp // // Copyright (C) 2001-2002 Tomohide Kano. All rights reserved. //----------------------------------------------------------------------------- #pragma comment (lib, "d3dx8.lib") #define DECLARE_EXTENSION_SUBSTANCE #include#include #include #include "extensions.h" #include "FurMesh.h" #include "FurTexture.h" #include "NormalMap.h" #include "MeshFunc.h" #include "text.h" #include "timer.h" #include "trackball.h" //============================================================================= // constants //============================================================================= enum { MENU_NOP, MENU_CHANGE_OBJECT, MENU_TOGGLE_WIREFRAME, MENU_INCREASE_FUR_LAYERS, MENU_DECREASE_FUR_LAYERS, MENU_INCREASE_FUR_LENGTH, MENU_DECREASE_FUR_LENGTH, MENU_TOGGLE_ANISOTROPIC_FILTER, MENU_TOGGLE_BACKFACE_CULLING, MENU_TOGGLE_ALPHA_TEST, MENU_TOGGLE_LIGHT_ROTATION, MENU_TOGGLE_DIFFUSE, MENU_TOGGLE_SPECULAR, MENU_TOGGLE_SIMULATION, MENU_TOGGLE_GRAVITY, MENU_SHOW_HIDE_NORMAL_MAP, MENU_SHOW_HIDE_OFFSET_MAP, MENU_SHOW_HIDE_INFO, MENU_TOGGLE_FULLSCREEN, MENU_EXIT }; #define FULLSCREEN_WIDTH 640 #define FULLSCREEN_HEIGHT 480 #define DEMO_MANUAL_MODE_TIMEOUT 5.0 #define DEMO_AUTOMATIC_MODE_OBJECT_SWITCH_TIMEOUT 10.0 //============================================================================= // static variables //============================================================================= // initialization flag static bool s_Initialized = false; // miscellaneous states static int s_NumLayers = 20; static float s_FurLength = 0.7; static bool s_Wireframe = false; static bool s_RotateLight = false; static bool s_UseAnisotropy = true; static bool s_CullBackface = true; static bool s_UseAlphaTest = false; static bool s_DrawDiffuse = true; static bool s_DrawSpecular = true; static bool s_UseSimulation = true; static bool s_UseGravity = true; static bool s_ShowNormalMap = false; static bool s_ShowOffsetMap = false; static bool s_FullScreenPossible = false; static bool s_FullScreenMode = false; static bool s_ShowFPS = false; static int s_ShowInfo = 1; // timer used to determine what mode the demo is in, // demo will go into automatic mode in 5 seconds if nothing is touched static float s_IdleModeTimer = DEMO_MANUAL_MODE_TIMEOUT; // client area size static int s_Width; static int s_Height; // mouse tracking (modelview transformation) static bool s_MouseState[3] = { false, false, false }; static D3DXVECTOR2 s_Mouse; static D3DXVECTOR2 s_PrevMouse; static D3DXVECTOR3 s_Trans; static D3DXVECTOR3 s_TransDelta; static D3DXQUATERNION s_Rot; static D3DXQUATERNION s_RotDelta; // projection parameters static float s_NearZ = 0.5; static float s_FarZ = 50.0; static float s_FovY = 30.0; // modelview matrix static D3DXMATRIX s_ModelView; static D3DXMATRIX s_ModelViewInv; // model meshes static FurMesh * s_pMeshes = NULL; static FurMesh * s_pCurrentMesh = NULL; static int s_CurrentMesh = 0; // textures static FurTexture * s_pFurTexture = NULL; static NormalMap * s_pNormalMap = NULL; static GLuint s_texLighting = 0; // vertex/fragment shaders static GLuint s_vsSkin = 0; static GLuint s_vsFur = 0; static GLuint s_fsFur = 0; // vertex shader invariants static GLuint I_LIGHT_DIR = 0; static GLuint I_CAMERA_POS = 0; static GLuint I_FUR_LENGTH = 0; static GLuint I_OFFSET_SCALE = 0; //============================================================================= // function prototypes //============================================================================= void InitInterface(); bool SetupScreenMode(int width, int height, bool aAttemptFullScreen); void CleanUp(); bool Init(); bool InitShaders(); void MouseButton(int button, int state, int x, int y); void MouseMotion(int x, int y); void Menu(int item); void Keyboard(unsigned char key, int x, int y); void SpecialKey(int key, int x, int y); void Reshape(int w, int h); void Idle(); void Display(); void RenderFurMesh(); void ShowTexture(int x, int y, int w, int h); void ShowInfo(); //----------------------------------------------------------------------------- // main //----------------------------------------------------------------------------- int main(int argc, char *argv[]) { bool fullScreen = true; atexit(CleanUp); // initialize GLUT glutInit(&argc, argv); glutInitDisplayString("double rgb>=8 depth>=16"); // check command line options for (int i = 1; i < argc; i++) { if (strcmp("-window", argv[i]) == 0) { fullScreen = false; } if (strcmp("-showfps", argv[i]) == 0) { s_ShowFPS = true; } } s_FullScreenPossible = SetupScreenMode(FULLSCREEN_WIDTH, FULLSCREEN_HEIGHT, fullScreen); s_FullScreenMode = s_FullScreenPossible; InitInterface(); return 0; } //----------------------------------------------------------------------------- // InitInterface //----------------------------------------------------------------------------- void InitInterface() { // register GLUT callback functions glutKeyboardFunc(Keyboard); glutSpecialFunc(SpecialKey); glutReshapeFunc(Reshape); glutMouseFunc(MouseButton); glutMotionFunc(MouseMotion); glutIdleFunc(Idle); glutDisplayFunc(Display); // initialize popup menu if not in full screen mode if (!s_FullScreenMode) { glutCreateMenu(Menu); glutAddMenuEntry("Change Object [Enter]", MENU_CHANGE_OBJECT); glutAddMenuEntry("Toggle Wireframe [W]", MENU_TOGGLE_WIREFRAME); glutAddMenuEntry("-------------------------", MENU_NOP); glutAddMenuEntry("Increase Fur Layers [PageUp]", MENU_INCREASE_FUR_LAYERS); glutAddMenuEntry("Decrease Fur Layers [PageDown]", MENU_DECREASE_FUR_LAYERS); glutAddMenuEntry("Increase Fur Length [Home]", MENU_INCREASE_FUR_LENGTH); glutAddMenuEntry("Decrease Fur Length [End]", MENU_DECREASE_FUR_LENGTH); glutAddMenuEntry("-------------------------", MENU_NOP); glutAddMenuEntry("Toggle Anisotropic Filter [F]", MENU_TOGGLE_ANISOTROPIC_FILTER); glutAddMenuEntry("Toggle Backface Culling [B]", MENU_TOGGLE_BACKFACE_CULLING); glutAddMenuEntry("Toggle Alpha Test [A]", MENU_TOGGLE_ALPHA_TEST); glutAddMenuEntry("-------------------------", MENU_NOP); glutAddMenuEntry("Toggle Light Rotation [L]", MENU_TOGGLE_LIGHT_ROTATION); glutAddMenuEntry("Toggle Diffuse [D]", MENU_TOGGLE_DIFFUSE); glutAddMenuEntry("Toggle Specular [S]", MENU_TOGGLE_SPECULAR); glutAddMenuEntry("-------------------------", MENU_NOP); glutAddMenuEntry("Toggle Fur Simulation [Space]", MENU_TOGGLE_SIMULATION); glutAddMenuEntry("Toggle Gravity [G]", MENU_TOGGLE_GRAVITY); glutAddMenuEntry("-------------------------", MENU_NOP); glutAddMenuEntry("Show/Hide Normal Map [N]", MENU_SHOW_HIDE_NORMAL_MAP); glutAddMenuEntry("Show/Hide Offset Map [O]", MENU_SHOW_HIDE_OFFSET_MAP); glutAddMenuEntry("Show/Hide Info [F1]", MENU_SHOW_HIDE_INFO); glutAddMenuEntry("-------------------------", MENU_NOP); if (s_FullScreenPossible) glutAddMenuEntry("Toggle Full Screen [Tab]", MENU_TOGGLE_FULLSCREEN); glutAddMenuEntry("Exit [Esc]", MENU_EXIT); glutAttachMenu(GLUT_RIGHT_BUTTON); } if (!Init()) { exit(0); } glutMainLoop(); } //----------------------------------------------------------------------------- // SetupScreenMode //----------------------------------------------------------------------------- bool SetupScreenMode(int width, int height, bool aAttemptFullScreen) { char modeString[256]; // attempt full screen mode if (aAttemptFullScreen) { // prefer as higher refresh rate as possible sprintf(modeString, "width=%d height=%d bpp=32 hertz>=60", width, height); glutGameModeString(modeString); if (glutGameModeGet(GLUT_GAME_MODE_WIDTH) != -1) { glutEnterGameMode(); // enter full screen mode return true; } } // no full screen mode, init window instead glutInitWindowSize(width, height); glutCreateWindow("Dynamic Fur Demo"); return false; } //----------------------------------------------------------------------------- // CleanUp //----------------------------------------------------------------------------- void CleanUp() { if (!s_Initialized) { MessageBox(NULL, "Failed to initialize the demo.", "Error", MB_ICONERROR); return; } if (s_pMeshes) delete [] s_pMeshes; if (s_pFurTexture) delete s_pFurTexture; if (s_pNormalMap) delete s_pNormalMap; if (s_texLighting) glDeleteTextures(1, &s_texLighting); if (s_vsSkin) glDeleteVertexShaderEXT(s_vsSkin); if (s_vsFur) glDeleteVertexShaderEXT(s_vsFur); if (s_fsFur) glDeleteFragmentShaderATI(s_fsFur); textDestroy(); timerDestroy(); } //----------------------------------------------------------------------------- // Init //----------------------------------------------------------------------------- bool Init() { // OpenGL extensions if (!InitExtensions()) return false; // modelview transformation s_Trans = D3DXVECTOR3(0, 0, -17); s_TransDelta = D3DXVECTOR3(0, 0, 0); D3DXQuaternionRotationYawPitchRoll(&s_Rot, 0, -0.3*D3DX_PI, 0.2*D3DX_PI); D3DXQuaternionIdentity(&s_RotDelta); // model meshes s_pMeshes = new FurMesh[NUM_MESHES]; for (int i = 0; i < NUM_MESHES; i++) { if (!s_pMeshes[i].Init(s_MeshParams[i])) return false; } s_pCurrentMesh = &s_pMeshes[s_CurrentMesh]; // fur texture s_pFurTexture = new FurTexture(); if (!s_pFurTexture->Init(timeGetTime(), 128, 20)) return false; s_pFurTexture->SetMaxAnisotropy(16); // normal map s_pNormalMap = new NormalMap(); if (!s_pNormalMap->Init(64, 32)) return false; // lighting texture glGenTextures(1, &s_texLighting); glBindTexture(GL_TEXTURE_3D_EXT, s_texLighting); BuildFurLightingTexture(); // vertex/fragment shaders if (!InitShaders()) return false; // text drawing if (!textInit()) return false; // timer if (!timerInit(1.0)) return false; s_Initialized = true; return true; } //----------------------------------------------------------------------------- // InitShaders //----------------------------------------------------------------------------- bool InitShaders() { // invariants GLuint I_MODELVIEWPROJ = glBindParameterEXT(GL_MVP_MATRIX_EXT); GLuint I_MODELVIEW = glBindParameterEXT(GL_MODELVIEW_MATRIX); I_LIGHT_DIR = glGenSymbolsEXT(GL_VECTOR_EXT, GL_INVARIANT_EXT, GL_FULL_RANGE_EXT, 1); I_CAMERA_POS = glGenSymbolsEXT(GL_VECTOR_EXT, GL_INVARIANT_EXT, GL_FULL_RANGE_EXT, 1); I_FUR_LENGTH = glGenSymbolsEXT(GL_SCALAR_EXT, GL_INVARIANT_EXT, GL_FULL_RANGE_EXT, 1); I_OFFSET_SCALE = glGenSymbolsEXT(GL_SCALAR_EXT, GL_INVARIANT_EXT, GL_FULL_RANGE_EXT, 1); // variants GLuint V_POS = glBindParameterEXT(GL_CURRENT_VERTEX_EXT); GLuint V_TEX1 = glBindTextureUnitParameterEXT(GL_TEXTURE0_ARB, GL_CURRENT_TEXTURE_COORDS); GLuint V_TEX2 = glBindTextureUnitParameterEXT(GL_TEXTURE1_ARB, GL_CURRENT_TEXTURE_COORDS); GLuint V_SCALE = glBindTextureUnitParameterEXT(GL_TEXTURE2_ARB, GL_CURRENT_TEXTURE_COORDS); GLuint V_RADIUS = glBindTextureUnitParameterEXT(GL_TEXTURE3_ARB, GL_CURRENT_TEXTURE_COORDS); GLuint V_DS = glBindTextureUnitParameterEXT(GL_TEXTURE4_ARB, GL_CURRENT_TEXTURE_COORDS); GLuint V_DT = glBindTextureUnitParameterEXT(GL_TEXTURE5_ARB, GL_CURRENT_TEXTURE_COORDS); GLuint V_NORMAL = glBindParameterEXT(GL_CURRENT_NORMAL); ////////////////////////////////////////////////////////////////////// // vertex shader for skin s_vsSkin = glGenVertexShadersEXT(1); glBindVertexShaderEXT(s_vsSkin); glBeginVertexShaderEXT(); { // transform the vertex into homogeneous clip space glShaderOp2EXT(GL_OP_MULTIPLY_MATRIX_EXT, GL_OUTPUT_VERTEX_EXT, I_MODELVIEWPROJ, V_POS); // calculate diffuse color (hemisphere lighting) GLuint R_TEMP = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); GLuint C_1 = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1); GLuint C_2 = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1); glSetLocalConstantEXT(C_1, GL_FLOAT, D3DXVECTOR4(0.09, 0.06, 0.03, 0.0)); glSetLocalConstantEXT(C_2, GL_FLOAT, D3DXVECTOR4(0.09, 0.06, 0.03, 0.0)); glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMP, I_LIGHT_DIR, V_NORMAL); glShaderOp3EXT(GL_OP_MADD_EXT, GL_OUTPUT_COLOR0_EXT, R_TEMP, C_1, C_2); } glEndVertexShaderEXT(); ////////////////////////////////////////////////////////////////////// // vertex shader for fur s_vsFur = glGenVertexShadersEXT(1); glBindVertexShaderEXT(s_vsFur); glBeginVertexShaderEXT(); { // local variables GLuint R_TEMPS = glGenSymbolsEXT(GL_SCALAR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); GLuint R_TEMPV = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); GLuint R_POS = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); GLuint R_HALF = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); // add fur length to vertex position glShaderOp3EXT(GL_OP_MADD_EXT, R_POS, V_NORMAL, I_FUR_LENGTH, V_POS); // transform vertex into homogeneous clip space glShaderOp2EXT(GL_OP_MULTIPLY_MATRIX_EXT, GL_OUTPUT_VERTEX_EXT, I_MODELVIEWPROJ, R_POS); // assign texture coord for normal map glShaderOp1EXT(GL_OP_MOV_EXT, GL_OUTPUT_TEXTURE_COORD0_EXT, V_TEX1); // calculate light vector { // transform light vector into local texture space glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_DS, I_LIGHT_DIR); glInsertComponentEXT(R_TEMPV, R_TEMPS, 0); glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_DT, I_LIGHT_DIR); glInsertComponentEXT(R_TEMPV, R_TEMPS, 1); glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_NORMAL, I_LIGHT_DIR); glInsertComponentEXT(R_TEMPV, R_TEMPS, 2); // normalize light vector and store it into TEXTURE_COORD1 // (texture space is not necessarily orthonormal) glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, R_TEMPV, R_TEMPV); glShaderOp1EXT(GL_OP_RECIP_SQRT_EXT, R_TEMPS, R_TEMPS); glShaderOp2EXT(GL_OP_MUL_EXT, GL_OUTPUT_TEXTURE_COORD1_EXT, R_TEMPV, R_TEMPS); } // calculate halfangle vector { // calculate halfangle vector in model space glShaderOp2EXT(GL_OP_SUB_EXT, R_TEMPV, I_CAMERA_POS, R_POS); glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, R_TEMPV, R_TEMPV); glShaderOp1EXT(GL_OP_RECIP_SQRT_EXT, R_TEMPS, R_TEMPS); glShaderOp3EXT(GL_OP_MADD_EXT, R_HALF, R_TEMPV, R_TEMPS, I_LIGHT_DIR); glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, R_HALF, R_HALF); glShaderOp1EXT(GL_OP_RECIP_SQRT_EXT, R_TEMPS, R_TEMPS); glShaderOp2EXT(GL_OP_MUL_EXT, R_HALF, R_HALF, R_TEMPS); // transform halfangle vector into local texture space glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_DS, R_HALF); glInsertComponentEXT(R_TEMPV, R_TEMPS, 0); glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_DT, R_HALF); glInsertComponentEXT(R_TEMPV, R_TEMPS, 1); glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_NORMAL, R_HALF); glInsertComponentEXT(R_TEMPV, R_TEMPS, 2); // normalize halfangle vector and store it into TEXTURE_COORD2 glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, R_TEMPV, R_TEMPV); glShaderOp1EXT(GL_OP_RECIP_SQRT_EXT, R_TEMPS, R_TEMPS); glShaderOp2EXT(GL_OP_MUL_EXT, GL_OUTPUT_TEXTURE_COORD2_EXT, R_TEMPV, R_TEMPS); } // assign texture coord for fur texture (unperturbed) glShaderOp1EXT(GL_OP_MOV_EXT, GL_OUTPUT_TEXTURE_COORD3_EXT, V_TEX2); // calculate offset scale factor and store it into TEXTURE_COORD4 { // R_TEMPV = radius / (radius + length) glShaderOp2EXT(GL_OP_ADD_EXT, R_TEMPV, V_RADIUS, I_FUR_LENGTH); glExtractComponentEXT(R_TEMPS, R_TEMPV, 0); glShaderOp1EXT(GL_OP_RECIP_EXT, R_TEMPS, R_TEMPS); glInsertComponentEXT(R_TEMPV, R_TEMPS, 0); glExtractComponentEXT(R_TEMPS, R_TEMPV, 1); glShaderOp1EXT(GL_OP_RECIP_EXT, R_TEMPS, R_TEMPS); glInsertComponentEXT(R_TEMPV, R_TEMPS, 1); glShaderOp2EXT(GL_OP_MUL_EXT, R_TEMPV, R_TEMPV, V_RADIUS); // TEX_COORD2 = R_TEMPV * V_SCALE * I_OFFSET_SCALE glShaderOp2EXT(GL_OP_MUL_EXT, R_TEMPV, R_TEMPV, V_SCALE); glShaderOp2EXT(GL_OP_MUL_EXT, GL_OUTPUT_TEXTURE_COORD4_EXT, R_TEMPV, I_OFFSET_SCALE); } } glEndVertexShaderEXT(); ////////////////////////////////////////////////////////////////////// // fragment shaders for fur // s_fsFur : diffuse and specular // s_fsFur+1 : diffuse only // s_fsFur+2 : specular only s_fsFur = glGenFragmentShadersATI(3); for (int i = 0; i < 3; i++) { glBindFragmentShaderATI(s_fsFur+i); glBeginFragmentShaderATI(); ////////// first pass ////////// // cubemap normalizer could be used for L and H instead of just passing tex coords, // but it would consume more fill rate while quality would not be all that improved. glSampleMapATI(GL_REG_0_ATI, GL_TEXTURE0_ARB, GL_SWIZZLE_STR_ATI); // 0.5*N + 0.5 glPassTexCoordATI(GL_REG_1_ATI, GL_TEXTURE1_ARB, GL_SWIZZLE_STR_ATI); // L glPassTexCoordATI(GL_REG_2_ATI, GL_TEXTURE2_ARB, GL_SWIZZLE_STR_ATI); // H glPassTexCoordATI(GL_REG_3_ATI, GL_TEXTURE3_ARB, GL_SWIZZLE_STR_ATI); // tex_coord glPassTexCoordATI(GL_REG_4_ATI, GL_TEXTURE4_ARB, GL_SWIZZLE_STR_ATI); // offset_scale // REG_2 = texture coord for lighting texture { // REG_2.y = N dot H glColorFragmentOp2ATI(GL_DOT3_ATI, GL_REG_2_ATI, GL_GREEN_BIT_ATI, GL_NONE, GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, GL_REG_2_ATI, GL_NONE, GL_NONE); // REG_2.x = N dot L glColorFragmentOp2ATI(GL_DOT3_ATI, GL_REG_2_ATI, GL_RED_BIT_ATI, GL_NONE, GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, GL_REG_1_ATI, GL_NONE, GL_NONE); // REG_2.z = compressed(L.z) glSetFragmentShaderConstantATI(GL_CON_0_ATI, D3DXVECTOR4(0.5, 0.5, 0.5, 0.0)); glColorFragmentOp3ATI(GL_MAD_ATI, GL_REG_2_ATI, GL_BLUE_BIT_ATI, GL_NONE, GL_REG_1_ATI, GL_BLUE, GL_NONE, GL_CON_0_ATI, GL_NONE, GL_NONE, GL_CON_0_ATI, GL_NONE, GL_NONE); } // REG_1 = texture coord for fur texture { // REG_1 = (Nx*Nx + Ny*Ny + 1) glColorFragmentOp3ATI(GL_DOT2_ADD_ATI, GL_REG_1_ATI, GL_NONE, GL_NONE, GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, GL_ONE, GL_NONE, GL_NONE); // REG_1 = (Nx*Nx + Ny*Ny + 1) * (Nx, Ny, **) // this gives fairly good approximation of the offset map, // and is faster than sampling both the normal map and the offset map. glColorFragmentOp2ATI(GL_MUL_ATI, GL_REG_1_ATI, GL_NONE, GL_NONE, GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, GL_REG_1_ATI, GL_NONE, GL_NONE); // REG_1 = tex_coord + offset_scale * REG_1 glColorFragmentOp3ATI(GL_MAD_ATI, GL_REG_1_ATI, GL_RED_BIT_ATI | GL_GREEN_BIT_ATI, GL_NONE, GL_REG_4_ATI, GL_NONE, GL_NONE, GL_REG_1_ATI, GL_NONE, GL_NONE, GL_REG_3_ATI, GL_NONE, GL_NONE); } ////////// second pass ////////// glSampleMapATI(GL_REG_1_ATI, GL_REG_2_ATI, GL_SWIZZLE_STR_ATI); // diffuse, specular glSampleMapATI(GL_REG_2_ATI, GL_REG_1_ATI, GL_SWIZZLE_STR_ATI); // fur_color, fur_transparency if (i == 0 || i == 1) { // REG_0.rgb = diffuse * fur_color glColorFragmentOp2ATI(GL_MUL_ATI, GL_REG_0_ATI, GL_NONE, GL_NONE, GL_REG_1_ATI, GL_NONE, GL_NONE, GL_REG_2_ATI, GL_NONE, GL_NONE); } if (i == 0 || i == 2) { // REG_0.a = specular * fur_color.r glAlphaFragmentOp2ATI(GL_MUL_ATI, GL_REG_0_ATI, GL_NONE, GL_REG_1_ATI, GL_NONE, GL_NONE, GL_REG_2_ATI, GL_RED, GL_NONE); if (i == 0) { // REG_0.rgb = CON_1 * REG_0.a + REG_0.rgb glSetFragmentShaderConstantATI(GL_CON_1_ATI, D3DXVECTOR4(0.5, 0.5, 0.4, 0.0)); glColorFragmentOp3ATI(GL_MAD_ATI, GL_REG_0_ATI, GL_NONE, GL_SATURATE_BIT_ATI, GL_CON_1_ATI, GL_NONE, GL_NONE, GL_REG_0_ATI, GL_ALPHA, GL_NONE, GL_REG_0_ATI, GL_NONE, GL_NONE); } else { // REG_0.rgb = CON_1 * REG_0.a glSetFragmentShaderConstantATI(GL_CON_1_ATI, D3DXVECTOR4(0.6, 0.6, 0.5, 0.0)); glColorFragmentOp2ATI(GL_MUL_ATI, GL_REG_0_ATI, GL_NONE, GL_SATURATE_BIT_ATI, GL_CON_1_ATI, GL_NONE, GL_NONE, GL_REG_0_ATI, GL_ALPHA, GL_NONE); } } // REG_0.a = fur_transparency glAlphaFragmentOp1ATI(GL_MOV_ATI, GL_REG_0_ATI, GL_SATURATE_BIT_ATI, GL_REG_2_ATI, GL_NONE, GL_NONE); glEndFragmentShaderATI(); } if (glGetError()) return false; return true; } //----------------------------------------------------------------------------- // Menu //----------------------------------------------------------------------------- void Menu(int item) { switch (item) { case MENU_CHANGE_OBJECT: s_CurrentMesh = (s_CurrentMesh + 1) % NUM_MESHES; s_pCurrentMesh = &s_pMeshes[s_CurrentMesh]; break; case MENU_TOGGLE_WIREFRAME: s_Wireframe = !s_Wireframe; break; case MENU_INCREASE_FUR_LAYERS: s_NumLayers++; break; case MENU_DECREASE_FUR_LAYERS: if (s_NumLayers > 0) s_NumLayers--; break; case MENU_INCREASE_FUR_LENGTH: s_FurLength += 0.02; if (s_FurLength > 1.0) s_FurLength = 1.0; break; case MENU_DECREASE_FUR_LENGTH: s_FurLength -= 0.02; if (s_FurLength < 0.2) s_FurLength = 0.2; break; case MENU_TOGGLE_ANISOTROPIC_FILTER: s_UseAnisotropy = !s_UseAnisotropy; s_pFurTexture->SetMaxAnisotropy(s_UseAnisotropy ? 16 : 1); break; case MENU_TOGGLE_BACKFACE_CULLING: s_CullBackface = !s_CullBackface; break; case MENU_TOGGLE_ALPHA_TEST: s_UseAlphaTest = !s_UseAlphaTest; break; case MENU_TOGGLE_LIGHT_ROTATION: s_RotateLight = !s_RotateLight; break; case MENU_TOGGLE_DIFFUSE: s_DrawDiffuse = !s_DrawDiffuse; break; case MENU_TOGGLE_SPECULAR: s_DrawSpecular = !s_DrawSpecular; break; case MENU_TOGGLE_SIMULATION: s_UseSimulation = !s_UseSimulation; s_pNormalMap->ResetInertia(); break; case MENU_TOGGLE_GRAVITY: s_UseGravity = !s_UseGravity; break; case MENU_SHOW_HIDE_NORMAL_MAP: s_ShowNormalMap = !s_ShowNormalMap; break; case MENU_SHOW_HIDE_OFFSET_MAP: s_ShowOffsetMap = !s_ShowOffsetMap; break; case MENU_SHOW_HIDE_INFO: s_ShowInfo = (s_ShowInfo + 1) % (s_ShowFPS ? 3 : 2); break; case MENU_TOGGLE_FULLSCREEN: if (s_FullScreenPossible) { CleanUp(); if (s_FullScreenMode) { glutLeaveGameMode(); } else { glutDestroyWindow(glutGetWindow()); } s_FullScreenMode = !s_FullScreenMode; SetupScreenMode(FULLSCREEN_WIDTH, FULLSCREEN_HEIGHT, s_FullScreenMode); InitInterface(); } break; case MENU_EXIT: exit(0); } glutPostRedisplay(); } //----------------------------------------------------------------------------- // Keyboard //----------------------------------------------------------------------------- void Keyboard(unsigned char key, int x, int y) { switch (key) { case 27: Menu(MENU_EXIT); break; case '\r': Menu(MENU_CHANGE_OBJECT); break; case 'w': Menu(MENU_TOGGLE_WIREFRAME); break; case 'f': Menu(MENU_TOGGLE_ANISOTROPIC_FILTER); break; case 'b': Menu(MENU_TOGGLE_BACKFACE_CULLING); break; case 'a': Menu(MENU_TOGGLE_ALPHA_TEST); break; case 'l': Menu(MENU_TOGGLE_LIGHT_ROTATION); break; case 'd': Menu(MENU_TOGGLE_DIFFUSE); break; case 's': Menu(MENU_TOGGLE_SPECULAR); break; case ' ': Menu(MENU_TOGGLE_SIMULATION); break; case 'g': Menu(MENU_TOGGLE_GRAVITY); break; case 'n': Menu(MENU_SHOW_HIDE_NORMAL_MAP); break; case 'o': Menu(MENU_SHOW_HIDE_OFFSET_MAP); break; case '\t': Menu(MENU_TOGGLE_FULLSCREEN); break; } } //----------------------------------------------------------------------------- // SpecialKey //----------------------------------------------------------------------------- void SpecialKey(int key, int x, int y) { switch (key) { case GLUT_KEY_PAGE_UP: Menu(MENU_INCREASE_FUR_LAYERS); break; case GLUT_KEY_PAGE_DOWN: Menu(MENU_DECREASE_FUR_LAYERS); break; case GLUT_KEY_HOME: Menu(MENU_INCREASE_FUR_LENGTH); break; case GLUT_KEY_END: Menu(MENU_DECREASE_FUR_LENGTH); break; case GLUT_KEY_F1: Menu(MENU_SHOW_HIDE_INFO); break; } } //----------------------------------------------------------------------------- // Mouse //----------------------------------------------------------------------------- void MouseButton(int button, int state, int x, int y) { s_IdleModeTimer = 0.0; // reset demo mode timer s_Mouse.x = x; s_Mouse.y = y; s_PrevMouse = s_Mouse; if (button == GLUT_LEFT_BUTTON) { s_MouseState[0] = (state == GLUT_DOWN); } else if (button == GLUT_RIGHT_BUTTON) { s_MouseState[1] = (state == GLUT_DOWN); } else if (button == GLUT_MIDDLE_BUTTON) { s_MouseState[2] = (state == GLUT_DOWN); } } //----------------------------------------------------------------------------- // MouseMotion //----------------------------------------------------------------------------- void MouseMotion(int x, int y) { s_IdleModeTimer = 0.0; // reset demo mode timer s_Mouse.x = x; s_Mouse.y = y; } //----------------------------------------------------------------------------- // Reshape //----------------------------------------------------------------------------- void Reshape(int w, int h) { s_Width = w; s_Height = h; glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(s_FovY, (double)w/h, s_NearZ, s_FarZ); } //----------------------------------------------------------------------------- // Idle //----------------------------------------------------------------------------- void Idle() { // update timer timerUpdate(); // time step float dt = min(0.1, timerGetDT()); // filtered time step static float dt2 = 0.025; dt2 = 0.9 * dt2 + 0.1 * dt; // update demo mode timer s_IdleModeTimer += dt; // demo mode, change meshes every DEMO_AUTOMATIC_MODE_OBJECT_SWITCH_TIMEOUT seconds // after demo mode has been entered { static prev_count = 0; if (s_IdleModeTimer > DEMO_MANUAL_MODE_TIMEOUT) { int count = int((s_IdleModeTimer - DEMO_MANUAL_MODE_TIMEOUT) / DEMO_AUTOMATIC_MODE_OBJECT_SWITCH_TIMEOUT); if (count != prev_count) { s_CurrentMesh = (s_CurrentMesh + 1) % NUM_MESHES; s_pCurrentMesh = &s_pMeshes[s_CurrentMesh]; prev_count = count; } } else { prev_count = 0; } } // update mouse tracking { D3DXVECTOR3 tempVec(0, 0, 0); D3DXQUATERNION tempQuat(0, 0, 0, 1); short shift = GetAsyncKeyState(VK_SHIFT); short ctrl = GetAsyncKeyState(VK_CONTROL); if (s_MouseState[2] || s_MouseState[0] && shift) // translate { tempVec.x = 5 * (s_Mouse.x - s_PrevMouse.x) / s_Height; tempVec.y = -5 * (s_Mouse.y - s_PrevMouse.y) / s_Height; } else if (s_MouseState[0] && ctrl) // zoom { s_Trans.z += 10 * (s_Mouse.y - s_PrevMouse.y) / s_Height; s_Trans.z = max(-s_FarZ, min(-s_NearZ, s_Trans.z)); } else if (s_MouseState[0]) // rotate { D3DXVECTOR2 pt1(float(2*s_PrevMouse.x)/s_Width - 1, 1 - float(2*s_PrevMouse.y)/s_Height); D3DXVECTOR2 pt2(float(2*s_Mouse.x) /s_Width - 1, 1 - float(2*s_Mouse.y) /s_Height); trackball(&tempQuat, &pt1, &pt2); } s_PrevMouse = s_Mouse; // demo mode, rotate object randomly when manual mode times out if (s_IdleModeTimer > DEMO_MANUAL_MODE_TIMEOUT) { static D3DXVECTOR3 randomAxis(0, 1, 0); static float randomSpeed = 0.0; // change rotation randomly if (rand() < 0.5 * dt * RAND_MAX) { if (rand() < 0.5 * RAND_MAX) { // reversal of motion randomAxis *= -1; } else { // change axis of rotation randomAxis.x = -1.0 + 2.0 * rand() / RAND_MAX; randomAxis.y = -1.0 + 2.0 * rand() / RAND_MAX; randomAxis.z = -1.0 + 2.0 * rand() / RAND_MAX; D3DXVec3Normalize(&randomAxis, &randomAxis); // change rotation speed (rad/sec) randomSpeed = (0.5 + 0.5 * rand() / RAND_MAX) * D3DX_PI; } } D3DXQuaternionRotationAxis(&tempQuat, &randomAxis, randomSpeed * dt); } float damping; if (s_IdleModeTimer > DEMO_MANUAL_MODE_TIMEOUT) { // demo mode, smooth out abrupt motions damping = min(1.0, dt/0.8); } else { // mouse mode damping = min(1.0, dt/0.125); } // update modelview traslation and rotation using damping factor s_TransDelta = (1 - damping) * s_TransDelta + damping * tempVec; s_Trans += s_TransDelta; D3DXQuaternionSlerp(&s_RotDelta, &s_RotDelta, &tempQuat, damping); D3DXQuaternionNormalize(&s_RotDelta, &s_RotDelta); D3DXQuaternionNormalize(&s_Rot, &(s_Rot * s_RotDelta)); } // calculate modelview matrix { D3DXMATRIX rotation, translation; D3DXMatrixRotationQuaternion(&rotation, &s_Rot); D3DXMatrixTranslation(&translation, s_Trans.x, s_Trans.y, s_Trans.z); D3DXMatrixMultiply(&s_ModelView, &rotation, &translation); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(s_ModelView); // inverse of modelview matrix D3DXMatrixInverse(&s_ModelViewInv, NULL, &s_ModelView); // camera position glSetInvariantEXT(I_CAMERA_POS, GL_FLOAT, &s_ModelViewInv._41); } // calculate light direction in model space { static float angle1 = 0.5 * D3DX_PI; static float angle2 = 0.2 * D3DX_PI; if (s_RotateLight) { angle1 += 1.00 * dt; angle2 += 0.35 * dt; } D3DXVECTOR4 dir(sin(angle1), sin(angle2), cos(angle1), 0); D3DXVec4Normalize(&dir, D3DXVec4Transform(&dir, &dir, &s_ModelViewInv)); glSetInvariantEXT(I_LIGHT_DIR, GL_FLOAT, &dir); } // update normal map if (!s_Wireframe && s_UseSimulation) { // gravity in model space D3DXVECTOR4 gravity(0, s_UseGravity ? -0.4 : -0.02, 0, 0); D3DXVec4Transform(&gravity, &gravity, &s_ModelViewInv); // translational velocity in model space D3DXVECTOR4 velocity; velocity.x = s_TransDelta.x / dt2; velocity.y = s_TransDelta.y / dt2; velocity.z = s_TransDelta.z / dt2; velocity.w = 0; D3DXVec4Transform(&velocity, &velocity, &s_ModelViewInv); // angular velocity in model space D3DXVECTOR4 omega; D3DXQuaternionLn((D3DXQUATERNION*)&omega, &s_RotDelta); D3DXVec4Transform(&omega, &(2*omega/dt2), &s_ModelViewInv); s_pNormalMap->Update(s_pCurrentMesh, dt2, gravity, velocity, omega); } glutPostRedisplay(); } //----------------------------------------------------------------------------- // Display //----------------------------------------------------------------------------- void Display() { glClearColor(0.4, 0.4, 0.4, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (s_ShowNormalMap) { glEnable(GL_TEXTURE_2D); s_pNormalMap->Bind(); // upper right of screen ShowTexture(s_Width - 128, s_Height - 128, 128, 128); glDisable(GL_TEXTURE_2D); } if (s_ShowOffsetMap) { glEnable(GL_TEXTURE_2D); s_pNormalMap->BindOffsetMap(); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // lower left of screen ShowTexture(s_Width - 128, 0, 128, 128); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glDisable(GL_TEXTURE_2D); } RenderFurMesh(); ShowInfo(); glutSwapBuffers(); } //----------------------------------------------------------------------------- // RenderFurMesh //----------------------------------------------------------------------------- void RenderFurMesh() { if (s_Wireframe) { glPushAttrib(GL_ENABLE_BIT); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glEnable(GL_LINE_SMOOTH); glLineWidth(0.9); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor3f(0, 0, 0); s_pCurrentMesh->Draw(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPopAttrib(); return; } glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT); glEnable(GL_DEPTH_TEST); // draw skin { glEnable(GL_CULL_FACE); glEnable(GL_VERTEX_SHADER_EXT); glBindVertexShaderEXT(s_vsSkin); s_pCurrentMesh->Draw(); } // draw fur if (!s_Wireframe && s_NumLayers > 0 && (s_DrawDiffuse || s_DrawSpecular)) { s_CullBackface ? glEnable(GL_CULL_FACE) : glDisable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (s_UseAlphaTest) { glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.1); } // vertex shader glEnable(GL_VERTEX_SHADER_EXT); glBindVertexShaderEXT(s_vsFur); // fragment shader glEnable(GL_FRAGMENT_SHADER_ATI); glBindFragmentShaderATI(s_DrawDiffuse ? (s_DrawSpecular ? s_fsFur : s_fsFur+1) : s_fsFur+2); // normal map glActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); s_pNormalMap->Bind(); // lighting texture glActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_3D_EXT); glBindTexture(GL_TEXTURE_3D_EXT, s_texLighting); // fur texture glActiveTextureARB(GL_TEXTURE2_ARB); glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, 0.5); // draw fur shells for (int i = 0; i < s_NumLayers; i++) { float layer = float(i+1) / s_NumLayers; float length = s_FurLength * layer; float scale = -s_FurLength * (1.0*layer*layer + 0.4*layer); s_pFurTexture->Bind(layer); glSetInvariantEXT(I_FUR_LENGTH, GL_FLOAT, &length); glSetInvariantEXT(I_OFFSET_SCALE, GL_FLOAT, &scale); s_pCurrentMesh->Draw(); } glActiveTextureARB(GL_TEXTURE0_ARB); } glPopAttrib(); } //----------------------------------------------------------------------------- // ShowTexture //----------------------------------------------------------------------------- void ShowTexture(int x, int y, int w, int h) { glPushAttrib(GL_VIEWPORT_BIT); glViewport(x, y, w, h); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, 1, 0, 1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0, 0); glVertex2f(0, 0); glTexCoord2f(1, 0); glVertex2f(1, 0); glTexCoord2f(0, 1); glVertex2f(0, 1); glTexCoord2f(1, 1); glVertex2f(1, 1); glEnd(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); } //----------------------------------------------------------------------------- // ShowInfo //----------------------------------------------------------------------------- void ShowInfo() { textBegin(s_Width, s_Height); if (s_ShowInfo == 1) { #define ONOFF(x) ((x) ? "ON" : "OFF") textSetPosition(4, 4); if (s_ShowFPS) { textSetColor(1, 1, 0); textPrintf("%.1f fps (%dx%d)\n", timerGetFPS(), s_Width, s_Height); textLineFeed(5); } textSetColor(1, 1, 1); textPrintf("Object: %s\n", s_pCurrentMesh->GetName()); textPrintf("Fur Layers: %d\n", s_NumLayers); textPrintf("Fur Length: %.2f\n", s_FurLength); textLineFeed(5); textPrintf("Anisotropic Filter: %s\n", ONOFF(s_UseAnisotropy)); textPrintf("Backface Culling: %s\n", ONOFF(s_CullBackface)); textPrintf("Alpha Test: %s\n", ONOFF(s_UseAlphaTest)); textLineFeed(5); textPrintf("Diffuse: %s\n", ONOFF(s_DrawDiffuse)); textPrintf("Specular: %s\n", ONOFF(s_DrawSpecular)); textLineFeed(5); textPrintf("Simulation: %s\n", ONOFF(s_UseSimulation)); textPrintf("Gravity: %s\n", ONOFF(s_UseGravity)); textSetPosition(4, s_Height-83); textPrint("Mouse Controls\n"); textPrint("Left: Rotate\n"); textPrint("Shift+Left: Translate\n"); textPrint("Crtl+Left: Zoom\n"); if (!s_FullScreenMode) { textPrint("Right: Menu"); } } else if (s_ShowInfo == 2) { textSetPosition(4, 4); textSetColor(1, 1, 0); textPrintf("%.1f fps\n", timerGetFPS()); } textSetPosition(s_Width-246, s_Height-35); textSetColor(1, 1, 1); textPrint("Copyright \251 2002 Tomohide Kano"); textSetPosition(s_Width-470, s_Height-19); textSetColor(1, 1, 1); textPrint("Demo source code available at http://www.ati.com/developer"); textEnd(); }