| /****************************************************************************** | |
| @File OGLES2ChameleonMan.cpp | |
| @Title OGLES2ChameleonMan | |
| @Version | |
| @Copyright Copyright (c) Imagination Technologies Limited. | |
| @Platform Independent | |
| @Description Shows how to perform skinning combined with Dot3 lighting | |
| ******************************************************************************/ | |
| #include "PVRShell.h" | |
| #include "OGLES2Tools.h" | |
| /****************************************************************************** | |
| Constants | |
| ******************************************************************************/ | |
| // Camera constants used to generate the projection matrix | |
| const float g_fCameraNear = 4.0f; | |
| const float g_fCameraFar = 30000.0f; | |
| const float g_fDemoFrameRate = 0.02f; | |
| /****************************************************************************** | |
| shader attributes | |
| ******************************************************************************/ | |
| // Skinned | |
| // vertex attributes | |
| enum EVertexAttrib { | |
| VERTEX_ARRAY, NORMAL_ARRAY, TANGENT_ARRAY, BINORMAL_ARRAY, TEXCOORD_ARRAY, BONEWEIGHT_ARRAY, BONEINDEX_ARRAY, eNumAttribs }; | |
| const char* g_aszAttribNames[] = { | |
| "inVertex", "inNormal", "inTangent", "inBiNormal", "inTexCoord", "inBoneWeight", "inBoneIndex" }; | |
| // shader uniforms | |
| enum ESkinnnedUniform { | |
| eViewProj, eLightPos, eBoneCount, eBoneMatrices, eBoneMatricesIT, ebUseDot3, eNumSkinnedUniforms }; | |
| const char* g_aszSkinnedUniformNames[] = { | |
| "ViewProjMatrix", "LightPos", "BoneCount", "BoneMatrixArray[0]", "BoneMatrixArrayIT[0]", "bUseDot3" }; | |
| // Default | |
| // vertex attributes | |
| enum EDefaultVertexAttrib { | |
| DEFAULT_VERTEX_ARRAY, DEFAULT_TEXCOORD_ARRAY, eNumDefaultAttribs }; | |
| const char* g_aszDefaultAttribNames[] = { | |
| "inVertex", "inTexCoord"}; | |
| // shader uniforms | |
| enum EDefaultUniform { | |
| eDefaultMVPMatrix, eDefaultUOffset, eNumDefaultUniforms }; | |
| const char* g_aszDefaultUniformNames[] = { | |
| "MVPMatrix", "fUOffset" }; | |
| /****************************************************************************** | |
| Content file names | |
| ******************************************************************************/ | |
| // Source and binary shaders | |
| const char c_szSkinnedFragShaderSrcFile[] = "SkinnedFragShader.fsh"; | |
| const char c_szSkinnedFragShaderBinFile[] = "SkinnedFragShader.fsc"; | |
| const char c_szSkinnedVertShaderSrcFile[] = "SkinnedVertShader.vsh"; | |
| const char c_szSkinnedVertShaderBinFile[] = "SkinnedVertShader.vsc"; | |
| const char c_szDefaultFragShaderSrcFile[] = "DefaultFragShader.fsh"; | |
| const char c_szDefaultFragShaderBinFile[] = "DefaultFragShader.fsc"; | |
| const char c_szDefaultVertShaderSrcFile[] = "DefaultVertShader.vsh"; | |
| const char c_szDefaultVertShaderBinFile[] = "DefaultVertShader.vsc"; | |
| // Base Textures | |
| const char c_szFinalChameleonManHeadBodyTexFile[] = "FinalChameleonManHeadBody.pvr"; | |
| const char c_szFinalChameleonManLegsTexFile[] = "FinalChameleonManLegs.pvr"; | |
| const char c_szLampTexFile[] = "lamp.pvr"; | |
| const char c_szChameleonBeltTexFile[] = "ChameleonBelt.pvr"; | |
| const char c_szSkylineTexFile[] = "skyline.pvr"; | |
| const char c_szWallDiffuseBakedTexFile[] = "Wall_diffuse_baked.pvr"; | |
| // Tangent Space BumpMap Textures | |
| const char c_szTang_space_BodyMapTexFile[] = "Tang_space_BodyMap.pvr"; | |
| const char c_szTang_space_LegsMapTexFile[] = "Tang_space_LegsMap.pvr"; | |
| const char c_szTang_space_BeltMapTexFile[] = "Tang_space_BeltMap.pvr"; | |
| // POD scene files | |
| const char c_szSceneFile[] = "ChameleonScene.pod"; | |
| /**************************************************************************** | |
| ** Enums ** | |
| ****************************************************************************/ | |
| enum EMeshes | |
| { | |
| eBody, | |
| eLegs, | |
| eBelt, | |
| eWall, | |
| eBackground, | |
| eLights | |
| }; | |
| /**************************************************************************** | |
| ** Structures | |
| ****************************************************************************/ | |
| /*!**************************************************************************** | |
| Class implementing the PVRShell functions. | |
| ******************************************************************************/ | |
| class OGLES2ChameleonMan : public PVRShell | |
| { | |
| // Print3D class used to display text | |
| CPVRTPrint3D m_Print3D; | |
| // 3D Model | |
| CPVRTModelPOD m_Scene; | |
| // Model transformation variables | |
| float m_fWallPos; | |
| float m_fBackgroundPos; | |
| float m_fLightPos; | |
| // OpenGL handles for shaders and VBOs | |
| GLuint m_uiSkinnedVertShader; | |
| GLuint m_uiDefaultVertShader; | |
| GLuint m_uiSkinnedFragShader; | |
| GLuint m_uiDefaultFragShader; | |
| GLuint* m_puiVbo; | |
| GLuint* m_puiIndexVbo; | |
| // Texture IDs | |
| GLuint m_ui32TexHeadBody; | |
| GLuint m_ui32TexLegs; | |
| GLuint m_ui32TexBeltNormalMap; | |
| GLuint m_ui32TexHeadNormalMap; | |
| GLuint m_ui32TexLegsNormalMap; | |
| GLuint m_ui32TexSkyLine; | |
| GLuint m_ui32TexWall; | |
| GLuint m_ui32TexLamp; | |
| GLuint m_ui32TexBelt; | |
| // Group shader programs and their uniform locations together | |
| struct | |
| { | |
| GLuint uiId; | |
| GLuint auiLoc[eNumSkinnedUniforms]; | |
| } | |
| m_SkinnedShaderProgram; | |
| struct | |
| { | |
| GLuint uiId; | |
| GLuint auiLoc[eNumDefaultUniforms]; | |
| } | |
| m_DefaultShaderProgram; | |
| bool m_bEnableDOT3; | |
| // Variables to handle the animation in a time-based manner | |
| unsigned long m_iTimePrev; | |
| float m_fFrame; | |
| public: | |
| OGLES2ChameleonMan() : m_fWallPos(0), | |
| m_fBackgroundPos(0), | |
| m_fLightPos(0), | |
| m_puiVbo(0), | |
| m_puiIndexVbo(0), | |
| m_bEnableDOT3(true), | |
| m_iTimePrev(0), | |
| m_fFrame(0) | |
| { | |
| } | |
| virtual bool InitApplication(); | |
| virtual bool InitView(); | |
| virtual bool ReleaseView(); | |
| virtual bool QuitApplication(); | |
| virtual bool RenderScene(); | |
| bool LoadTextures(CPVRTString* pErrorStr); | |
| bool LoadShaders(CPVRTString* pErrorStr); | |
| void LoadVbos(); | |
| void DrawSkinnedMesh(int i32NodeIndex); | |
| }; | |
| /*!**************************************************************************** | |
| @Function LoadTextures | |
| @Output pErrorStr A string describing the error on failure | |
| @Return bool true if no error occured | |
| @Description Loads the textures required for this training course | |
| ******************************************************************************/ | |
| bool OGLES2ChameleonMan::LoadTextures(CPVRTString* const pErrorStr) | |
| { | |
| // Load Textures | |
| if(PVRTTextureLoadFromPVR(c_szFinalChameleonManHeadBodyTexFile, &m_ui32TexHeadBody) != PVR_SUCCESS) | |
| { | |
| *pErrorStr = CPVRTString("ERROR: Failed to load texture for Upper Body.\n"); | |
| return false; | |
| } | |
| if(PVRTTextureLoadFromPVR(c_szFinalChameleonManLegsTexFile, &m_ui32TexLegs) != PVR_SUCCESS) | |
| { | |
| *pErrorStr = CPVRTString("ERROR: Failed to load texture for Legs.\n"); | |
| return false; | |
| } | |
| if(PVRTTextureLoadFromPVR(c_szTang_space_BodyMapTexFile, &m_ui32TexHeadNormalMap) != PVR_SUCCESS) | |
| { | |
| *pErrorStr = CPVRTString("ERROR: Failed to load normalmap texture for Upper Body.\n"); | |
| return false; | |
| } | |
| if(PVRTTextureLoadFromPVR(c_szTang_space_LegsMapTexFile, &m_ui32TexLegsNormalMap) != PVR_SUCCESS) | |
| { | |
| *pErrorStr = CPVRTString("ERROR: Failed to load normalmap texture for Legs.\n"); | |
| return false; | |
| } | |
| if(PVRTTextureLoadFromPVR(c_szTang_space_BeltMapTexFile, &m_ui32TexBeltNormalMap) != PVR_SUCCESS) | |
| { | |
| *pErrorStr = CPVRTString("ERROR: Failed to load normalmap texture for Belt.\n"); | |
| return false; | |
| } | |
| if(PVRTTextureLoadFromPVR(c_szSkylineTexFile, &m_ui32TexSkyLine) != PVR_SUCCESS) | |
| { | |
| *pErrorStr = CPVRTString("ERROR: Failed to load texture for SkyLine.\n"); | |
| return false; | |
| } | |
| if(PVRTTextureLoadFromPVR(c_szWallDiffuseBakedTexFile, &m_ui32TexWall) != PVR_SUCCESS) | |
| { | |
| *pErrorStr = CPVRTString("ERROR: Failed to load texture for Wall.\n"); | |
| return false; | |
| } | |
| if(PVRTTextureLoadFromPVR(c_szLampTexFile, &m_ui32TexLamp) != PVR_SUCCESS) | |
| { | |
| *pErrorStr = CPVRTString("ERROR: Failed to load texture for Lamps.\n"); | |
| return false; | |
| } | |
| if(PVRTTextureLoadFromPVR(c_szChameleonBeltTexFile, &m_ui32TexBelt) != PVR_SUCCESS) | |
| { | |
| *pErrorStr = CPVRTString("ERROR: Failed to load texture for Belt.\n"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /*!**************************************************************************** | |
| @Function LoadShaders | |
| @Output pErrorStr A string describing the error on failure | |
| @Return bool true if no error occured | |
| @Description Loads and compiles the shaders and links the shader programs | |
| required for this training course | |
| ******************************************************************************/ | |
| bool OGLES2ChameleonMan::LoadShaders(CPVRTString* pErrorStr) | |
| { | |
| int i; | |
| /* | |
| Load and compile the shaders from files. | |
| Binary shaders are tried first, source shaders | |
| are used as fallback. | |
| */ | |
| // Create the skinned program | |
| if(PVRTShaderLoadFromFile( | |
| c_szSkinnedVertShaderBinFile, c_szSkinnedVertShaderSrcFile, GL_VERTEX_SHADER, GL_SGX_BINARY_IMG, &m_uiSkinnedVertShader, pErrorStr) != PVR_SUCCESS) | |
| { | |
| return false; | |
| } | |
| if(PVRTShaderLoadFromFile( | |
| c_szSkinnedFragShaderBinFile, c_szSkinnedFragShaderSrcFile, GL_FRAGMENT_SHADER, GL_SGX_BINARY_IMG, &m_uiSkinnedFragShader, pErrorStr) != PVR_SUCCESS) | |
| { | |
| return false; | |
| } | |
| if(PVRTCreateProgram(&m_SkinnedShaderProgram.uiId, m_uiSkinnedVertShader, m_uiSkinnedFragShader, g_aszAttribNames, eNumAttribs, pErrorStr) != PVR_SUCCESS) | |
| { | |
| PVRShellSet(prefExitMessage, pErrorStr->c_str()); | |
| return false; | |
| } | |
| // Store the location of uniforms for later use | |
| for(i = 0; i < eNumSkinnedUniforms; ++i) | |
| { | |
| m_SkinnedShaderProgram.auiLoc[i] = glGetUniformLocation(m_SkinnedShaderProgram.uiId, g_aszSkinnedUniformNames[i]); | |
| } | |
| glUniform1i(m_SkinnedShaderProgram.auiLoc[ebUseDot3], m_bEnableDOT3); | |
| // Set the sampler2D uniforms to corresponding texture units | |
| glUniform1i(glGetUniformLocation(m_SkinnedShaderProgram.uiId, "sTexture"), 0); | |
| glUniform1i(glGetUniformLocation(m_SkinnedShaderProgram.uiId, "sNormalMap"), 1); | |
| // Create the non-skinned program | |
| if(PVRTShaderLoadFromFile( | |
| c_szDefaultVertShaderBinFile, c_szDefaultVertShaderSrcFile, GL_VERTEX_SHADER, GL_SGX_BINARY_IMG, &m_uiDefaultVertShader, pErrorStr) != PVR_SUCCESS) | |
| { | |
| return false; | |
| } | |
| if(PVRTShaderLoadFromFile( | |
| c_szDefaultFragShaderBinFile, c_szDefaultFragShaderSrcFile, GL_FRAGMENT_SHADER, GL_SGX_BINARY_IMG, &m_uiDefaultFragShader, pErrorStr) != PVR_SUCCESS) | |
| { | |
| return false; | |
| } | |
| if(PVRTCreateProgram(&m_DefaultShaderProgram.uiId, m_uiDefaultVertShader, m_uiDefaultFragShader, g_aszDefaultAttribNames, eNumDefaultAttribs, pErrorStr) != PVR_SUCCESS) | |
| { | |
| PVRShellSet(prefExitMessage, pErrorStr->c_str()); | |
| return false; | |
| } | |
| // Store the location of uniforms for later use | |
| for(i = 0; i < eNumDefaultUniforms; ++i) | |
| { | |
| m_DefaultShaderProgram.auiLoc[i] = glGetUniformLocation(m_DefaultShaderProgram.uiId, g_aszDefaultUniformNames[i]); | |
| } | |
| // Set the sampler2D uniforms to corresponding texture units | |
| glUniform1i(glGetUniformLocation(m_DefaultShaderProgram.uiId, "sTexture"), 0); | |
| return true; | |
| } | |
| /*!**************************************************************************** | |
| @Function LoadVbos | |
| @Description Loads the mesh data required for this training course into | |
| vertex buffer objects | |
| ******************************************************************************/ | |
| void OGLES2ChameleonMan::LoadVbos() | |
| { | |
| if (!m_puiVbo) m_puiVbo = new GLuint[m_Scene.nNumMesh]; | |
| if (!m_puiIndexVbo) m_puiIndexVbo = new GLuint[m_Scene.nNumMesh]; | |
| /* | |
| Load vertex data of all meshes in the scene into VBOs | |
| The meshes have been exported with the "Interleave Vectors" option, | |
| so all data is interleaved in the buffer at pMesh->pInterleaved. | |
| Interleaving data improves the memory access pattern and cache efficiency, | |
| thus it can be read faster by the hardware. | |
| */ | |
| glGenBuffers(m_Scene.nNumMesh, m_puiVbo); | |
| for (unsigned int i = 0; i < m_Scene.nNumMesh; ++i) | |
| { | |
| // Load vertex data into buffer object | |
| SPODMesh& Mesh = m_Scene.pMesh[i]; | |
| unsigned int uiSize = Mesh.nNumVertex * Mesh.sVertex.nStride; | |
| glBindBuffer(GL_ARRAY_BUFFER, m_puiVbo[i]); | |
| glBufferData(GL_ARRAY_BUFFER, uiSize, Mesh.pInterleaved, GL_STATIC_DRAW); | |
| // Load index data into buffer object if available | |
| m_puiIndexVbo[i] = 0; | |
| if (Mesh.sFaces.pData) | |
| { | |
| glGenBuffers(1, &m_puiIndexVbo[i]); | |
| uiSize = PVRTModelPODCountIndices(Mesh) * sizeof(GLshort); | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_puiIndexVbo[i]); | |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, uiSize, Mesh.sFaces.pData, GL_STATIC_DRAW); | |
| } | |
| } | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); | |
| } | |
| /*!**************************************************************************** | |
| @Function InitApplication | |
| @Return bool true if no error occured | |
| @Description Code in InitApplication() will be called by PVRShell once per | |
| run, before the rendering context is created. | |
| Used to initialize variables that are not dependant on it | |
| (e.g. external modules, loading meshes, etc.) | |
| If the rendering context is lost, InitApplication() will | |
| not be called again. | |
| ******************************************************************************/ | |
| bool OGLES2ChameleonMan::InitApplication() | |
| { | |
| // Get and set the read path for content files | |
| CPVRTResourceFile::SetReadPath((char*)PVRShellGet(prefReadPath)); | |
| // Get and set the load/release functions for loading external files. | |
| // In the majority of cases the PVRShell will return NULL function pointers implying that | |
| // nothing special is required to load external files. | |
| CPVRTResourceFile::SetLoadReleaseFunctions(PVRShellGet(prefLoadFileFunc), PVRShellGet(prefReleaseFileFunc)); | |
| // Load the scene | |
| if (m_Scene.ReadFromFile(c_szSceneFile) != PVR_SUCCESS) | |
| { | |
| PVRShellSet(prefExitMessage, "ERROR: Couldn't load the .pod file\n"); | |
| return false; | |
| } | |
| // The cameras are stored in the file. We check it contains at least one. | |
| if (m_Scene.nNumCamera == 0) | |
| { | |
| PVRShellSet(prefExitMessage, "ERROR: The scene does not contain a camera\n"); | |
| return false; | |
| } | |
| // Check the scene contains at least one light | |
| if (m_Scene.nNumLight == 0) | |
| { | |
| PVRShellSet(prefExitMessage, "ERROR: The scene does not contain a light\n"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /*!**************************************************************************** | |
| @Function QuitApplication | |
| @Return bool true if no error occured | |
| @Description Code in QuitApplication() will be called by PVRShell once per | |
| run, just before exiting the program. | |
| If the rendering context is lost, QuitApplication() will | |
| not be called. | |
| ******************************************************************************/ | |
| bool OGLES2ChameleonMan::QuitApplication() | |
| { | |
| // Free the memory allocated for the scene | |
| m_Scene.Destroy(); | |
| delete [] m_puiVbo; | |
| delete [] m_puiIndexVbo; | |
| return true; | |
| } | |
| /*!**************************************************************************** | |
| @Function InitView | |
| @Return bool true if no error occured | |
| @Description Code in InitView() will be called by PVRShell upon | |
| initialization or after a change in the rendering context. | |
| Used to initialize variables that are dependant on the rendering | |
| context (e.g. textures, vertex buffers, etc.) | |
| ******************************************************************************/ | |
| bool OGLES2ChameleonMan::InitView() | |
| { | |
| CPVRTString ErrorStr; | |
| /* | |
| Initialize VBO data | |
| */ | |
| LoadVbos(); | |
| /* | |
| Load textures | |
| */ | |
| if (!LoadTextures(&ErrorStr)) | |
| { | |
| PVRShellSet(prefExitMessage, ErrorStr.c_str()); | |
| return false; | |
| } | |
| /* | |
| Load and compile the shaders & link programs | |
| */ | |
| if (!LoadShaders(&ErrorStr)) | |
| { | |
| PVRShellSet(prefExitMessage, ErrorStr.c_str()); | |
| return false; | |
| } | |
| /* | |
| Initialize Print3D | |
| */ | |
| // Is the screen rotated? | |
| bool bRotate = PVRShellGet(prefIsRotated) && PVRShellGet(prefFullScreen); | |
| if(m_Print3D.SetTextures(0,PVRShellGet(prefWidth),PVRShellGet(prefHeight), bRotate) != PVR_SUCCESS) | |
| { | |
| PVRShellSet(prefExitMessage, "ERROR: Cannot initialise Print3D\n"); | |
| return false; | |
| } | |
| /* | |
| Set OpenGL ES render states needed for this training course | |
| */ | |
| // Enable backface culling and depth test | |
| glCullFace(GL_BACK); | |
| glEnable(GL_CULL_FACE); | |
| glEnable(GL_DEPTH_TEST); | |
| // Use black as our clear colour | |
| glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
| // Initialise variables used for the animation | |
| m_iTimePrev = PVRShellGetTime(); | |
| return true; | |
| } | |
| /*!**************************************************************************** | |
| @Function ReleaseView | |
| @Return bool true if no error occured | |
| @Description Code in ReleaseView() will be called by PVRShell when the | |
| application quits or before a change in the rendering context. | |
| ******************************************************************************/ | |
| bool OGLES2ChameleonMan::ReleaseView() | |
| { | |
| // Delete textures | |
| glDeleteTextures(1, &m_ui32TexLegs); | |
| glDeleteTextures(1, &m_ui32TexBeltNormalMap); | |
| glDeleteTextures(1, &m_ui32TexHeadNormalMap); | |
| glDeleteTextures(1, &m_ui32TexLegsNormalMap); | |
| glDeleteTextures(1, &m_ui32TexSkyLine); | |
| glDeleteTextures(1, &m_ui32TexWall); | |
| glDeleteTextures(1, &m_ui32TexLamp); | |
| glDeleteTextures(1, &m_ui32TexBelt); | |
| // Delete program and shader objects | |
| glDeleteProgram(m_SkinnedShaderProgram.uiId); | |
| glDeleteProgram(m_DefaultShaderProgram.uiId); | |
| glDeleteShader(m_uiSkinnedVertShader); | |
| glDeleteShader(m_uiDefaultVertShader); | |
| glDeleteShader(m_uiSkinnedFragShader); | |
| glDeleteShader(m_uiDefaultFragShader); | |
| // Delete buffer objects | |
| glDeleteBuffers(m_Scene.nNumMesh, m_puiVbo); | |
| glDeleteBuffers(m_Scene.nNumMesh, m_puiIndexVbo); | |
| // Release Print3D Textures | |
| m_Print3D.ReleaseTextures(); | |
| return true; | |
| } | |
| /*!**************************************************************************** | |
| @Function RenderScene | |
| @Return bool true if no error occured | |
| @Description Main rendering loop function of the program. The shell will | |
| call this function every frame. | |
| eglSwapBuffers() will be performed by PVRShell automatically. | |
| PVRShell will also manage important OS events. | |
| Will also manage relevent OS events. The user has access to | |
| these events through an abstraction layer provided by PVRShell. | |
| ******************************************************************************/ | |
| bool OGLES2ChameleonMan::RenderScene() | |
| { | |
| // Clear the color and depth buffer | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| // Use shader program | |
| glUseProgram(m_SkinnedShaderProgram.uiId); | |
| if(PVRShellIsKeyPressed(PVRShellKeyNameACTION1)) | |
| { | |
| m_bEnableDOT3 = !m_bEnableDOT3; | |
| glUniform1i(m_SkinnedShaderProgram.auiLoc[ebUseDot3], m_bEnableDOT3); | |
| } | |
| /* | |
| Calculates the frame number to animate in a time-based manner. | |
| Uses the shell function PVRShellGetTime() to get the time in milliseconds. | |
| */ | |
| unsigned long iTime = PVRShellGetTime(); | |
| if(iTime > m_iTimePrev) | |
| { | |
| float fDelta = (float) (iTime - m_iTimePrev); | |
| m_fFrame += fDelta * g_fDemoFrameRate; | |
| // Increment the counters to make sure our animation works | |
| m_fLightPos += fDelta * 0.0034f; | |
| m_fWallPos += fDelta * 0.00027f; | |
| m_fBackgroundPos += fDelta * -0.000027f; | |
| // Wrap the Animation back to the Start | |
| if(m_fLightPos >= PVRT_TWO_PI) | |
| m_fLightPos -= PVRT_TWO_PI; | |
| if(m_fWallPos >= PVRT_TWO_PI) | |
| m_fWallPos -= PVRT_TWO_PI; | |
| if(m_fBackgroundPos <= 0) | |
| m_fBackgroundPos += 1.0f; | |
| if(m_fFrame > m_Scene.nNumFrame - 1) | |
| m_fFrame = 0; | |
| } | |
| m_iTimePrev = iTime; | |
| // Set the scene animation to the current frame | |
| m_Scene.SetFrame(m_fFrame); | |
| // Set up camera | |
| PVRTVec3 vFrom, vTo, vUp(0.0f, 1.0f, 0.0f); | |
| PVRTMat4 mView, mProjection; | |
| PVRTVec3 LightPos; | |
| float fFOV; | |
| int i; | |
| bool bRotate = PVRShellGet(prefIsRotated) && PVRShellGet(prefFullScreen); | |
| // Get the camera position, target and field of view (fov) | |
| if(m_Scene.pCamera[0].nIdxTarget != -1) // Does the camera have a target? | |
| fFOV = m_Scene.GetCameraPos( vFrom, vTo, 0); // vTo is taken from the target node | |
| else | |
| fFOV = m_Scene.GetCamera( vFrom, vTo, vUp, 0); // vTo is calculated from the rotation | |
| fFOV *= bRotate ? (float)PVRShellGet(prefWidth)/(float)PVRShellGet(prefHeight) : (float)PVRShellGet(prefHeight)/(float)PVRShellGet(prefWidth); | |
| /* | |
| We can build the model view matrix from the camera position, target and an up vector. | |
| For this we use PVRTMat4::LookAtRH(). | |
| */ | |
| mView = PVRTMat4::LookAtRH(vFrom, vTo, vUp); | |
| // Calculate the projection matrix | |
| mProjection = PVRTMat4::PerspectiveFovRH(fFOV, (float)PVRShellGet(prefWidth)/(float)PVRShellGet(prefHeight), g_fCameraNear, g_fCameraFar, PVRTMat4::OGL, bRotate); | |
| // Update Light Position and related VGP Program constant | |
| LightPos.x = 200.0f; | |
| LightPos.y = 350.0f; | |
| LightPos.z = 200.0f * PVRTABS(sin((PVRT_PI / 4.0f) + m_fLightPos)); | |
| glUniform3fv(m_SkinnedShaderProgram.auiLoc[eLightPos], 1, LightPos.ptr()); | |
| // Set up the View * Projection Matrix | |
| PVRTMat4 mViewProjection; | |
| mViewProjection = mProjection * mView; | |
| glUniformMatrix4fv(m_SkinnedShaderProgram.auiLoc[eViewProj], 1, GL_FALSE, mViewProjection.ptr()); | |
| // Enable the vertex attribute arrays | |
| for(i = 0; i < eNumAttribs; ++i) glEnableVertexAttribArray(i); | |
| // Draw skinned meshes | |
| for(unsigned int i32NodeIndex = 0; i32NodeIndex < 3; ++i32NodeIndex) | |
| { | |
| // Bind correct texture | |
| switch(i32NodeIndex) | |
| { | |
| case eBody: | |
| glActiveTexture(GL_TEXTURE1); | |
| glBindTexture(GL_TEXTURE_2D, m_ui32TexHeadNormalMap); | |
| glActiveTexture(GL_TEXTURE0); | |
| glBindTexture(GL_TEXTURE_2D, m_ui32TexHeadBody); | |
| break; | |
| case eLegs: | |
| glActiveTexture(GL_TEXTURE1); | |
| glBindTexture(GL_TEXTURE_2D, m_ui32TexLegsNormalMap); | |
| glActiveTexture(GL_TEXTURE0); | |
| glBindTexture(GL_TEXTURE_2D, m_ui32TexLegs); | |
| break; | |
| default: | |
| glActiveTexture(GL_TEXTURE1); | |
| glBindTexture(GL_TEXTURE_2D, m_ui32TexBeltNormalMap); | |
| glActiveTexture(GL_TEXTURE0); | |
| glBindTexture(GL_TEXTURE_2D, m_ui32TexBelt); | |
| break; | |
| } | |
| DrawSkinnedMesh(i32NodeIndex); | |
| } | |
| // Safely disable the vertex attribute arrays | |
| for(i = 0; i < eNumAttribs; ++i) glDisableVertexAttribArray(i); | |
| // Draw non-skinned meshes | |
| glUseProgram(m_DefaultShaderProgram.uiId); | |
| // Enable the vertex attribute arrays | |
| for(i = 0; i < eNumDefaultAttribs; ++i) glEnableVertexAttribArray(i); | |
| for(unsigned int i32NodeIndex = 3; i32NodeIndex < m_Scene.nNumMeshNode; ++i32NodeIndex) | |
| { | |
| SPODNode& Node = m_Scene.pNode[i32NodeIndex]; | |
| SPODMesh& Mesh = m_Scene.pMesh[Node.nIdx]; | |
| // bind the VBO for the mesh | |
| glBindBuffer(GL_ARRAY_BUFFER, m_puiVbo[Node.nIdx]); | |
| // bind the index buffer, won't hurt if the handle is 0 | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_puiIndexVbo[Node.nIdx]); | |
| // Get the node model matrix | |
| PVRTMat4 mWorld; | |
| mWorld = m_Scene.GetWorldMatrix(Node); | |
| // Setup the appropriate texture and transformation (if needed) | |
| switch(i32NodeIndex) | |
| { | |
| case eWall: | |
| glBindTexture(GL_TEXTURE_2D, m_ui32TexWall); | |
| // Rotate the wall mesh which is circular | |
| mWorld *= PVRTMat4::RotationY(m_fWallPos); | |
| glUniform1f(m_DefaultShaderProgram.auiLoc[eDefaultUOffset], 0); | |
| break; | |
| case eBackground: | |
| glBindTexture(GL_TEXTURE_2D, m_ui32TexSkyLine); | |
| glUniform1f(m_DefaultShaderProgram.auiLoc[eDefaultUOffset], m_fBackgroundPos); | |
| break; | |
| case eLights: | |
| { | |
| glBindTexture(GL_TEXTURE_2D, m_ui32TexLamp); | |
| PVRTMat4 mWallWorld = m_Scene.GetWorldMatrix(m_Scene.pNode[eWall]); | |
| mWorld = mWallWorld * PVRTMat4::RotationY(m_fWallPos) * mWallWorld.inverse() * mWorld; | |
| glUniform1f(m_DefaultShaderProgram.auiLoc[eDefaultUOffset], 0); | |
| } | |
| break; | |
| default: | |
| break; | |
| }; | |
| // Set up shader uniforms | |
| PVRTMat4 mModelViewProj; | |
| mModelViewProj = mViewProjection * mWorld; | |
| glUniformMatrix4fv(m_DefaultShaderProgram.auiLoc[eDefaultMVPMatrix], 1, GL_FALSE, mModelViewProj.ptr()); | |
| // Set the vertex attribute offsets | |
| glVertexAttribPointer(DEFAULT_VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, Mesh.sVertex.nStride, Mesh.sVertex.pData); | |
| glVertexAttribPointer(DEFAULT_TEXCOORD_ARRAY, 2, GL_FLOAT, GL_FALSE, Mesh.psUVW[0].nStride, Mesh.psUVW[0].pData); | |
| // Indexed Triangle list | |
| glDrawElements(GL_TRIANGLES, Mesh.nNumFaces*3, GL_UNSIGNED_SHORT, 0); | |
| } | |
| // Safely disable the vertex attribute arrays | |
| for(i = 0; i < eNumAttribs; ++i) glDisableVertexAttribArray(i); | |
| // unbind the VBOs | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); | |
| // Display the demo name using the tools. For a detailed explanation, see the training course IntroducingPVRTools | |
| const char * pDescription; | |
| if(m_bEnableDOT3) | |
| pDescription = "Skinning with DOT3 Per Pixel Lighting"; | |
| else | |
| pDescription = "Skinning with Vertex Lighting"; | |
| m_Print3D.DisplayDefaultTitle("Chameleon Man", pDescription, ePVRTPrint3DSDKLogo); | |
| m_Print3D.Flush(); | |
| return true; | |
| } | |
| /*!**************************************************************************** | |
| @Function DrawSkinnedMesh | |
| @Input i32NodeIndex Node index of the mesh to draw | |
| @Description Draws a SPODMesh after the model view matrix has been set and | |
| the meterial prepared. | |
| ******************************************************************************/ | |
| void OGLES2ChameleonMan::DrawSkinnedMesh(int i32NodeIndex) | |
| { | |
| SPODNode& Node = m_Scene.pNode[i32NodeIndex]; | |
| SPODMesh& Mesh = m_Scene.pMesh[Node.nIdx]; | |
| // bind the VBO for the mesh | |
| glBindBuffer(GL_ARRAY_BUFFER, m_puiVbo[Node.nIdx]); | |
| // bind the index buffer, won't hurt if the handle is 0 | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_puiIndexVbo[Node.nIdx]); | |
| // Set the vertex attribute offsets | |
| glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, Mesh.sVertex.nStride, Mesh.sVertex.pData); | |
| glVertexAttribPointer(NORMAL_ARRAY, 3, GL_FLOAT, GL_FALSE, Mesh.sNormals.nStride, Mesh.sNormals.pData); | |
| glVertexAttribPointer(TANGENT_ARRAY, 3, GL_FLOAT, GL_FALSE, Mesh.sTangents.nStride, Mesh.sTangents.pData); | |
| glVertexAttribPointer(BINORMAL_ARRAY, 3, GL_FLOAT, GL_FALSE, Mesh.sBinormals.nStride, Mesh.sBinormals.pData); | |
| glVertexAttribPointer(TEXCOORD_ARRAY, 2, GL_FLOAT, GL_FALSE, Mesh.psUVW[0].nStride, Mesh.psUVW[0].pData); | |
| glVertexAttribPointer(BONEINDEX_ARRAY, Mesh.sBoneIdx.n, GL_UNSIGNED_BYTE, GL_FALSE, Mesh.sBoneIdx.nStride, Mesh.sBoneIdx.pData); | |
| glVertexAttribPointer(BONEWEIGHT_ARRAY, Mesh.sBoneWeight.n, GL_UNSIGNED_BYTE, GL_TRUE, Mesh.sBoneWeight.nStride, Mesh.sBoneWeight.pData); | |
| for(int i32Batch = 0; i32Batch < Mesh.sBoneBatches.nBatchCnt; ++i32Batch) | |
| { | |
| /* | |
| If the current mesh has bone index and weight data then we need to | |
| set up some additional variables in the shaders. | |
| */ | |
| // Set the number of bones that will influence each vertex in the mesh | |
| glUniform1i(m_SkinnedShaderProgram.auiLoc[eBoneCount], Mesh.sBoneIdx.n); | |
| // Go through the bones for the current bone batch | |
| PVRTMat4 amBoneWorld[8]; | |
| PVRTMat3 afBoneWorldIT[8], mBoneIT; | |
| int i32Count = Mesh.sBoneBatches.pnBatchBoneCnt[i32Batch]; | |
| for(int i = 0; i < i32Count; ++i) | |
| { | |
| // Get the Node of the bone | |
| int i32NodeID = Mesh.sBoneBatches.pnBatches[i32Batch * Mesh.sBoneBatches.nBatchBoneMax + i]; | |
| // Get the World transformation matrix for this bone and combine it with our app defined | |
| // transformation matrix | |
| amBoneWorld[i] = m_Scene.GetBoneWorldMatrix(Node, m_Scene.pNode[i32NodeID]); | |
| // Calculate the inverse transpose of the 3x3 rotation/scale part for correct lighting | |
| afBoneWorldIT[i] = PVRTMat3(amBoneWorld[i]).inverse().transpose(); | |
| } | |
| glUniformMatrix4fv(m_SkinnedShaderProgram.auiLoc[eBoneMatrices], i32Count, GL_FALSE, amBoneWorld[0].ptr()); | |
| glUniformMatrix3fv(m_SkinnedShaderProgram.auiLoc[eBoneMatricesIT], i32Count, GL_FALSE, afBoneWorldIT[0].ptr()); | |
| /* | |
| As we are using bone batching we don't want to draw all the faces contained within pMesh, we only want | |
| to draw the ones that are in the current batch. To do this we pass to the drawMesh function the offset | |
| to the start of the current batch of triangles (Mesh.sBoneBatches.pnBatchOffset[i32Batch]) and the | |
| total number of triangles to draw (i32Tris) | |
| */ | |
| int i32Tris; | |
| if(i32Batch+1 < Mesh.sBoneBatches.nBatchCnt) | |
| i32Tris = Mesh.sBoneBatches.pnBatchOffset[i32Batch+1] - Mesh.sBoneBatches.pnBatchOffset[i32Batch]; | |
| else | |
| i32Tris = Mesh.nNumFaces - Mesh.sBoneBatches.pnBatchOffset[i32Batch]; | |
| // Draw the mesh | |
| size_t offset = sizeof(GLushort) * 3 * Mesh.sBoneBatches.pnBatchOffset[i32Batch]; | |
| glDrawElements(GL_TRIANGLES, i32Tris * 3, GL_UNSIGNED_SHORT, (void*) offset); | |
| } | |
| } | |
| /*!**************************************************************************** | |
| @Function NewDemo | |
| @Return PVRShell* The demo supplied by the user | |
| @Description This function must be implemented by the user of the shell. | |
| The user should return its PVRShell object defining the | |
| behaviour of the application. | |
| ******************************************************************************/ | |
| PVRShell* NewDemo() | |
| { | |
| return new OGLES2ChameleonMan(); | |
| } | |
| /****************************************************************************** | |
| End of file (OGLES2ChameleonMan.cpp) | |
| ******************************************************************************/ | |