From ba60918eaae142598fc84ee7e2af06b3ddc26ba1 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Wed, 4 May 2016 20:25:32 +0200 Subject: [PATCH 01/11] Updated Oculus sample Now GLFW3 windows/context creation works ok and a sample red rectangle has been drawn using rlgl. Next step is working in tracking position/orientation maths and try to get a simple 3d scene... --- .../oculus_glfw_sample/oculus_glfw_sample.c | 598 +++++++++--------- .../oculus_glfw_sample.old.c | 498 +++++++++++++++ .../oculus_glfw_sample_new.c | 280 -------- .../raylib_OculusRiftCV1.png | Bin 0 -> 218472 bytes examples/oculus_glfw_sample/rlgl.c | 339 +++++----- examples/oculus_glfw_sample/rlgl.h | 18 +- 6 files changed, 972 insertions(+), 761 deletions(-) create mode 100644 examples/oculus_glfw_sample/oculus_glfw_sample.old.c delete mode 100644 examples/oculus_glfw_sample/oculus_glfw_sample_new.c create mode 100644 examples/oculus_glfw_sample/raylib_OculusRiftCV1.png diff --git a/examples/oculus_glfw_sample/oculus_glfw_sample.c b/examples/oculus_glfw_sample/oculus_glfw_sample.c index 19de0188c..b1fabbe97 100644 --- a/examples/oculus_glfw_sample/oculus_glfw_sample.c +++ b/examples/oculus_glfw_sample/oculus_glfw_sample.c @@ -17,57 +17,52 @@ * ********************************************************************************************/ -#if defined(_WIN32) - #define GLFW_EXPOSE_NATIVE_WIN32 - #define GLFW_EXPOSE_NATIVE_WGL - #define OVR_OS_WIN32 -#elif defined(__APPLE__) - #define GLFW_EXPOSE_NATIVE_COCOA - #define GLFW_EXPOSE_NATIVE_NSGL - #define OVR_OS_MAC -#elif defined(__linux__) - #define GLFW_EXPOSE_NATIVE_X11 - #define GLFW_EXPOSE_NATIVE_GLX - #define OVR_OS_LINUX -#endif - -#include "glad.h" // Extensions loading library - -#include -#include +#include +#include +#include +#include -#include "OculusSDK/LibOVR/Include/OVR_CAPI_GL.h" // Oculus SDK for OpenGL +#include "glad.h" // Extensions loading library +#include // Windows/Context and inputs management -//#include "GL/CAPI_GLE.h" // stripped-down GLEW/GLAD library to manage extensions (really required?) -//#include "Extras/OVR_Math.h" // math utilities C++ (really required?) +#include "OculusSDK/LibOVR/Include/OVR_CAPI_GL.h" // Oculus SDK for OpenGL #define RLGL_STANDALONE #include "rlgl.h" -#include -#include +// OVR device variables +ovrSession session; +ovrHmdDesc hmdDesc; +ovrGraphicsLuid luid; + +// OVR OpenGL required variables +GLuint fbo = 0; +GLuint depthBuffer = 0; +ovrTextureSwapChain eyeTexture; + +GLuint mirrorFbo = 0; +ovrMirrorTexture mirrorTexture; +ovrEyeRenderDesc eyeRenderDescs[2]; +Matrix eyeProjections[2]; + +ovrLayerEyeFov eyeLayer; +ovrViewScaleDesc viewScaleDesc; + +Vector2 renderTargetSize = { 0, 0 }; +Vector2 mirrorSize; +unsigned int frame = 0; + +// GLFW variables +GLFWwindow *window = NULL; //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -typedef struct OculusBuffer { - ovrTextureSwapChain textureChain; - GLuint depthId; - GLuint fboId; - int width; - int height; -} OculusBuffer; - typedef enum { LOG_INFO = 0, LOG_ERROR, LOG_WARNING, LOG_DEBUG, LOG_OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static OculusBuffer LoadOculusBuffer(ovrSession session, int width, int height); -static void UnloadOculusBuffer(ovrSession session, OculusBuffer buffer); -static void SetOculusBuffer(ovrSession session, OculusBuffer buffer); -static void UnsetOculusBuffer(OculusBuffer buffer); - static void ErrorCallback(int error, const char* description) { fputs(description, stderr); @@ -83,18 +78,15 @@ static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, i static void DrawRectangleV(Vector2 position, Vector2 size, Color color); static void TraceLog(int msgType, const char *text, ...); +static Matrix FromOvrMatrix(ovrMatrix4f ovrM); +void DrawGrid(int slices, float spacing); +void DrawCube(Vector3 position, float width, float height, float length, Color color); //---------------------------------------------------------------------------------- // Main Entry point //---------------------------------------------------------------------------------- -int main() +int main() { - // Initialization - //-------------------------------------------------------------------------------------- - ovrSession session; - ovrGraphicsLuid luid; // Useless for OpenGL since SDK 0.7 - ovrHmdDesc hmdDesc; - ovrResult result = ovr_Initialize(NULL); if (OVR_FAILURE(result)) TraceLog(LOG_ERROR, "OVR: Could not initialize Oculus device"); @@ -114,15 +106,37 @@ int main() TraceLog(LOG_INFO, "OVR: Serian Number: %s", hmdDesc.SerialNumber); TraceLog(LOG_INFO, "OVR: Resolution: %ix%i", hmdDesc.Resolution.w, hmdDesc.Resolution.h); - int screenWidth = hmdDesc.Resolution.w/2 + 100; // Added 100 pixels for testing - int screenHeight = hmdDesc.Resolution.h/2 + 100; // Added 100 pixels for testing + + viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; + memset(&eyeLayer, 0, sizeof(ovrLayerEyeFov)); + eyeLayer.Header.Type = ovrLayerType_EyeFov; + eyeLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; + + for (int eye = 0; eye < 2; eye++) + { + eyeRenderDescs[eye] = ovr_GetRenderDesc(session, eye, hmdDesc.DefaultEyeFov[eye]); + ovrMatrix4f ovrPerspectiveProjection = ovrMatrix4f_Projection(eyeRenderDescs[eye].Fov, 0.01f, 1000.0f, ovrProjection_ClipRangeOpenGL); + // NOTE struct ovrMatrix4f { float M[4][4] } + eyeProjections[eye] = FromOvrMatrix(ovrPerspectiveProjection); + viewScaleDesc.HmdToEyeOffset[eye] = eyeRenderDescs[eye].HmdToEyeOffset; + + eyeLayer.Fov[eye] = eyeRenderDescs[eye].Fov; + ovrSizei eyeSize = ovr_GetFovTextureSize(session, eye, eyeLayer.Fov[eye], 1.0f); + eyeLayer.Viewport[eye].Size = eyeSize; + eyeLayer.Viewport[eye].Pos.x = renderTargetSize.x; + eyeLayer.Viewport[eye].Pos.y = 0; + + renderTargetSize.y = eyeSize.h; //std::max(renderTargetSize.y, (uint32_t)eyeSize.h); + renderTargetSize.x += eyeSize.w; + } + + // Make the on screen window 1/2 the resolution of the device + mirrorSize.x = hmdDesc.Resolution.w/2; + mirrorSize.y = hmdDesc.Resolution.h/2; + // GLFW3 Initialization + OpenGL 3.3 Context + Extensions //-------------------------------------------------------- - GLFWwindow *window; - - glfwSetErrorCallback(ErrorCallback); - if (!glfwInit()) { TraceLog(LOG_WARNING, "GLFW3: Can not initialize GLFW"); @@ -133,12 +147,11 @@ int main() glfwWindowHint(GLFW_DEPTH_BITS, 16); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); - glfwWindowHint(GLFW_DECORATED, GL_FALSE); // Mandatory on Oculus Rift to avoid program crash! + //glfwWindowHint(GLFW_DECORATED, GL_FALSE); // Mandatory on Oculus Rift to avoid program crash? --> NO - window = glfwCreateWindow(screenWidth, screenHeight, "rlgl standalone", NULL, NULL); + window = glfwCreateWindow(mirrorSize.x, mirrorSize.y, "raylib oculus sample", NULL, NULL); if (!window) { @@ -147,6 +160,7 @@ int main() } else TraceLog(LOG_INFO, "GLFW3: Window created successfully"); + glfwSetErrorCallback(ErrorCallback); glfwSetKeyCallback(window, KeyCallback); glfwMakeContextCurrent(window); @@ -159,174 +173,132 @@ int main() } else TraceLog(LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); - rlglInit(); - rlglInitGraphics(0, 0, screenWidth, screenHeight); - rlClearColor(245, 245, 245, 255); // Define clear color + // Initialize OVR OpenGL swap chain textures + ovrTextureSwapChainDesc desc = {}; + desc.Type = ovrTexture_2D; + desc.ArraySize = 1; + desc.Width = renderTargetSize.x; + desc.Height = renderTargetSize.y; + desc.MipLevels = 1; + desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; + desc.SampleCount = 1; + desc.StaticImage = ovrFalse; - Vector2 position = { screenWidth/2 - 100, screenHeight/2 - 100 }; - Vector2 size = { 200, 200 }; - Color color = { 180, 20, 20, 255 }; - //--------------------------------------------------------------------------- + result = ovr_CreateTextureSwapChainGL(session, &desc, &eyeTexture); + eyeLayer.ColorTexture[0] = eyeTexture; - OculusBuffer eyeRenderBuffer[2]; - - GLuint mirrorFBO = 0; - ovrMirrorTexture mirrorTexture = NULL; - - bool isVisible = true; - long long frameIndex = 0; - - // Make eyes render buffers - ovrSizei recommendedTexSizeLeft = ovr_GetFovTextureSize(session, ovrEye_Left, hmdDesc.DefaultEyeFov[0], 1.0f); - eyeRenderBuffer[0] = LoadOculusBuffer(session, recommendedTexSizeLeft.w, recommendedTexSizeLeft.h); - ovrSizei recommendedTexSizeRight = ovr_GetFovTextureSize(session, ovrEye_Right, hmdDesc.DefaultEyeFov[1], 1.0f); - eyeRenderBuffer[1] = LoadOculusBuffer(session, recommendedTexSizeRight.w, recommendedTexSizeRight.h); + if (!OVR_SUCCESS(result)) TraceLog(LOG_WARNING, "Failed to create swap textures"); - // Note: the mirror window can be any size, for this sample we use 1/2 the HMD resolution - ovrSizei windowSize = { hmdDesc.Resolution.w/2, hmdDesc.Resolution.h/2 }; + int length = 0; + result = ovr_GetTextureSwapChainLength(session, eyeTexture, &length); + + if (!OVR_SUCCESS(result) || !length) TraceLog(LOG_WARNING, "Unable to count swap chain textures"); - // Define mirror texture descriptor + for (int i = 0; i < length; ++i) + { + GLuint chainTexId; + ovr_GetTextureSwapChainBufferGL(session, eyeTexture, i, &chainTexId); + glBindTexture(GL_TEXTURE_2D, chainTexId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + glBindTexture(GL_TEXTURE_2D, 0); + + // Setup framebuffer object + glGenFramebuffers(1, &fbo); + glGenRenderbuffers(1, &depthBuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, renderTargetSize.x, renderTargetSize.y); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + // Setup mirror texture ovrMirrorTextureDesc mirrorDesc; memset(&mirrorDesc, 0, sizeof(mirrorDesc)); - mirrorDesc.Width = windowSize.w; - mirrorDesc.Height = windowSize.h; mirrorDesc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; + mirrorDesc.Width = mirrorSize.x; + mirrorDesc.Height = mirrorSize.y; + + if (!OVR_SUCCESS(ovr_CreateMirrorTextureGL(session, &mirrorDesc, &mirrorTexture))) TraceLog(LOG_WARNING, "Could not create mirror texture"); - // Create mirror texture and an FBO used to copy mirror texture to back buffer - result = ovr_CreateMirrorTextureGL(session, &mirrorDesc, &mirrorTexture); - if (!OVR_SUCCESS(result)) TraceLog(LOG_WARNING, "OVR: Failed to create mirror texture"); - - // Configure the mirror read buffer - GLuint texId; - ovr_GetMirrorTextureBufferGL(session, mirrorTexture, &texId); + glGenFramebuffers(1, &mirrorFbo); - glGenFramebuffers(1, &mirrorFBO); - glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFBO); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texId, 0); - glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - { - glDeleteFramebuffers(1, &mirrorFBO); - TraceLog(LOG_WARNING, "OVR: Could not initialize mirror framebuffers"); - } + // Recenter OVR tracking origin + ovr_RecenterTrackingOrigin(session); - glClearColor(1.0f, 0.1f, 0.1f, 0.0f); + // Initialize rlgl internal buffers and OpenGL state + rlglInit(); + rlglInitGraphics(0, 0, mirrorSize.x, mirrorSize.y); + rlClearColor(245, 245, 245, 255); // Define clear color glEnable(GL_DEPTH_TEST); - ovr_RecenterTrackingOrigin(session); - // FloorLevel will give tracking poses where the floor height is 0 - ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel); - //-------------------------------------------------------------------------------------- + Vector2 position = { mirrorSize.x/2 - 100, mirrorSize.y/2 - 100 }; + Vector2 size = { 200, 200 }; + Color color = { 180, 20, 20, 255 }; + Vector3 cubePosition = { 0.0f, 0.0f, 0.0f }; - // Main loop - while (!glfwWindowShouldClose(window)) + while (!glfwWindowShouldClose(window)) { // Update //---------------------------------------------------------------------------------- - frameIndex++; - - // TODO: Update game here! + frame++; - // Call ovr_GetRenderDesc each frame to get the ovrEyeRenderDesc, as the returned values (e.g. HmdToEyeOffset) may change at runtime. - ovrEyeRenderDesc eyeRenderDesc[2]; - eyeRenderDesc[0] = ovr_GetRenderDesc(session, ovrEye_Left, hmdDesc.DefaultEyeFov[0]); - eyeRenderDesc[1] = ovr_GetRenderDesc(session, ovrEye_Right, hmdDesc.DefaultEyeFov[1]); - - // Get eye poses, feeding in correct IPD offset - ovrPosef eyeRenderPose[2]; - ovrVector3f hmdToEyeOffset[2] = { eyeRenderDesc[0].HmdToEyeOffset, eyeRenderDesc[1].HmdToEyeOffset }; - - double sensorSampleTime; // sensorSampleTime is fed into the layer later - ovr_GetEyePoses(session, frameIndex, ovrTrue, hmdToEyeOffset, eyeRenderPose, &sensorSampleTime); + ovrPosef eyePoses[2]; + ovr_GetEyePoses(session, frame, ovrTrue, viewScaleDesc.HmdToEyeOffset, eyePoses, &eyeLayer.SensorSampleTime); //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- + int curIndex; + ovr_GetTextureSwapChainCurrentIndex(session, eyeTexture, &curIndex); + GLuint curTexId; + ovr_GetTextureSwapChainBufferGL(session, eyeTexture, curIndex, &curTexId); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, curTexId, 0); - // Clear screen to red color - //glClearColor(1.0f, 0.1f, 0.1f, 0.0f); - //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - if (isVisible) - { - for (int eye = 0; eye < 2; ++eye) - { - SetOculusBuffer(session, eyeRenderBuffer[eye]); - - // TODO: Get view and projection matrices for the eye - // Sample using Oculus OVR_Math.h (C++) - /* - Matrix4f projection[eye] = Matrix4f(ovrMatrix4f_Projection(eyeRenderDesc[eye].Fov, 0.01f, 10000.0f, ovrProjection_None)); - Matrix4f eyeOrientation[eye] = Matrix4f(Quatf(eyeRenderPose[eye].Orientation).Inverted()); - Matrix4f eyePose[eye] = Matrix4f::Translation(-Vector3f(eyeRenderPose[eye].Position)); - Matrix4f mvp = projection[eye]*eyeOrientation[eye]*eyePose[eye]; - */ - - // Sample using custom raymath.h (C) -INCOMPLETE- - /* - Matrix projection = MatrixPerspective(eyeRenderDesc[eye].Fov, ((double)screenWidth/(double)screenHeight), 0.01, 1000.0); - Matrix eyeOrientation = QuaternionToMatrix((Quaternion){ -eyeRenderPose[eye].Orientation.x, -eyeRenderPose[eye].Orientation.y, - -eyeRenderPose[eye].Orientation.z, -eyeRenderPose[eye].Orientation.w }); - Matrix eyePose = MatrixTranslate(-eyeRenderPose[eye].Position.x, -eyeRenderPose[eye].Position.y, -eyeRenderPose[eye].Position.z); - Matrix mvp = MatrixMultiply(projection, MatrixMultiply(eyeOrientation, eyePose)); - */ - - // Render everything - // TODO: Pass calculated mvp matrix to default shader to consider projection and orientation! - //DrawRectangleV(position, size, color); - //rlglDraw(); - - UnsetOculusBuffer(eyeRenderBuffer[eye]); - - // Commit changes to the textures so they get picked up frame - ovr_CommitTextureSwapChain(session, eyeRenderBuffer[eye].textureChain); - } - } + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // Set up positional data - ovrViewScaleDesc viewScaleDesc; - viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; - viewScaleDesc.HmdToEyeOffset[0] = hmdToEyeOffset[0]; - viewScaleDesc.HmdToEyeOffset[1] = hmdToEyeOffset[1]; - - // Create the main eye layer - ovrLayerEyeFov eyeLayer; - eyeLayer.Header.Type = ovrLayerType_EyeFov; - eyeLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; // Because OpenGL - for (int eye = 0; eye < 2; eye++) { - eyeLayer.ColorTexture[eye] = eyeRenderBuffer[eye].textureChain; - eyeLayer.Viewport[eye] = (ovrRecti){ eyeRenderBuffer[eye].width, eyeRenderBuffer[eye].height }; - eyeLayer.Fov[eye] = hmdDesc.DefaultEyeFov[eye]; - eyeLayer.RenderPose[eye] = eyeRenderPose[eye]; - eyeLayer.SensorSampleTime = sensorSampleTime; + glViewport(eyeLayer.Viewport[eye].Pos.x, eyeLayer.Viewport[eye].Pos.y, + eyeLayer.Viewport[eye].Size.w, eyeLayer.Viewport[eye].Size.h); + eyeLayer.RenderPose[eye] = eyePoses[eye]; + + // Convert struct ovrPosef { ovrQuatf Orientation; ovrVector3f Position; } to Matrix + // TODO: Review maths! + Matrix eyeOrientation = QuaternionToMatrix((Quaternion){ -eyePoses[eye].Orientation.x, -eyePoses[eye].Orientation.y, -eyePoses[eye].Orientation.z, -eyePoses[eye].Orientation.w }); + Matrix eyePosition = MatrixTranslate(-eyePoses[eye].Position.x, -eyePoses[eye].Position.y, -eyePoses[eye].Position.z); + Matrix mvp = MatrixMultiply(eyeProjections[eye], MatrixMultiply(eyeOrientation, eyePosition)); + + // NOTE: Nothing is drawn until rlglDraw() + DrawRectangleV(position, size, color); + //DrawCube(cubePosition, 2.0f, 2.0f, 2.0f, color); + //DrawGrid(10, 1.0f); + + // NOTE: rlglDraw() must be modified to support an external modelview-projection matrix + // TODO: Still working on it (now uses internal mvp) + rlglDraw(mvp); } - - // Append all the layers to global list - ovrLayerHeader *layerList = &eyeLayer.Header; - ovrResult result = ovr_SubmitFrame(session, frameIndex, NULL, &layerList, 1); - // exit the rendering loop if submit returns an error, will retry on ovrError_DisplayLost - if (!OVR_SUCCESS(result)) return 1; - - isVisible = (result == ovrSuccess); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + ovr_CommitTextureSwapChain(session, eyeTexture); + ovrLayerHeader *headerList = &eyeLayer.Header; + ovr_SubmitFrame(session, frame, &viewScaleDesc, &headerList, 1); - // Get session status information - ovrSessionStatus sessionStatus; - ovr_GetSessionStatus(session, &sessionStatus); - if (sessionStatus.ShouldQuit) TraceLog(LOG_WARNING, "OVR: Session should quit."); - if (sessionStatus.ShouldRecenter) ovr_RecenterTrackingOrigin(session); - // Blit mirror texture to back buffer - glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFBO); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - GLint w = mirrorDesc.Width; - GLint h = mirrorDesc.Height; - glBlitFramebuffer(0, h, w, 0, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + GLuint mirrorTextureId; + ovr_GetMirrorTextureBufferGL(session, mirrorTexture, &mirrorTextureId); + glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFbo); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mirrorTextureId, 0); + glBlitFramebuffer(0, 0, mirrorSize.x, mirrorSize.y, 0, mirrorSize.y, mirrorSize.x, 0, GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glfwSwapBuffers(window); glfwPollEvents(); @@ -335,10 +307,13 @@ int main() // De-Initialization //-------------------------------------------------------------------------------------- - if (mirrorFBO) glDeleteFramebuffers(1, &mirrorFBO); + if (mirrorFbo) glDeleteFramebuffers(1, &mirrorFbo); if (mirrorTexture) ovr_DestroyMirrorTexture(session, mirrorTexture); - for (int eye = 0; eye < 2; eye++) UnloadOculusBuffer(session, eyeRenderBuffer[eye]); - + + if (fbo) glDeleteFramebuffers(1, &fbo); + if (depthBuffer) glDeleteTextures(1, &depthBuffer); + if (eyeTexture) ovr_DestroyTextureSwapChain(session, eyeTexture); + rlglClose(); glfwDestroyWindow(window); @@ -355,108 +330,6 @@ int main() // Module specific Functions Definitions //---------------------------------------------------------------------------------- -// Load Oculus required buffers: texture-swap-chain, fbo, texture-depth -static OculusBuffer LoadOculusBuffer(ovrSession session, int width, int height) -{ - OculusBuffer buffer; - buffer.width = width; - buffer.height = height; - - // Create OVR texture chain - ovrTextureSwapChainDesc desc = {}; - desc.Type = ovrTexture_2D; - desc.ArraySize = 1; - desc.Width = width; - desc.Height = height; - desc.MipLevels = 1; - desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; - desc.SampleCount = 1; - desc.StaticImage = ovrFalse; - - ovrResult result = ovr_CreateTextureSwapChainGL(session, &desc, &buffer.textureChain); - - int textureCount = 0; - ovr_GetTextureSwapChainLength(session, buffer.textureChain, &textureCount); - - if (OVR_SUCCESS(result)) - { - for (int i = 0; i < textureCount; ++i) - { - GLuint chainTexId; - ovr_GetTextureSwapChainBufferGL(session, buffer.textureChain, i, &chainTexId); - glBindTexture(GL_TEXTURE_2D, chainTexId); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - } - - // Generate framebuffer - glGenFramebuffers(1, &buffer.fboId); - - // Create Depth texture - glGenTextures(1, &buffer.depthId); - glBindTexture(GL_TEXTURE_2D, buffer.depthId); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, buffer.width, buffer.height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); - - return buffer; -} - -// Unload texture required buffers -static void UnloadOculusBuffer(ovrSession session, OculusBuffer buffer) -{ - if (buffer.textureChain) - { - ovr_DestroyTextureSwapChain(session, buffer.textureChain); - buffer.textureChain = NULL; - } - - if (buffer.depthId) - { - glDeleteTextures(1, &buffer.depthId); - buffer.depthId = 0; - } - - if (buffer.fboId) - { - glDeleteFramebuffers(1, &buffer.fboId); - buffer.fboId = 0; - } -} - -// Set current Oculus buffer -static void SetOculusBuffer(ovrSession session, OculusBuffer buffer) -{ - GLuint currentTexId; - int currentIndex; - - ovr_GetTextureSwapChainCurrentIndex(session, buffer.textureChain, ¤tIndex); - ovr_GetTextureSwapChainBufferGL(session, buffer.textureChain, currentIndex, ¤tTexId); - - glBindFramebuffer(GL_FRAMEBUFFER, buffer.fboId); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, currentTexId, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, buffer.depthId, 0); - - glViewport(0, 0, buffer.width, buffer.height); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable(GL_FRAMEBUFFER_SRGB); -} - -// Unset Oculus buffer -static void UnsetOculusBuffer(OculusBuffer buffer) -{ - glBindFramebuffer(GL_FRAMEBUFFER, buffer.fboId); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); -} - // Draw rectangle using rlgl OpenGL 1.1 style coding (translated to OpenGL 3.3 internally) static void DrawRectangleV(Vector2 position, Vector2 size, Color color) { @@ -495,4 +368,137 @@ static void TraceLog(int msgType, const char *text, ...) va_end(args); //if (msgType == LOG_ERROR) exit(1); -} \ No newline at end of file +} + +static Matrix FromOvrMatrix(ovrMatrix4f ovrmat) +{ + Matrix rmat; + + rmat.m0 = ovrmat.M[0][0]; + rmat.m1 = ovrmat.M[1][0]; + rmat.m2 = ovrmat.M[2][0]; + rmat.m3 = ovrmat.M[3][0]; + rmat.m4 = ovrmat.M[0][1]; + rmat.m5 = ovrmat.M[1][1]; + rmat.m6 = ovrmat.M[2][1]; + rmat.m7 = ovrmat.M[3][1]; + rmat.m8 = ovrmat.M[0][2]; + rmat.m9 = ovrmat.M[1][2]; + rmat.m10 = ovrmat.M[2][2]; + rmat.m11 = ovrmat.M[3][2]; + rmat.m12 = ovrmat.M[0][3]; + rmat.m13 = ovrmat.M[1][3]; + rmat.m14 = ovrmat.M[2][3]; + rmat.m15 = ovrmat.M[3][3]; + + //MatrixTranspose(&rmat); + + return rmat; +} + +// Draw cube +// NOTE: Cube position is the center position +void DrawCube(Vector3 position, float width, float height, float length, Color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + rlPushMatrix(); + + // NOTE: Be careful! Function order matters (rotate -> scale -> translate) + rlTranslatef(position.x, position.y, position.z); + //rlScalef(2.0f, 2.0f, 2.0f); + //rlRotatef(45, 0, 1, 0); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // Front Face ----------------------------------------------------- + rlVertex3f(x-width/2, y-height/2, z+length/2); // Bottom Left + rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left + + rlVertex3f(x+width/2, y+height/2, z+length/2); // Top Right + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left + rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + + // Back Face ------------------------------------------------------ + rlVertex3f(x-width/2, y-height/2, z-length/2); // Bottom Left + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left + rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + + rlVertex3f(x+width/2, y+height/2, z-length/2); // Top Right + rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left + + // Top Face ------------------------------------------------------- + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left + rlVertex3f(x-width/2, y+height/2, z+length/2); // Bottom Left + rlVertex3f(x+width/2, y+height/2, z+length/2); // Bottom Right + + rlVertex3f(x+width/2, y+height/2, z-length/2); // Top Right + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left + rlVertex3f(x+width/2, y+height/2, z+length/2); // Bottom Right + + // Bottom Face ---------------------------------------------------- + rlVertex3f(x-width/2, y-height/2, z-length/2); // Top Left + rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + rlVertex3f(x-width/2, y-height/2, z+length/2); // Bottom Left + + rlVertex3f(x+width/2, y-height/2, z-length/2); // Top Right + rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + rlVertex3f(x-width/2, y-height/2, z-length/2); // Top Left + + // Right face ----------------------------------------------------- + rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + rlVertex3f(x+width/2, y+height/2, z-length/2); // Top Right + rlVertex3f(x+width/2, y+height/2, z+length/2); // Top Left + + rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom Left + rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + rlVertex3f(x+width/2, y+height/2, z+length/2); // Top Left + + // Left Face ------------------------------------------------------ + rlVertex3f(x-width/2, y-height/2, z-length/2); // Bottom Right + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Right + + rlVertex3f(x-width/2, y-height/2, z+length/2); // Bottom Left + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left + rlVertex3f(x-width/2, y-height/2, z-length/2); // Bottom Right + rlEnd(); + rlPopMatrix(); +} + +// Draw a grid centered at (0, 0, 0) +void DrawGrid(int slices, float spacing) +{ + int halfSlices = slices / 2; + + rlBegin(RL_LINES); + for(int i = -halfSlices; i <= halfSlices; i++) + { + if (i == 0) + { + rlColor3f(0.5f, 0.5f, 0.5f); + rlColor3f(0.5f, 0.5f, 0.5f); + rlColor3f(0.5f, 0.5f, 0.5f); + rlColor3f(0.5f, 0.5f, 0.5f); + } + else + { + rlColor3f(0.75f, 0.75f, 0.75f); + rlColor3f(0.75f, 0.75f, 0.75f); + rlColor3f(0.75f, 0.75f, 0.75f); + rlColor3f(0.75f, 0.75f, 0.75f); + } + + rlVertex3f((float)i*spacing, 0.0f, (float)-halfSlices*spacing); + rlVertex3f((float)i*spacing, 0.0f, (float)halfSlices*spacing); + + rlVertex3f((float)-halfSlices*spacing, 0.0f, (float)i*spacing); + rlVertex3f((float)halfSlices*spacing, 0.0f, (float)i*spacing); + } + rlEnd(); +} diff --git a/examples/oculus_glfw_sample/oculus_glfw_sample.old.c b/examples/oculus_glfw_sample/oculus_glfw_sample.old.c new file mode 100644 index 000000000..c4997eae5 --- /dev/null +++ b/examples/oculus_glfw_sample/oculus_glfw_sample.old.c @@ -0,0 +1,498 @@ +/******************************************************************************************* +* +* raylib Oculus minimum sample (OpenGL 3.3 Core) +* +* NOTE: This example requires raylib module [rlgl] +* +* Compile rlgl using: +* gcc -c rlgl.c -Wall -std=c99 -DRLGL_STANDALONE -DRAYMATH_IMPLEMENTATION -DGRAPHICS_API_OPENGL_33 +* +* Compile example using: +* gcc -o oculus_glfw_sample.exe oculus_glfw_sample.c rlgl.o glad.o -L. -lLibOVRRT32_1 -lglfw3 -lopengl32 -lgdi32 -std=c99 +* +* This example has been created using raylib 1.5 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2015 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#if defined(_WIN32) + #define GLFW_EXPOSE_NATIVE_WIN32 + #define GLFW_EXPOSE_NATIVE_WGL + #define OVR_OS_WIN32 +#elif defined(__APPLE__) + #define GLFW_EXPOSE_NATIVE_COCOA + #define GLFW_EXPOSE_NATIVE_NSGL + #define OVR_OS_MAC +#elif defined(__linux__) + #define GLFW_EXPOSE_NATIVE_X11 + #define GLFW_EXPOSE_NATIVE_GLX + #define OVR_OS_LINUX +#endif + +#include "glad.h" // Extensions loading library + +#include +#include + +#include "OculusSDK/LibOVR/Include/OVR_CAPI_GL.h" // Oculus SDK for OpenGL + +//#include "GL/CAPI_GLE.h" // stripped-down GLEW/GLAD library to manage extensions (really required?) +//#include "Extras/OVR_Math.h" // math utilities C++ (really required?) + +#define RLGL_STANDALONE +#include "rlgl.h" + +#include +#include + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct OculusBuffer { + ovrTextureSwapChain textureChain; + GLuint depthId; + GLuint fboId; + int width; + int height; +} OculusBuffer; + +typedef enum { LOG_INFO = 0, LOG_ERROR, LOG_WARNING, LOG_DEBUG, LOG_OTHER } TraceLogType; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static OculusBuffer LoadOculusBuffer(ovrSession session, int width, int height); +static void UnloadOculusBuffer(ovrSession session, OculusBuffer buffer); +static void SetOculusBuffer(ovrSession session, OculusBuffer buffer); +static void UnsetOculusBuffer(OculusBuffer buffer); + +static void ErrorCallback(int error, const char* description) +{ + fputs(description, stderr); +} + +static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) + { + glfwSetWindowShouldClose(window, GL_TRUE); + } +} + +static void DrawRectangleV(Vector2 position, Vector2 size, Color color); +static void TraceLog(int msgType, const char *text, ...); + +//---------------------------------------------------------------------------------- +// Main Entry point +//---------------------------------------------------------------------------------- +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + ovrSession session; + ovrGraphicsLuid luid; // Useless for OpenGL since SDK 0.7 + ovrHmdDesc hmdDesc; + + ovrResult result = ovr_Initialize(NULL); + if (OVR_FAILURE(result)) TraceLog(LOG_ERROR, "OVR: Could not initialize Oculus device"); + + result = ovr_Create(&session, &luid); + if (OVR_FAILURE(result)) + { + TraceLog(LOG_WARNING, "OVR: Could not create Oculus session"); + ovr_Shutdown(); + } + + hmdDesc = ovr_GetHmdDesc(session); + + TraceLog(LOG_INFO, "OVR: Product Name: %s", hmdDesc.ProductName); + TraceLog(LOG_INFO, "OVR: Manufacturer: %s", hmdDesc.Manufacturer); + TraceLog(LOG_INFO, "OVR: Product ID: %i", hmdDesc.ProductId); + TraceLog(LOG_INFO, "OVR: Product Type: %i", hmdDesc.Type); + TraceLog(LOG_INFO, "OVR: Serian Number: %s", hmdDesc.SerialNumber); + TraceLog(LOG_INFO, "OVR: Resolution: %ix%i", hmdDesc.Resolution.w, hmdDesc.Resolution.h); + + int screenWidth = hmdDesc.Resolution.w/2 + 100; // Added 100 pixels for testing + int screenHeight = hmdDesc.Resolution.h/2 + 100; // Added 100 pixels for testing + + // GLFW3 Initialization + OpenGL 3.3 Context + Extensions + //-------------------------------------------------------- + GLFWwindow *window; + + glfwSetErrorCallback(ErrorCallback); + + if (!glfwInit()) + { + TraceLog(LOG_WARNING, "GLFW3: Can not initialize GLFW"); + exit(EXIT_FAILURE); + } + else TraceLog(LOG_INFO, "GLFW3: GLFW initialized successfully"); + + glfwWindowHint(GLFW_DEPTH_BITS, 16); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); + glfwWindowHint(GLFW_DECORATED, GL_FALSE); // Mandatory on Oculus Rift to avoid program crash! + + window = glfwCreateWindow(screenWidth, screenHeight, "rlgl standalone", NULL, NULL); + + if (!window) + { + glfwTerminate(); + exit(EXIT_FAILURE); + } + else TraceLog(LOG_INFO, "GLFW3: Window created successfully"); + + glfwSetKeyCallback(window, KeyCallback); + + glfwMakeContextCurrent(window); + glfwSwapInterval(0); + + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + TraceLog(LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + exit(1); + } + else TraceLog(LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + + rlglInit(); + rlglInitGraphics(0, 0, screenWidth, screenHeight); + rlClearColor(245, 245, 245, 255); // Define clear color + + Vector2 position = { screenWidth/2 - 100, screenHeight/2 - 100 }; + Vector2 size = { 200, 200 }; + Color color = { 180, 20, 20, 255 }; + //--------------------------------------------------------------------------- + + OculusBuffer eyeRenderBuffer[2]; + + GLuint mirrorFBO = 0; + ovrMirrorTexture mirrorTexture = NULL; + + bool isVisible = true; + long long frameIndex = 0; + + // Make eyes render buffers + ovrSizei recommendedTexSizeLeft = ovr_GetFovTextureSize(session, ovrEye_Left, hmdDesc.DefaultEyeFov[0], 1.0f); + eyeRenderBuffer[0] = LoadOculusBuffer(session, recommendedTexSizeLeft.w, recommendedTexSizeLeft.h); + ovrSizei recommendedTexSizeRight = ovr_GetFovTextureSize(session, ovrEye_Right, hmdDesc.DefaultEyeFov[1], 1.0f); + eyeRenderBuffer[1] = LoadOculusBuffer(session, recommendedTexSizeRight.w, recommendedTexSizeRight.h); + + // Note: the mirror window can be any size, for this sample we use 1/2 the HMD resolution + ovrSizei windowSize = { hmdDesc.Resolution.w/2, hmdDesc.Resolution.h/2 }; + + // Define mirror texture descriptor + ovrMirrorTextureDesc mirrorDesc; + memset(&mirrorDesc, 0, sizeof(mirrorDesc)); + mirrorDesc.Width = windowSize.w; + mirrorDesc.Height = windowSize.h; + mirrorDesc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; + + // Create mirror texture and an FBO used to copy mirror texture to back buffer + result = ovr_CreateMirrorTextureGL(session, &mirrorDesc, &mirrorTexture); + if (!OVR_SUCCESS(result)) TraceLog(LOG_WARNING, "OVR: Failed to create mirror texture"); + + // Configure the mirror read buffer + GLuint texId; + ovr_GetMirrorTextureBufferGL(session, mirrorTexture, &texId); + + glGenFramebuffers(1, &mirrorFBO); + glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFBO); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texId, 0); + glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteFramebuffers(1, &mirrorFBO); + TraceLog(LOG_WARNING, "OVR: Could not initialize mirror framebuffers"); + } + + glClearColor(1.0f, 0.1f, 0.1f, 0.0f); + glEnable(GL_DEPTH_TEST); + ovr_RecenterTrackingOrigin(session); + + // FloorLevel will give tracking poses where the floor height is 0 + ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel); + //-------------------------------------------------------------------------------------- + + // Main loop + while (!glfwWindowShouldClose(window)) + { + // Update + //---------------------------------------------------------------------------------- + frameIndex++; + + // TODO: Update game here! + + // Call ovr_GetRenderDesc each frame to get the ovrEyeRenderDesc, as the returned values (e.g. HmdToEyeOffset) may change at runtime. + ovrEyeRenderDesc eyeRenderDesc[2]; + eyeRenderDesc[0] = ovr_GetRenderDesc(session, ovrEye_Left, hmdDesc.DefaultEyeFov[0]); + eyeRenderDesc[1] = ovr_GetRenderDesc(session, ovrEye_Right, hmdDesc.DefaultEyeFov[1]); + + // Get eye poses, feeding in correct IPD offset + ovrPosef eyeRenderPose[2]; + ovrVector3f hmdToEyeOffset[2] = { eyeRenderDesc[0].HmdToEyeOffset, eyeRenderDesc[1].HmdToEyeOffset }; + + double sensorSampleTime; // sensorSampleTime is fed into the layer later + ovr_GetEyePoses(session, frameIndex, ovrTrue, hmdToEyeOffset, eyeRenderPose, &sensorSampleTime); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + + // Clear screen to red color + glClearColor(1.0f, 0.1f, 0.1f, 0.0f); + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + if (isVisible) + { + for (int eye = 0; eye < 2; ++eye) + { + SetOculusBuffer(session, eyeRenderBuffer[eye]); + + // TODO: Get view and projection matrices for the eye + // Sample using Oculus OVR_Math.h (C++) + /* + Matrix4f projection[eye] = Matrix4f(ovrMatrix4f_Projection(eyeRenderDesc[eye].Fov, 0.01f, 10000.0f, ovrProjection_None)); + Matrix4f eyeOrientation[eye] = Matrix4f(Quatf(eyeRenderPose[eye].Orientation).Inverted()); + Matrix4f eyePose[eye] = Matrix4f::Translation(-Vector3f(eyeRenderPose[eye].Position)); + Matrix4f mvp = projection[eye]*eyeOrientation[eye]*eyePose[eye]; + */ + + // Sample using custom raymath.h (C) -INCOMPLETE- + /* + Matrix projection = MatrixPerspective(eyeRenderDesc[eye].Fov, ((double)screenWidth/(double)screenHeight), 0.01, 1000.0); + Matrix eyeOrientation = QuaternionToMatrix((Quaternion){ -eyeRenderPose[eye].Orientation.x, -eyeRenderPose[eye].Orientation.y, + -eyeRenderPose[eye].Orientation.z, -eyeRenderPose[eye].Orientation.w }); + Matrix eyePose = MatrixTranslate(-eyeRenderPose[eye].Position.x, -eyeRenderPose[eye].Position.y, -eyeRenderPose[eye].Position.z); + Matrix mvp = MatrixMultiply(projection, MatrixMultiply(eyeOrientation, eyePose)); + */ + + // Render everything + // TODO: Pass calculated mvp matrix to default shader to consider projection and orientation! + //DrawRectangleV(position, size, color); + //rlglDraw(); + + UnsetOculusBuffer(eyeRenderBuffer[eye]); + + // Commit changes to the textures so they get picked up frame + ovr_CommitTextureSwapChain(session, eyeRenderBuffer[eye].textureChain); + } + } + + // Set up positional data + ovrViewScaleDesc viewScaleDesc; + viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; + viewScaleDesc.HmdToEyeOffset[0] = hmdToEyeOffset[0]; + viewScaleDesc.HmdToEyeOffset[1] = hmdToEyeOffset[1]; + + // Create the main eye layer + ovrLayerEyeFov eyeLayer; + eyeLayer.Header.Type = ovrLayerType_EyeFov; + eyeLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; // Because OpenGL + + for (int eye = 0; eye < 2; eye++) + { + eyeLayer.ColorTexture[eye] = eyeRenderBuffer[eye].textureChain; + eyeLayer.Viewport[eye] = (ovrRecti){ eyeRenderBuffer[eye].width, eyeRenderBuffer[eye].height }; + eyeLayer.Fov[eye] = hmdDesc.DefaultEyeFov[eye]; + eyeLayer.RenderPose[eye] = eyeRenderPose[eye]; + eyeLayer.SensorSampleTime = sensorSampleTime; + } + + // Append all the layers to global list + ovrLayerHeader *layerList = &eyeLayer.Header; + ovrResult result = ovr_SubmitFrame(session, frameIndex, NULL, &layerList, 1); + + // exit the rendering loop if submit returns an error, will retry on ovrError_DisplayLost + if (!OVR_SUCCESS(result)) return 1; + + isVisible = (result == ovrSuccess); + + // Get session status information + ovrSessionStatus sessionStatus; + ovr_GetSessionStatus(session, &sessionStatus); + if (sessionStatus.ShouldQuit) TraceLog(LOG_WARNING, "OVR: Session should quit."); + if (sessionStatus.ShouldRecenter) ovr_RecenterTrackingOrigin(session); + + // Blit mirror texture to back buffer + glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFBO); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + GLint w = mirrorDesc.Width; + GLint h = mirrorDesc.Height; + glBlitFramebuffer(0, h, w, 0, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + glfwSwapBuffers(window); + glfwPollEvents(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + if (mirrorFBO) glDeleteFramebuffers(1, &mirrorFBO); + if (mirrorTexture) ovr_DestroyMirrorTexture(session, mirrorTexture); + for (int eye = 0; eye < 2; eye++) UnloadOculusBuffer(session, eyeRenderBuffer[eye]); + + rlglClose(); + + glfwDestroyWindow(window); + glfwTerminate(); + + ovr_Destroy(session); // Must be called after glfwTerminate() + ovr_Shutdown(); + //-------------------------------------------------------------------------------------- + + return 0; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definitions +//---------------------------------------------------------------------------------- + +// Load Oculus required buffers: texture-swap-chain, fbo, texture-depth +static OculusBuffer LoadOculusBuffer(ovrSession session, int width, int height) +{ + OculusBuffer buffer; + buffer.width = width; + buffer.height = height; + + // Create OVR texture chain + ovrTextureSwapChainDesc desc = {}; + desc.Type = ovrTexture_2D; + desc.ArraySize = 1; + desc.Width = width; + desc.Height = height; + desc.MipLevels = 1; + desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; + desc.SampleCount = 1; + desc.StaticImage = ovrFalse; + + ovrResult result = ovr_CreateTextureSwapChainGL(session, &desc, &buffer.textureChain); + + int textureCount = 0; + ovr_GetTextureSwapChainLength(session, buffer.textureChain, &textureCount); + + if (OVR_SUCCESS(result)) + { + for (int i = 0; i < textureCount; ++i) + { + GLuint chainTexId; + ovr_GetTextureSwapChainBufferGL(session, buffer.textureChain, i, &chainTexId); + glBindTexture(GL_TEXTURE_2D, chainTexId); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + } + + // Generate framebuffer + glGenFramebuffers(1, &buffer.fboId); + + // Create Depth texture + glGenTextures(1, &buffer.depthId); + glBindTexture(GL_TEXTURE_2D, buffer.depthId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, buffer.width, buffer.height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + return buffer; +} + +// Unload texture required buffers +static void UnloadOculusBuffer(ovrSession session, OculusBuffer buffer) +{ + if (buffer.textureChain) + { + ovr_DestroyTextureSwapChain(session, buffer.textureChain); + buffer.textureChain = NULL; + } + + if (buffer.depthId) + { + glDeleteTextures(1, &buffer.depthId); + buffer.depthId = 0; + } + + if (buffer.fboId) + { + glDeleteFramebuffers(1, &buffer.fboId); + buffer.fboId = 0; + } +} + +// Set current Oculus buffer +static void SetOculusBuffer(ovrSession session, OculusBuffer buffer) +{ + GLuint currentTexId; + int currentIndex; + + ovr_GetTextureSwapChainCurrentIndex(session, buffer.textureChain, ¤tIndex); + ovr_GetTextureSwapChainBufferGL(session, buffer.textureChain, currentIndex, ¤tTexId); + + glBindFramebuffer(GL_FRAMEBUFFER, buffer.fboId); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, currentTexId, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, buffer.depthId, 0); + + glViewport(0, 0, buffer.width, buffer.height); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_FRAMEBUFFER_SRGB); +} + +// Unset Oculus buffer +static void UnsetOculusBuffer(OculusBuffer buffer) +{ + glBindFramebuffer(GL_FRAMEBUFFER, buffer.fboId); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); +} + +// Draw rectangle using rlgl OpenGL 1.1 style coding (translated to OpenGL 3.3 internally) +static void DrawRectangleV(Vector2 position, Vector2 size, Color color) +{ + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2i(position.x, position.y); + rlVertex2i(position.x, position.y + size.y); + rlVertex2i(position.x + size.x, position.y + size.y); + + rlVertex2i(position.x, position.y); + rlVertex2i(position.x + size.x, position.y + size.y); + rlVertex2i(position.x + size.x, position.y); + rlEnd(); +} + +// Output a trace log message +// NOTE: Expected msgType: (0)Info, (1)Error, (2)Warning +static void TraceLog(int msgType, const char *text, ...) +{ + va_list args; + va_start(args, text); + + switch(msgType) + { + case LOG_INFO: fprintf(stdout, "INFO: "); break; + case LOG_ERROR: fprintf(stdout, "ERROR: "); break; + case LOG_WARNING: fprintf(stdout, "WARNING: "); break; + case LOG_DEBUG: fprintf(stdout, "DEBUG: "); break; + default: break; + } + + vfprintf(stdout, text, args); + fprintf(stdout, "\n"); + + va_end(args); + + //if (msgType == LOG_ERROR) exit(1); +} \ No newline at end of file diff --git a/examples/oculus_glfw_sample/oculus_glfw_sample_new.c b/examples/oculus_glfw_sample/oculus_glfw_sample_new.c deleted file mode 100644 index 4a4689495..000000000 --- a/examples/oculus_glfw_sample/oculus_glfw_sample_new.c +++ /dev/null @@ -1,280 +0,0 @@ - -#include -#include -#include - -#include "glad.h" // Extensions loading library -#include - -#include "OculusSDK/LibOVR/Include/OVR_CAPI_GL.h" // Oculus SDK for OpenGL - -#define FAIL(X) printf(X); - -typedef struct Vector2 { - float x; - float y; -} Vector2; - -typedef struct Matrix { - float m0, m4, m8, m12; - float m1, m5, m9, m13; - float m2, m6, m10, m14; - float m3, m7, m11, m15; -} Matrix; - -// RiftManagerApp class -ovrSession session; -ovrHmdDesc hmdDesc; -ovrGraphicsLuid luid; - -// RiftApp class -GLuint fbo = 0; -GLuint depthBuffer = 0; -ovrTextureSwapChain eyeTexture; - -GLuint mirrorFbo = 0; -ovrMirrorTexture mirrorTexture; -ovrEyeRenderDesc eyeRenderDescs[2]; -Matrix eyeProjections[2]; - -ovrLayerEyeFov eyeLayer; -ovrViewScaleDesc viewScaleDesc; - -Vector2 renderTargetSize; -Vector2 mirrorSize; - -// GlfwApp class -GLFWwindow *window = NULL; -unsigned int frame = 0; - -static void ErrorCallback(int error, const char* description) -{ - fputs(description, stderr); -} - -static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) -{ - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) - { - glfwSetWindowShouldClose(window, GL_TRUE); - } -} - -// Execute our example class -int main() -{ - if (!OVR_SUCCESS(ovr_Initialize(NULL))) FAIL("Failed to initialize the Oculus SDK\n"); - - //result = ExampleApp().run(); // class ExampleApp : public RiftApp : public GlfwApp, public RiftManagerApp - - if (!OVR_SUCCESS(ovr_Create(&session, &luid))) FAIL("Unable to create HMD session\n"); - hmdDesc = ovr_GetHmdDesc(session); - - // RiftApp() constructor - viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; - memset(&eyeLayer, 0, sizeof(ovrLayerEyeFov)); - eyeLayer.Header.Type = ovrLayerType_EyeFov; - eyeLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; - - //ovr::for_each_eye([&](ovrEyeType eye) - for (int eye = 0; eye < 2; eye++) - { - eyeRenderDescs[eye] = ovr_GetRenderDesc(session, eye, hmdDesc.DefaultEyeFov[eye]); - ovrMatrix4f ovrPerspectiveProjection = ovrMatrix4f_Projection(eyeRenderDescs[eye].Fov, 0.01f, 1000.0f, ovrProjection_ClipRangeOpenGL); - //eyeProjections[eye] = ovr::toGlm(ovrPerspectiveProjection); - viewScaleDesc.HmdToEyeOffset[eye] = eyeRenderDescs[eye].HmdToEyeOffset; - - eyeLayer.Fov[eye] = eyeRenderDescs[eye].Fov; - ovrSizei eyeSize = ovr_GetFovTextureSize(session, eye, eyeLayer.Fov[eye], 1.0f); - eyeLayer.Viewport[eye].Size = eyeSize; - eyeLayer.Viewport[eye].Pos.x = renderTargetSize.x; - eyeLayer.Viewport[eye].Pos.y = 0; - - renderTargetSize.y = renderTargetSize.y; // std::max(renderTargetSize.y, (uint32_t)eyeSize.h); - renderTargetSize.x += eyeSize.w; - } - - // Make the on screen window 1/4 the resolution of the render target - mirrorSize = renderTargetSize; - mirrorSize.x /= 2; - mirrorSize.y /= 2; - - // GLFWApp() constructor - if (!glfwInit()) FAIL("Failed to initialize GLFW\n"); // Initialize the GLFW system for creating and positioning windows - glfwSetErrorCallback(ErrorCallback); - - ////preCreate(); - glfwWindowHint(GLFW_DEPTH_BITS, 16); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); - - //***************window = createRenderingTarget(windowSize, windowPosition); //GLFWwindow *createRenderingTarget(uvec2 & size, ivec2 & pos) = 0; //glfw::createWindow(_mirrorSize); - /* - GLFWwindow *createWindow(const uvec2 &size, const ivec2 &position = ivec2(INT_MIN)) - { - GLFWwindow *window = glfwCreateWindow(size.x, size.y, "glfw", NULL, NULL); // size = mirrorSize - - if (!window) FAIL("Unable to create rendering window\n"); - - if ((position.x > INT_MIN) && (position.y > INT_MIN)) // INT_MIN = -32767 // #define INT_MIN (-2147483647 - 1) - { - glfwSetWindowPos(window, position.x, position.y); - } - - return window; - } - */ - - window = glfwCreateWindow(mirrorSize.x, mirrorSize.y, "glfw", NULL, NULL); - - if (!window) FAIL("Unable to create OpenGL window\n"); - - ////postCreate(); - //glfwSetWindowUserPointer(window, this); //// Useful to hack input callbacks - glfwSetKeyCallback(window, KeyCallback); - glfwMakeContextCurrent(window); - - // Initialize the OpenGL extensions - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) FAIL("GLAD failed\n"); - /* - glewExperimental = GL_TRUE; - if (0 != glewInit()) FAIL("Failed to initialize GLEW\n"); - glGetError(); - - if (GLEW_KHR_debug) - { - GLint v; - glGetIntegerv(GL_CONTEXT_FLAGS, &v); - if (v & GL_CONTEXT_FLAG_DEBUG_BIT) glDebugMessageCallback(glDebugCallbackHandler, this); - } - */ - - ////initGl(); - { - // RiftApp::InitGL() -----> - //GlfwApp::initGl(); // virtual - - // Disable the v-sync for buffer swap - glfwSwapInterval(0); - - ovrTextureSwapChainDesc desc = {}; - desc.Type = ovrTexture_2D; - desc.ArraySize = 1; - desc.Width = renderTargetSize.x; - desc.Height = renderTargetSize.y; - desc.MipLevels = 1; - desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; - desc.SampleCount = 1; - desc.StaticImage = ovrFalse; - - ovrResult result = ovr_CreateTextureSwapChainGL(session, &desc, &eyeTexture); - eyeLayer.ColorTexture[0] = eyeTexture; - - if (!OVR_SUCCESS(result)) FAIL("Failed to create swap textures"); - - int length = 0; - result = ovr_GetTextureSwapChainLength(session, eyeTexture, &length); - - if (!OVR_SUCCESS(result) || !length) FAIL("Unable to count swap chain textures"); - - for (int i = 0; i < length; ++i) - { - GLuint chainTexId; - ovr_GetTextureSwapChainBufferGL(session, eyeTexture, i, &chainTexId); - glBindTexture(GL_TEXTURE_2D, chainTexId); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - glBindTexture(GL_TEXTURE_2D, 0); - - // Set up the framebuffer object - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &depthBuffer); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, renderTargetSize.x, renderTargetSize.y); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - - ovrMirrorTextureDesc mirrorDesc; - memset(&mirrorDesc, 0, sizeof(mirrorDesc)); - mirrorDesc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; - mirrorDesc.Width = mirrorSize.x; - mirrorDesc.Height = mirrorSize.y; - - if (!OVR_SUCCESS(ovr_CreateMirrorTextureGL(session, &mirrorDesc, &mirrorTexture))) FAIL("Could not create mirror texture"); - - glGenFramebuffers(1, &mirrorFbo); - - // RiftApp::InitGL() <------ - - glClearColor(0.2f, 0.2f, 0.2f, 0.0f); - glEnable(GL_DEPTH_TEST); - ovr_RecenterTrackingOrigin(session); - - // TODO: Init cube scene --> cubeScene = std::shared_ptr(new ColorCubeScene()); - } - - while (!glfwWindowShouldClose(window)) - { - frame++; - glfwPollEvents(); - - //update(); - - //draw(); ------> - ovrPosef eyePoses[2]; - ovr_GetEyePoses(session, frame, ovrTrue, viewScaleDesc.HmdToEyeOffset, eyePoses, &eyeLayer.SensorSampleTime); - - int curIndex; - ovr_GetTextureSwapChainCurrentIndex(session, eyeTexture, &curIndex); - GLuint curTexId; - ovr_GetTextureSwapChainBufferGL(session, eyeTexture, curIndex, &curTexId); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, curTexId, 0); - - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - for (int eye = 0; eye < 2; eye++) - { - glViewport(eyeLayer.Viewport[eye].Pos.x, eyeLayer.Viewport[eye].Pos.y, - eyeLayer.Viewport[eye].Size.w, eyeLayer.Viewport[eye].Size.h); - eyeLayer.RenderPose[eye] = eyePoses[eye]; - - //renderScene(_eyeProjections[eye], ovr::toGlm(eyePoses[eye])); --> cubeScene->render(projection, glm::inverse(headPose)); - } - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - ovr_CommitTextureSwapChain(session, eyeTexture); - ovrLayerHeader *headerList = &eyeLayer.Header; - - ovr_SubmitFrame(session, frame, &viewScaleDesc, &headerList, 1); - - GLuint mirrorTextureId; - ovr_GetMirrorTextureBufferGL(session, mirrorTexture, &mirrorTextureId); - glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFbo); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mirrorTextureId, 0); - glBlitFramebuffer(0, 0, mirrorSize.x, mirrorSize.y, 0, mirrorSize.y, mirrorSize.x, 0, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - //draw() <------------- - - glfwSwapBuffers(window); //finishFrame(); - } - - //shutdownGl(); // Delete scene: cubeScene.reset(); - - glfwDestroyWindow(window); - glfwTerminate(); - - ovr_Destroy(session); - ovr_Shutdown(); - - return 0; -} diff --git a/examples/oculus_glfw_sample/raylib_OculusRiftCV1.png b/examples/oculus_glfw_sample/raylib_OculusRiftCV1.png new file mode 100644 index 0000000000000000000000000000000000000000..37a87489243cfe3a28c2b7d119f7390845180794 GIT binary patch literal 218472 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

7`wuly)K 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 0) || (triangles.vCounter > 0) || (quads.vCounter > 0)) { glUseProgram(currentShader.id); - Matrix matMVP = MatrixMultiply(modelview, projection); // Create modelview-projection matrix + Matrix mvp2 = MatrixMultiply(modelview, projection); // Create modelview-projection matrix - glUniformMatrix4fv(currentShader.mvpLoc, 1, false, MatrixToFloat(matMVP)); + glUniformMatrix4fv(currentShader.mvpLoc, 1, false, MatrixToFloat(mvp2)); glUniform1i(currentShader.mapDiffuseLoc, 0); glUniform4f(currentShader.tintColorLoc, 1.0f, 1.0f, 1.0f, 1.0f); } @@ -1348,14 +1297,14 @@ void rlglDrawModel(Model model, Vector3 position, Vector3 rotationAxis, float ro glBindTexture(GL_TEXTURE_2D, model.material.texDiffuse.id); glUniform1i(model.material.shader.mapDiffuseLoc, 0); // Texture fits in active texture unit 0 - if (model.material.texNormal.id != 0) + if ((model.material.texNormal.id != 0) && (model.material.shader.mapNormalLoc != -1)) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, model.material.texNormal.id); glUniform1i(model.material.shader.mapNormalLoc, 1); // Texture fits in active texture unit 1 } - if (model.material.texSpecular.id != 0) + if ((model.material.texSpecular.id != 0) && (model.material.shader.mapSpecularLoc != -1)) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, model.material.texSpecular.id); @@ -1844,7 +1793,9 @@ void rlglGenerateMipmaps(Texture2D texture) // NOTE: Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data free(data); -#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically TraceLog(INFO, "[TEX ID %i] Mipmaps generated automatically", texture.id); @@ -2114,8 +2065,8 @@ Shader LoadShader(char *vsFileName, char *fsFileName) #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Shaders loading from external text file - char *vShaderStr = TextFileRead(vsFileName); - char *fShaderStr = TextFileRead(fsFileName); + char *vShaderStr = ReadTextFile(vsFileName); + char *fShaderStr = ReadTextFile(fsFileName); if ((vShaderStr != NULL) && (fShaderStr != NULL)) { @@ -2123,17 +2074,13 @@ Shader LoadShader(char *vsFileName, char *fsFileName) // After shader loading, we try to load default location names if (shader.id != 0) LoadDefaultShaderLocations(&shader); - else - { - TraceLog(WARNING, "Custom shader could not be loaded"); - shader = defaultShader; - } // Shader strings must be freed free(vShaderStr); free(fShaderStr); } - else + + if (shader.id == 0) { TraceLog(WARNING, "Custom shader could not be loaded"); shader = defaultShader; @@ -2259,7 +2206,7 @@ void SetCustomShader(Shader shader) #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) if (currentShader.id != shader.id) { - rlglDraw(); + //rlglDraw(); currentShader = shader; } #endif @@ -2365,7 +2312,7 @@ void SetBlendMode(int mode) { if ((blendMode != mode) && (mode < 3)) { - rlglDraw(); + //rlglDraw(); switch (mode) { @@ -2379,18 +2326,6 @@ void SetBlendMode(int mode) } } -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -void PrintProjectionMatrix(void) -{ - PrintMatrix(projection); -} - -void PrintModelviewMatrix(void) -{ - PrintMatrix(modelview); -} -#endif - //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- @@ -2432,7 +2367,7 @@ static void LoadCompressedTexture(unsigned char *data, int width, int height, in } } -// Load Shader (Vertex and Fragment) +// Load default shader (Vertex and Fragment) // NOTE: This shader program is used for batch buffers (lines, triangles, quads) static Shader LoadDefaultShader(void) { @@ -2492,7 +2427,7 @@ static Shader LoadDefaultShader(void) if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Default shader loaded successfully", shader.id); else TraceLog(WARNING, "[SHDR ID %i] Default shader could not be loaded", shader.id); - LoadDefaultShaderLocations(&shader); + if (shader.id != 0) LoadDefaultShaderLocations(&shader); return shader; } @@ -2517,43 +2452,24 @@ static void LoadDefaultShaderLocations(Shader *shader) shader->mapSpecularLoc = glGetUniformLocation(shader->id, "texture2"); } -// Read text file -// NOTE: text chars array should be freed manually -static char *TextFileRead(char *fileName) +// Unload default shader +static void UnloadDefaultShader(void) { - FILE *textFile; - char *text = NULL; - - int count = 0; - - if (fileName != NULL) - { - textFile = fopen(fileName,"rt"); - - if (textFile != NULL) - { - fseek(textFile, 0, SEEK_END); - count = ftell(textFile); - rewind(textFile); - - if (count > 0) - { - text = (char *)malloc(sizeof(char)*(count + 1)); - count = fread(text, sizeof(char), count, textFile); - text[count] = '\0'; - } - - fclose(textFile); - } - else TraceLog(WARNING, "[%s] Text file could not be opened", fileName); - } + glUseProgram(0); - return text; + //glDetachShader(defaultShaderProgram, vertexShader); + //glDetachShader(defaultShaderProgram, fragmentShader); + //glDeleteShader(vertexShader); // Already deleted on shader compilation + //glDeleteShader(fragmentShader); // Already deleted on sahder compilation + glDeleteProgram(defaultShader.id); } -// Allocate and initialize float array buffers to store vertex data (lines, triangles, quads) -static void InitializeBuffers(void) +// Load default internal buffers (lines, triangles, quads) +static void LoadDefaultBuffers(void) { + // [CPU] Allocate and initialize float array buffers to store vertex data (lines, triangles, quads) + //-------------------------------------------------------------------------------------------- + // Initialize lines arrays (vertex position and color data) lines.vertices = (float *)malloc(sizeof(float)*3*2*MAX_LINES_BATCH); // 3 float by vertex, 2 vertex by line lines.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*2*MAX_LINES_BATCH); // 4 float by color, 2 colors by line @@ -2607,13 +2523,14 @@ static void InitializeBuffers(void) quads.tcCounter = 0; quads.cCounter = 0; - TraceLog(INFO, "CPU buffers (lines, triangles, quads) initialized successfully"); -} - -// Initialize Vertex Array Objects (Contain VBO) -// NOTE: lines, triangles and quads buffers use currentShader -static void InitializeBuffersGPU(void) -{ + TraceLog(INFO, "Default buffers initialized successfully in CPU (lines, triangles, quads)"); + //-------------------------------------------------------------------------------------------- + + // [GPU] Upload vertex data and initialize VAOs/VBOs (lines, triangles, quads) + // NOTE: Default buffers are linked to use currentShader (defaultShader) + //-------------------------------------------------------------------------------------------- + + // Upload and link lines vertex buffers if (vaoSupported) { // Initialize Lines VAO @@ -2636,10 +2553,10 @@ static void InitializeBuffersGPU(void) glEnableVertexAttribArray(currentShader.colorLoc); glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Lines VAO initialized successfully", vaoLines); - else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Lines VBOs initialized successfully", linesBuffer[0], linesBuffer[1]); - //-------------------------------------------------------------- + if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers (lines) VAO initialized successfully", vaoLines); + else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers (lines) VBOs initialized successfully", linesBuffer[0], linesBuffer[1]); + // Upload and link triangles vertex buffers if (vaoSupported) { // Initialize Triangles VAO @@ -2661,10 +2578,10 @@ static void InitializeBuffersGPU(void) glEnableVertexAttribArray(currentShader.colorLoc); glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Triangles VAO initialized successfully", vaoTriangles); - else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Triangles VBOs initialized successfully", trianglesBuffer[0], trianglesBuffer[1]); - //-------------------------------------------------------------- + if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers (triangles) VAO initialized successfully", vaoTriangles); + else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers (triangles) VBOs initialized successfully", trianglesBuffer[0], trianglesBuffer[1]); + // Upload and link quads vertex buffers if (vaoSupported) { // Initialize Quads VAO @@ -2699,18 +2616,20 @@ static void InitializeBuffersGPU(void) glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short)*6*MAX_QUADS_BATCH, quads.indices, GL_STATIC_DRAW); #endif - if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Quads VAO initialized successfully", vaoQuads); - else TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i][VBO ID %i] Quads VBOs initialized successfully", quadsBuffer[0], quadsBuffer[1], quadsBuffer[2], quadsBuffer[3]); + if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers (quads) VAO initialized successfully", vaoQuads); + else TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i][VBO ID %i] Default buffers (quads) VBOs initialized successfully", quadsBuffer[0], quadsBuffer[1], quadsBuffer[2], quadsBuffer[3]); // Unbind the current VAO if (vaoSupported) glBindVertexArray(0); + //-------------------------------------------------------------------------------------------- } -// Update VBOs with vertex array data +// Update default buffers (VAOs/VBOs) with vertex array data // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) -// TODO: If no data changed on the CPU arrays --> No need to update GPU arrays (change flag required) -static void UpdateBuffers(void) +// TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required) +static void UpdateDefaultBuffers(void) { + // Update lines vertex buffers if (lines.vCounter > 0) { // Activate Lines VAO @@ -2726,8 +2645,8 @@ static void UpdateBuffers(void) //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*lines.cCounter, lines.colors); } - //-------------------------------------------------------------- + // Update triangles vertex buffers if (triangles.vCounter > 0) { // Activate Triangles VAO @@ -2743,8 +2662,8 @@ static void UpdateBuffers(void) //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*triangles.cCounter, triangles.colors); } - //-------------------------------------------------------------- + // Update quads vertex buffers if (quads.vCounter > 0) { // Activate Quads VAO @@ -2766,7 +2685,7 @@ static void UpdateBuffers(void) glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*quads.vCounter, quads.colors); // Another option would be using buffer mapping... - //triangles.vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + //quads.vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); // Now we can modify vertices //glUnmapBuffer(GL_ARRAY_BUFFER); } @@ -2775,6 +2694,83 @@ static void UpdateBuffers(void) // Unbind the current VAO if (vaoSupported) glBindVertexArray(0); } + +// Unload default buffers vertex data from CPU and GPU +static void UnloadDefaultBuffers(void) +{ + // Unbind everything + if (vaoSupported) glBindVertexArray(0); + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + glDisableVertexAttribArray(3); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &linesBuffer[0]); + glDeleteBuffers(1, &linesBuffer[1]); + glDeleteBuffers(1, &trianglesBuffer[0]); + glDeleteBuffers(1, &trianglesBuffer[1]); + glDeleteBuffers(1, &quadsBuffer[0]); + glDeleteBuffers(1, &quadsBuffer[1]); + glDeleteBuffers(1, &quadsBuffer[2]); + glDeleteBuffers(1, &quadsBuffer[3]); + + if (vaoSupported) + { + // Delete VAOs from GPU (VRAM) + glDeleteVertexArrays(1, &vaoLines); + glDeleteVertexArrays(1, &vaoTriangles); + glDeleteVertexArrays(1, &vaoQuads); + } + + // Free vertex arrays memory from CPU (RAM) + free(lines.vertices); + free(lines.colors); + + free(triangles.vertices); + free(triangles.colors); + + free(quads.vertices); + free(quads.texcoords); + free(quads.colors); + free(quads.indices); +} + +// Read text data from file +// NOTE: text chars array should be freed manually +static char *ReadTextFile(const char *fileName) +{ + FILE *textFile; + char *text = NULL; + + int count = 0; + + if (fileName != NULL) + { + textFile = fopen(fileName,"rt"); + + if (textFile != NULL) + { + fseek(textFile, 0, SEEK_END); + count = ftell(textFile); + rewind(textFile); + + if (count > 0) + { + text = (char *)malloc(sizeof(char)*(count + 1)); + count = fread(text, sizeof(char), count, textFile); + text[count] = '\0'; + } + + fclose(textFile); + } + else TraceLog(WARNING, "[%s] Text file could not be opened", fileName); + } + + return text; +} #endif //defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) #if defined(GRAPHICS_API_OPENGL_11) @@ -2905,7 +2901,6 @@ static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight) #endif #if defined(RLGL_STANDALONE) - // Output a trace log message // NOTE: Expected msgType: (0)Info, (1)Error, (2)Warning static void TraceLog(int msgType, const char *text, ...) diff --git a/examples/oculus_glfw_sample/rlgl.h b/examples/oculus_glfw_sample/rlgl.h index 714961e11..99427929a 100644 --- a/examples/oculus_glfw_sample/rlgl.h +++ b/examples/oculus_glfw_sample/rlgl.h @@ -273,7 +273,7 @@ int rlGetVersion(void); // Returns current OpenGL versio //------------------------------------------------------------------------------------ void rlglInit(void); // Initialize rlgl (shaders, VAO, VBO...) void rlglClose(void); // De-init rlgl -void rlglDraw(void); // Draw VAO/VBO +void rlglDraw(Matrix mvp); // Draw VAO/VBO void rlglInitGraphics(int offsetX, int offsetY, int width, int height); // Initialize Graphics (OpenGL stuff) unsigned int rlglLoadTexture(void *data, int width, int height, int textureFormat, int mipmapCount); // Load texture in GPU @@ -292,11 +292,6 @@ Vector3 rlglUnproject(Vector3 source, Matrix proj, Matrix view); // Get world unsigned char *rlglReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) void *rlglReadTexturePixels(Texture2D texture); // Read texture pixel data -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -void PrintProjectionMatrix(void); // DEBUG: Print projection matrix -void PrintModelviewMatrix(void); // DEBUG: Print modelview matrix -#endif - #if defined(RLGL_STANDALONE) //------------------------------------------------------------------------------------ // Shaders System Functions (Module: rlgl) @@ -309,13 +304,10 @@ void SetCustomShader(Shader shader); // Set custo void SetDefaultShader(void); // Set default shader to be used in batch draw void SetModelShader(Model *model, Shader shader); // Link a shader to a model -int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); // Set shader uniform value (float) -void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size); // Set shader uniform value (int) -void SetShaderMapDiffuse(Shader *shader, Texture2D texture); // Default diffuse shader map texture assignment -void SetShaderMapNormal(Shader *shader, const char *uniformName, Texture2D texture); // Normal map texture shader assignment -void SetShaderMapSpecular(Shader *shader, const char *uniformName, Texture2D texture); // Specular map texture shader assignment -void SetShaderMap(Shader *shader, int mapLocation, Texture2D texture, int textureUnit); // TODO: Generic shader map assignment +int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); // Set shader uniform value (float) +void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size); // Set shader uniform value (int) +void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) void SetBlendMode(int mode); // Set blending mode (alpha, additive, multiplied) #endif From ec72a8868e61507a3dfdc83bfe30c7110fc3051f Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 7 May 2016 18:04:22 +0200 Subject: [PATCH 02/11] Comment tweak --- src/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core.c b/src/core.c index 669010f95..54d42f837 100644 --- a/src/core.c +++ b/src/core.c @@ -1448,6 +1448,7 @@ static void InitDisplay(int width, int height) glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! // Other values: GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_FALSE); // Fordward Compatibility Hint: Only 3.3 and above! + //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); } if (fullscreen) From 7ab008878afa202b4f2e579567be7a7d87242661 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 7 May 2016 18:07:15 +0200 Subject: [PATCH 03/11] Library redesign to accomodate materials system --- src/models.c | 164 +++++++-- src/raylib.h | 31 +- src/rlgl.c | 989 +++++++++++++++++++++++++-------------------------- src/rlgl.h | 15 +- 4 files changed, 641 insertions(+), 558 deletions(-) diff --git a/src/models.c b/src/models.c index 0bb2b8d69..9b139120b 100644 --- a/src/models.c +++ b/src/models.c @@ -55,7 +55,9 @@ extern unsigned int whiteTexture; //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static Mesh LoadOBJ(const char *fileName); +static Mesh LoadOBJ(const char *fileName); // Load OBJ mesh data +static Material LoadMTL(const char *fileName); // Load MTL material data + static Mesh GenMeshHeightmap(Image image, Vector3 size); static Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); @@ -542,24 +544,19 @@ void DrawGizmo(Vector3 position) Model LoadModel(const char *fileName) { Model model = { 0 }; - Mesh mesh = { 0 }; - // NOTE: Initialize default data for model in case loading fails, maybe a cube? + // TODO: Initialize default data for model in case loading fails, maybe a cube? - if (strcmp(GetExtension(fileName),"obj") == 0) mesh = LoadOBJ(fileName); + if (strcmp(GetExtension(fileName),"obj") == 0) model.mesh = LoadOBJ(fileName); else TraceLog(WARNING, "[%s] Model extension not recognized, it can't be loaded", fileName); - // NOTE: At this point we have all vertex, texcoord, normal data for the model in mesh struct - - if (mesh.vertexCount == 0) TraceLog(WARNING, "Model could not be loaded"); + if (model.mesh.vertexCount == 0) TraceLog(WARNING, "Model could not be loaded"); else { - // NOTE: model properties (transform, texture, shader) are initialized inside rlglLoadModel() - model = rlglLoadModel(mesh); // Upload vertex data to GPU - - // NOTE: Now that vertex data is uploaded to GPU VRAM, we can free arrays from CPU RAM - // We don't need CPU vertex data on OpenGL 3.3 or ES2... for static meshes... - // ...but we could keep CPU vertex data in case we need to update the mesh + rlglLoadMesh(&model.mesh); // Upload vertex data to GPU + + model.transform = MatrixIdentity(); + model.material = LoadDefaultMaterial(); } return model; @@ -568,12 +565,12 @@ Model LoadModel(const char *fileName) // Load a 3d model (from vertex data) Model LoadModelEx(Mesh data) { - Model model; + Model model = { 0 }; - // NOTE: model properties (transform, texture, shader) are initialized inside rlglLoadModel() - model = rlglLoadModel(data); // Upload vertex data to GPU + rlglLoadMesh(&data); // Upload vertex data to GPU - // NOTE: Vertex data is managed externally, must be deallocated manually + model.transform = MatrixIdentity(); + model.material = LoadDefaultMaterial(); return model; } @@ -582,8 +579,14 @@ Model LoadModelEx(Mesh data) // NOTE: model map size is defined in generic units Model LoadHeightmap(Image heightmap, Vector3 size) { - Mesh mesh = GenMeshHeightmap(heightmap, size); - Model model = rlglLoadModel(mesh); + Model model = { 0 }; + + model.mesh = GenMeshHeightmap(heightmap, size); + + rlglLoadMesh(&model.mesh); + + model.transform = MatrixIdentity(); + model.material = LoadDefaultMaterial(); return model; } @@ -591,8 +594,14 @@ Model LoadHeightmap(Image heightmap, Vector3 size) // Load a map image as a 3d model (cubes based) Model LoadCubicmap(Image cubicmap) { - Mesh mesh = GenMeshCubicmap(cubicmap, (Vector3){ 1.0, 1.0, 1.5f }); - Model model = rlglLoadModel(mesh); + Model model = { 0 }; + + model.mesh = GenMeshCubicmap(cubicmap, (Vector3){ 1.0, 1.0, 1.5f }); + + rlglLoadMesh(&model.mesh); + + model.transform = MatrixIdentity(); + model.material = LoadDefaultMaterial(); return model; } @@ -613,13 +622,44 @@ void UnloadModel(Model model) rlDeleteBuffers(model.mesh.vboId[0]); // vertex rlDeleteBuffers(model.mesh.vboId[1]); // texcoords rlDeleteBuffers(model.mesh.vboId[2]); // normals - //rlDeleteBuffers(model.mesh.vboId[3]); // texcoords2 (NOT USED) + //rlDeleteBuffers(model.mesh.vboId[3]); // colors (NOT USED) //rlDeleteBuffers(model.mesh.vboId[4]); // tangents (NOT USED) - //rlDeleteBuffers(model.mesh.vboId[5]); // colors (NOT USED) + //rlDeleteBuffers(model.mesh.vboId[5]); // texcoords2 (NOT USED) rlDeleteVertexArrays(model.mesh.vaoId); } +// Load material data (from file) +Material LoadMaterial(const char *fileName) +{ + Material material = { 0 }; + + if (strcmp(GetExtension(fileName),"mtl") == 0) material = LoadMTL(fileName); + else TraceLog(WARNING, "[%s] Material extension not recognized, it can't be loaded", fileName); + + return material; +} + +// Load default material (uses default models shader) +Material LoadDefaultMaterial(void) +{ + Material material = { 0 }; + + material.shader = GetDefaultShader(); + material.texDiffuse = GetDefaultTexture(); // White texture (1x1 pixel) + //material.texNormal; // NOTE: By default, not set + //material.texSpecular; // NOTE: By default, not set + + material.colDiffuse = WHITE; // Diffuse color + material.colAmbient = WHITE; // Ambient color + material.colSpecular = WHITE; // Specular color + + material.glossiness = 100.0f; // Glossiness level + material.normalDepth = 1.0f; // Normal map depth + + return material; +} + // Link a texture to a model void SetModelTexture(Model *model, Texture2D texture) { @@ -1100,31 +1140,59 @@ void DrawModel(Model model, Vector3 position, float scale, Color tint) { Vector3 vScale = { scale, scale, scale }; Vector3 rotationAxis = { 0.0f, 0.0f, 0.0f }; - + DrawModelEx(model, position, rotationAxis, 0.0f, vScale, tint); } // Draw a model with extended parameters void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) { - // NOTE: Rotation must be provided in degrees, it's converted to radians inside rlglDrawModel() - rlglDrawModel(model, position, rotationAxis, rotationAngle, scale, tint, false); + // Calculate transformation matrix from function parameters + // Get transform matrix (rotation -> scale -> translation) + Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD); + Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); + Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); + + // Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform) + //Matrix matModel = MatrixMultiply(model.transform, matTransform); // Transform to world-space coordinates + + model.transform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation); + model.material.colDiffuse = tint; + + rlglDrawEx(model.mesh, model.material, model.transform, false); } // Draw a model wires (with texture if set) -void DrawModelWires(Model model, Vector3 position, float scale, Color color) +void DrawModelWires(Model model, Vector3 position, float scale, Color tint) { Vector3 vScale = { scale, scale, scale }; Vector3 rotationAxis = { 0.0f, 0.0f, 0.0f }; - rlglDrawModel(model, position, rotationAxis, 0.0f, vScale, color, true); + // Calculate transformation matrix from function parameters + // Get transform matrix (rotation -> scale -> translation) + Matrix matRotation = MatrixRotate(rotationAxis, 0.0f); + Matrix matScale = MatrixScale(vScale.x, vScale.y, vScale.z); + Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); + + model.transform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation); + model.material.colDiffuse = tint; + + rlglDrawEx(model.mesh, model.material, model.transform, true); } // Draw a model wires (with texture if set) with extended parameters void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) { - // NOTE: Rotation must be provided in degrees, it's converted to radians inside rlglDrawModel() - rlglDrawModel(model, position, rotationAxis, rotationAngle, scale, tint, true); + // Calculate transformation matrix from function parameters + // Get transform matrix (rotation -> scale -> translation) + Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD); + Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); + Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); + + model.transform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation); + model.material.colDiffuse = tint; + + rlglDrawEx(model.mesh, model.material, model.transform, true); } // Draw a billboard @@ -1856,3 +1924,39 @@ static Mesh LoadOBJ(const char *fileName) return mesh; } + +// Load MTL material data +static Material LoadMTL(const char *fileName) +{ + Material material = { 0 }; + + // TODO: Load mtl file + + char dataType; + char comments[200]; + + FILE *mtlFile; + + mtlFile = fopen(fileName, "rt"); + + if (mtlFile == NULL) + { + TraceLog(WARNING, "[%s] MTL file could not be opened", fileName); + return material; + } + + // First reading pass: Get numVertex, numNormals, numTexCoords, numTriangles + // NOTE: vertex, texcoords and normals could be optimized (to be used indexed on faces definition) + // NOTE: faces MUST be defined as TRIANGLES (3 vertex per face) + while(!feof(mtlFile)) + { + fscanf(mtlFile, "%c", &dataType); + } + + fclose(mtlFile); + + // NOTE: At this point we have all material data + TraceLog(INFO, "[%s] Material loaded successfully", fileName); + + return material; +} diff --git a/src/raylib.h b/src/raylib.h index 8af7d2fb8..c88a60f45 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -387,10 +387,10 @@ typedef struct Shader { unsigned int id; // Shader program id // Variable attributes locations - int vertexLoc; // Vertex attribute location point (vertex shader) - int texcoordLoc; // Texcoord attribute location point (vertex shader) - int normalLoc; // Normal attribute location point (vertex shader) - int colorLoc; // Color attibute location point (vertex shader) + int vertexLoc; // Vertex attribute location point (default-location = 0) + int texcoordLoc; // Texcoord attribute location point (default-location = 1) + int normalLoc; // Normal attribute location point (default-location = 2) + int colorLoc; // Color attibute location point (default-location = 3) // Uniform locations int mvpLoc; // ModelView-Projection matrix uniform location point (vertex shader) @@ -801,17 +801,20 @@ void DrawGizmo(Vector3 position); //------------------------------------------------------------------------------------ // Model 3d Loading and Drawing Functions (Module: models) //------------------------------------------------------------------------------------ -Model LoadModel(const char *fileName); // Load a 3d model (.OBJ) -Model LoadModelEx(Mesh data); // Load a 3d model (from mesh data) -//Model LoadModelFromRES(const char *rresName, int resId); // TODO: Load a 3d model from rRES file (raylib Resource) -Model LoadHeightmap(Image heightmap, Vector3 size); // Load a heightmap image as a 3d model -Model LoadCubicmap(Image cubicmap); // Load a map image as a 3d model (cubes based) -void UnloadModel(Model model); // Unload 3d model from memory -void SetModelTexture(Model *model, Texture2D texture); // Link a texture to a model +Model LoadModel(const char *fileName); // Load a 3d model (.OBJ) +Model LoadModelEx(Mesh data); // Load a 3d model (from mesh data) +//Model LoadModelFromRES(const char *rresName, int resId); // TODO: Load a 3d model from rRES file (raylib Resource) +Model LoadHeightmap(Image heightmap, Vector3 size); // Load a heightmap image as a 3d model +Model LoadCubicmap(Image cubicmap); // Load a map image as a 3d model (cubes based) +void UnloadModel(Model model); // Unload 3d model from memory +void SetModelTexture(Model *model, Texture2D texture); // Link a texture to a model + +Material LoadMaterial(const char *fileName); // Load material data (from file) +Material LoadDefaultMaterial(void); // Load default material (uses default models shader) void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters -void DrawModelWires(Model model, Vector3 position, float scale, Color color); // Draw a model wires (with texture if set) +void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) @@ -832,11 +835,11 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p // NOTE: This functions are useless when using OpenGL 1.1 //------------------------------------------------------------------------------------ Shader LoadShader(char *vsFileName, char *fsFileName); // Load a custom shader and bind default locations -unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr); // Load custom shaders strings and return program id void UnloadShader(Shader shader); // Unload a custom shader from memory void SetDefaultShader(void); // Set default shader to be used in batch draw void SetCustomShader(Shader shader); // Set custom shader to be used in batch draw -void SetModelShader(Model *model, Shader shader); // Link a shader to a model +Shader GetDefaultShader(void); // Get default shader +Texture2D GetDefaultTexture(void); // Get default texture int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); // Set shader uniform value (float) diff --git a/src/rlgl.c b/src/rlgl.c index 9112e47e2..02649e305 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -255,14 +255,16 @@ unsigned int whiteTexture; //---------------------------------------------------------------------------------- #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat); +static unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr); // Load custom shader strings and return program id -static Shader LoadDefaultShader(void); -static void LoadDefaultShaderLocations(Shader *shader); -static void UnloadDefaultShader(void); +static Shader LoadDefaultShader(void); // Load default shader (just vertex positioning and texture coloring) +static void LoadDefaultShaderLocations(Shader *shader); // Bind default shader locations (attributes and uniforms) +static void UnloadDefaultShader(void); // Unload default shader -static void LoadDefaultBuffers(void); -static void UpdateDefaultBuffers(void); -static void UnloadDefaultBuffers(void); +static void LoadDefaultBuffers(void); // Load default internal buffers (lines, triangles, quads) +static void UpdateDefaultBuffers(void); // Update default internal buffers (VAOs/VBOs) with vertex data +static void DrawDefaultBuffers(void); // Draw default internal buffers vertex data +static void UnloadDefaultBuffers(void); // Unload default internal buffers vertex data from CPU and GPU static char *ReadTextFile(const char *fileName); #endif @@ -1061,167 +1063,12 @@ void rlglDraw(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) UpdateDefaultBuffers(); - - if ((lines.vCounter > 0) || (triangles.vCounter > 0) || (quads.vCounter > 0)) - { - glUseProgram(currentShader.id); - - Matrix matMVP = MatrixMultiply(modelview, projection); // Create modelview-projection matrix - - glUniformMatrix4fv(currentShader.mvpLoc, 1, false, MatrixToFloat(matMVP)); - glUniform1i(currentShader.mapDiffuseLoc, 0); - glUniform4f(currentShader.tintColorLoc, 1.0f, 1.0f, 1.0f, 1.0f); - } - - // NOTE: We draw in this order: lines, triangles, quads - - if (lines.vCounter > 0) - { - glBindTexture(GL_TEXTURE_2D, whiteTexture); - - if (vaoSupported) - { - glBindVertexArray(vaoLines); - } - else - { - glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); - glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.vertexLoc); - - if (currentShader.colorLoc != -1) - { - glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); - glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(currentShader.colorLoc); - } - } - - glDrawArrays(GL_LINES, 0, lines.vCounter); - - if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - } - - if (triangles.vCounter > 0) - { - glBindTexture(GL_TEXTURE_2D, whiteTexture); - - if (vaoSupported) - { - glBindVertexArray(vaoTriangles); - } - else - { - glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); - glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.vertexLoc); - - if (currentShader.colorLoc != -1) - { - glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); - glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(currentShader.colorLoc); - } - } - - glDrawArrays(GL_TRIANGLES, 0, triangles.vCounter); - - if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - } - - if (quads.vCounter > 0) - { - int quadsCount = 0; - int numIndicesToProcess = 0; - int indicesOffset = 0; - - if (vaoSupported) - { - glBindVertexArray(vaoQuads); - } - else - { - // Enable vertex attributes - glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); - glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.vertexLoc); - - glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); - glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.texcoordLoc); - - if (currentShader.colorLoc != -1) - { - glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); - glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(currentShader.colorLoc); - } - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadsBuffer[3]); - } - - //TraceLog(DEBUG, "Draws required per frame: %i", drawsCounter); - - for (int i = 0; i < drawsCounter; i++) - { - quadsCount = draws[i].vertexCount/4; - numIndicesToProcess = quadsCount*6; // Get number of Quads * 6 index by Quad - - //TraceLog(DEBUG, "Quads to render: %i - Vertex Count: %i", quadsCount, draws[i].vertexCount); - - glBindTexture(GL_TEXTURE_2D, draws[i].textureId); - - // NOTE: The final parameter tells the GPU the offset in bytes from the start of the index buffer to the location of the first index to process -#if defined(GRAPHICS_API_OPENGL_33) - glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_INT, (GLvoid*) (sizeof(GLuint) * indicesOffset)); -#elif defined(GRAPHICS_API_OPENGL_ES2) - glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_SHORT, (GLvoid*) (sizeof(GLushort) * indicesOffset)); -#endif - //GLenum err; - //if ((err = glGetError()) != GL_NO_ERROR) TraceLog(INFO, "OpenGL error: %i", (int)err); //GL_INVALID_ENUM! - - indicesOffset += draws[i].vertexCount/4*6; - } - - if (!vaoSupported) - { - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - - glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures - } - - if (vaoSupported) glBindVertexArray(0); // Unbind VAO - - glUseProgram(0); // Unbind shader program - - // Reset draws counter - drawsCounter = 1; - draws[0].textureId = whiteTexture; - draws[0].vertexCount = 0; - - // Reset vertex counters for next frame - lines.vCounter = 0; - lines.cCounter = 0; - - triangles.vCounter = 0; - triangles.cCounter = 0; - - quads.vCounter = 0; - quads.tcCounter = 0; - quads.cCounter = 0; - - // Reset depth for next draw - currentDepth = -1.0f; + DrawDefaultBuffers(); #endif } -// Draw a 3d model -// NOTE: Model transform can come within model struct -void rlglDrawModel(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color color, bool wires) +// Draw a 3d mesh with material and transform +void rlglDrawEx(Mesh mesh, Material material, Matrix transform, bool wires) { #if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) // NOTE: glPolygonMode() not available on OpenGL ES @@ -1230,27 +1077,21 @@ void rlglDrawModel(Model model, Vector3 position, Vector3 rotationAxis, float ro #if defined(GRAPHICS_API_OPENGL_11) glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, model.material.texDiffuse.id); + glBindTexture(GL_TEXTURE_2D, material.texDiffuse.id); // NOTE: On OpenGL 1.1 we use Vertex Arrays to draw model glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Enable texture coords array glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array - glVertexPointer(3, GL_FLOAT, 0, model.mesh.vertices); // Pointer to vertex coords array - glTexCoordPointer(2, GL_FLOAT, 0, model.mesh.texcoords); // Pointer to texture coords array - glNormalPointer(GL_FLOAT, 0, model.mesh.normals); // Pointer to normals array - //glColorPointer(4, GL_UNSIGNED_BYTE, 0, model.mesh.colors); // Pointer to colors array (NOT USED) + glVertexPointer(3, GL_FLOAT, 0, mesh.vertices); // Pointer to vertex coords array + glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords); // Pointer to texture coords array + glNormalPointer(GL_FLOAT, 0, mesh.normals); // Pointer to normals array + //glColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh.colors); // Pointer to colors array (NOT USED) - rlPushMatrix(); - rlTranslatef(position.x, position.y, position.z); - rlScalef(scale.x, scale.y, scale.z); - rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z); - - rlColor4ub(color.r, color.g, color.b, color.a); - - glDrawArrays(GL_TRIANGLES, 0, model.mesh.vertexCount); - rlPopMatrix(); + rlMultMatrixf(MatrixToFloat(transform)); + + glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount); glDisableClientState(GL_VERTEX_ARRAY); // Disable vertex array glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Disable texture coords array @@ -1261,97 +1102,83 @@ void rlglDrawModel(Model model, Vector3 position, Vector3 rotationAxis, float ro #endif #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(model.material.shader.id); + glUseProgram(material.shader.id); // At this point the modelview matrix just contains the view matrix (camera) // That's because Begin3dMode() sets it an no model-drawing function modifies it, all use rlPushMatrix() and rlPopMatrix() Matrix matView = modelview; // View matrix (camera) Matrix matProjection = projection; // Projection matrix (perspective) - - // Calculate transformation matrix from function parameters - // Get transform matrix (rotation -> scale -> translation) - Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD); - Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); - Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); - Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation); - - // Combine model internal transformation matrix (model.transform) with matrix generated by function parameters (matTransform) - Matrix matModel = MatrixMultiply(model.transform, matTransform); // Transform to world-space coordinates - + // Calculate model-view matrix combining matModel and matView - Matrix matModelView = MatrixMultiply(matModel, matView); // Transform to camera-space coordinates + Matrix matModelView = MatrixMultiply(transform, matView); // Transform to camera-space coordinates // Calculate model-view-projection matrix (MVP) Matrix matMVP = MatrixMultiply(matModelView, matProjection); // Transform to screen-space coordinates // Send combined model-view-projection matrix to shader - glUniformMatrix4fv(model.material.shader.mvpLoc, 1, false, MatrixToFloat(matMVP)); + glUniformMatrix4fv(material.shader.mvpLoc, 1, false, MatrixToFloat(matMVP)); - // Apply color tinting to model + // Apply color tinting (material.colDiffuse) // NOTE: Just update one uniform on fragment shader - float vColor[4] = { (float)color.r/255, (float)color.g/255, (float)color.b/255, (float)color.a/255 }; - glUniform4fv(model.material.shader.tintColorLoc, 1, vColor); + float vColor[4] = { (float)material.colDiffuse.r/255, (float)material.colDiffuse.g/255, (float)material.colDiffuse.b/255, (float)material.colDiffuse.a/255 }; + glUniform4fv(material.shader.tintColorLoc, 1, vColor); // Set shader textures (diffuse, normal, specular) glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, model.material.texDiffuse.id); - glUniform1i(model.material.shader.mapDiffuseLoc, 0); // Texture fits in active texture unit 0 + glBindTexture(GL_TEXTURE_2D, material.texDiffuse.id); + glUniform1i(material.shader.mapDiffuseLoc, 0); // Texture fits in active texture unit 0 - if ((model.material.texNormal.id != 0) && (model.material.shader.mapNormalLoc != -1)) + if ((material.texNormal.id != 0) && (material.shader.mapNormalLoc != -1)) { glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, model.material.texNormal.id); - glUniform1i(model.material.shader.mapNormalLoc, 1); // Texture fits in active texture unit 1 + glBindTexture(GL_TEXTURE_2D, material.texNormal.id); + glUniform1i(material.shader.mapNormalLoc, 1); // Texture fits in active texture unit 1 } - if ((model.material.texSpecular.id != 0) && (model.material.shader.mapSpecularLoc != -1)) + if ((material.texSpecular.id != 0) && (material.shader.mapSpecularLoc != -1)) { glActiveTexture(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, model.material.texSpecular.id); - glUniform1i(model.material.shader.mapSpecularLoc, 2); // Texture fits in active texture unit 2 + glBindTexture(GL_TEXTURE_2D, material.texSpecular.id); + glUniform1i(material.shader.mapSpecularLoc, 2); // Texture fits in active texture unit 2 } if (vaoSupported) { - glBindVertexArray(model.mesh.vaoId); + glBindVertexArray(mesh.vaoId); } else { - // Bind model VBO data: vertex position - glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[0]); - glVertexAttribPointer(model.material.shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(model.material.shader.vertexLoc); + // Bind mesh VBO data: vertex position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]); + glVertexAttribPointer(material.shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.vertexLoc); - // Bind model VBO data: vertex texcoords - glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[1]); - glVertexAttribPointer(model.material.shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(model.material.shader.texcoordLoc); + // Bind mesh VBO data: vertex texcoords (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]); + glVertexAttribPointer(material.shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.texcoordLoc); - // Bind model VBO data: vertex normals (if available) - if (model.material.shader.normalLoc != -1) + // Bind mesh VBO data: vertex normals (shader-location = 2, if available) + if (material.shader.normalLoc != -1) { - glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[2]); - glVertexAttribPointer(model.material.shader.normalLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(model.material.shader.normalLoc); + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]); + glVertexAttribPointer(material.shader.normalLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.normalLoc); } - // TODO: Bind model VBO data: colors, tangents, texcoords2 (if available) + // TODO: Bind mesh VBO data: colors, tangents, texcoords2 (if available) } // Draw call! - glDrawArrays(GL_TRIANGLES, 0, model.mesh.vertexCount); - - //glDisableVertexAttribArray(model.shader.vertexLoc); - //glDisableVertexAttribArray(model.shader.texcoordLoc); - //if (model.shader.normalLoc != -1) glDisableVertexAttribArray(model.shader.normalLoc); + glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount); - if (model.material.texNormal.id != 0) + if (material.texNormal.id != 0) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); } - if (model.material.texSpecular.id != 0) + if (material.texSpecular.id != 0) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, 0); @@ -1808,95 +1635,75 @@ void rlglGenerateMipmaps(Texture2D texture) glBindTexture(GL_TEXTURE_2D, 0); } -// Load vertex data into a VAO (if supported) and VBO -Model rlglLoadModel(Mesh mesh) +// Upload vertex data into a VAO (if supported) and VBO +void rlglLoadMesh(Mesh *mesh) { - Model model; - - model.mesh = mesh; - model.mesh.vaoId = 0; // Vertex Array Object - model.mesh.vboId[0] = 0; // Vertex positions VBO - model.mesh.vboId[1] = 0; // Vertex texcoords VBO - model.mesh.vboId[2] = 0; // Vertex normals VBO + mesh->vaoId = 0; // Vertex Array Object + mesh->vboId[0] = 0; // Vertex positions VBO + mesh->vboId[1] = 0; // Vertex texcoords VBO + mesh->vboId[2] = 0; // Vertex normals VBO + mesh->vboId[3] = 0; // Vertex color VBO + mesh->vboId[4] = 0; // Vertex tangent VBO + mesh->vboId[5] = 0; // Vertex texcoord2 VBO // TODO: Consider attributes: color, texcoords2, tangents (if available) - model.transform = MatrixIdentity(); - -#if defined(GRAPHICS_API_OPENGL_11) - model.material.texDiffuse.id = 0; // No texture required - model.material.shader.id = 0; // No shader used - -#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - model.material.shader = defaultShader; // Default model shader - - model.material.texDiffuse.id = whiteTexture; // Default whiteTexture - model.material.texDiffuse.width = 1; // Default whiteTexture width - model.material.texDiffuse.height = 1; // Default whiteTexture height - model.material.texDiffuse.format = UNCOMPRESSED_R8G8B8A8; // Default whiteTexture format - - model.material.texNormal.id = 0; // By default, no normal texture - model.material.texSpecular.id = 0; // By default, no specular texture - - // TODO: Fill default material properties (color, glossiness...) - - GLuint vaoModel = 0; // Vertex Array Objects (VAO) - GLuint vertexBuffer[3]; // Vertex Buffer Objects (VBO) +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + GLuint vaoId = 0; // Vertex Array Objects (VAO) + GLuint vboId[3]; // Vertex Buffer Objects (VBOs) if (vaoSupported) { // Initialize Quads VAO (Buffer A) - glGenVertexArrays(1, &vaoModel); - glBindVertexArray(vaoModel); + glGenVertexArrays(1, &vaoId); + glBindVertexArray(vaoId); } // Create buffers for our vertex data (positions, texcoords, normals) - glGenBuffers(3, vertexBuffer); - - // NOTE: Default shader is assigned to model, so vbo buffers are properly linked to vertex attribs - // If model shader is changed, vbo buffers must be re-assigned to new location points (previously loaded) - - // Enable vertex attributes: position - glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[0]); - glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh.vertexCount, mesh.vertices, GL_STATIC_DRAW); - glVertexAttribPointer(model.material.shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(model.material.shader.vertexLoc); - - // Enable vertex attributes: texcoords - glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[1]); - glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh.vertexCount, mesh.texcoords, GL_STATIC_DRAW); - glVertexAttribPointer(model.material.shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(model.material.shader.texcoordLoc); - - // Enable vertex attributes: normals - glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[2]); - glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh.vertexCount, mesh.normals, GL_STATIC_DRAW); - glVertexAttribPointer(model.material.shader.normalLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(model.material.shader.normalLoc); - - glVertexAttrib4f(model.material.shader.colorLoc, 1.0f, 1.0f, 1.0f, 1.0f); // Color vertex attribute set to default: WHITE - glDisableVertexAttribArray(model.material.shader.colorLoc); + glGenBuffers(3, vboId); + + // NOTE: Attributes must be uploaded considering default locations points + + // Enable vertex attributes: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, vboId[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->vertices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(0); + + // Enable vertex attributes: texcoords (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, vboId[1]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh->vertexCount, mesh->texcoords, GL_STATIC_DRAW); + glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(1); + + // Enable vertex attributes: normals (shader-location = 2) + glBindBuffer(GL_ARRAY_BUFFER, vboId[2]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->normals, GL_STATIC_DRAW); + glVertexAttribPointer(2, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(2); + + // Default color vertex attribute (shader-location = 3) + glVertexAttrib4f(3, 1.0f, 1.0f, 1.0f, 1.0f); // Color vertex attribute set to default: WHITE + glDisableVertexAttribArray(3); - model.mesh.vboId[0] = vertexBuffer[0]; // Vertex position VBO - model.mesh.vboId[1] = vertexBuffer[1]; // Texcoords VBO - model.mesh.vboId[2] = vertexBuffer[2]; // Normals VBO + mesh->vboId[0] = vboId[0]; // Vertex position VBO + mesh->vboId[1] = vboId[1]; // Texcoords VBO + mesh->vboId[2] = vboId[2]; // Normals VBO if (vaoSupported) { - if (vaoModel > 0) + if (vaoId > 0) { - model.mesh.vaoId = vaoModel; - TraceLog(INFO, "[VAO ID %i] Model uploaded successfully to VRAM (GPU)", vaoModel); + mesh->vaoId = vaoId; + TraceLog(INFO, "[VAO ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId); } - else TraceLog(WARNING, "Model could not be uploaded to VRAM (GPU)"); + else TraceLog(WARNING, "Mesh could not be uploaded to VRAM (GPU)"); } else { - TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i] Model uploaded successfully to VRAM (GPU)", model.mesh.vboId[0], model.mesh.vboId[1], model.mesh.vboId[2]); + TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vboId[0], mesh->vboId[1], mesh->vboId[2]); } #endif - - return model; } // Read screen pixel data (color buffer) @@ -2090,36 +1897,202 @@ Shader LoadShader(char *vsFileName, char *fsFileName) return shader; } -// Load custom shader strings and return program id -unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr) +// Unload a custom shader from memory +void UnloadShader(Shader shader) +{ + if (shader.id != 0) + { + rlDeleteShader(shader.id); + TraceLog(INFO, "[SHDR ID %i] Unloaded shader program data", shader.id); + } +} + +// Set custom shader to be used on batch draw +void SetCustomShader(Shader shader) { - unsigned int program = 0; - #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - GLuint vertexShader; - GLuint fragmentShader; + if (currentShader.id != shader.id) + { + rlglDraw(); + currentShader = shader; + } +#endif +} - vertexShader = glCreateShader(GL_VERTEX_SHADER); - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); +// Set default shader to be used in batch draw +void SetDefaultShader(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + SetCustomShader(defaultShader); +#endif +} - const char *pvs = vShaderStr; - const char *pfs = fShaderStr; +// Get default shader +Shader GetDefaultShader(void) +{ + return defaultShader; +} - glShaderSource(vertexShader, 1, &pvs, NULL); - glShaderSource(fragmentShader, 1, &pfs, NULL); +// Get shader uniform location +int GetShaderLocation(Shader shader, const char *uniformName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetUniformLocation(shader.id, uniformName); + + if (location == -1) TraceLog(WARNING, "[SHDR ID %i] Shader location for %s could not be found", shader.id, uniformName); +#endif + return location; +} - GLint success = 0; +// Set shader uniform value (float) +void SetShaderValue(Shader shader, int uniformLoc, float *value, int size) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glUseProgram(shader.id); - glCompileShader(vertexShader); + if (size == 1) glUniform1fv(uniformLoc, 1, value); // Shader uniform type: float + else if (size == 2) glUniform2fv(uniformLoc, 1, value); // Shader uniform type: vec2 + else if (size == 3) glUniform3fv(uniformLoc, 1, value); // Shader uniform type: vec3 + else if (size == 4) glUniform4fv(uniformLoc, 1, value); // Shader uniform type: vec4 + else TraceLog(WARNING, "Shader value float array size not supported"); + + glUseProgram(0); +#endif +} - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); +// Set shader uniform value (int) +void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glUseProgram(shader.id); - if (success != GL_TRUE) - { - TraceLog(WARNING, "[VSHDR ID %i] Failed to compile vertex shader...", vertexShader); + if (size == 1) glUniform1iv(uniformLoc, 1, value); // Shader uniform type: int + else if (size == 2) glUniform2iv(uniformLoc, 1, value); // Shader uniform type: ivec2 + else if (size == 3) glUniform3iv(uniformLoc, 1, value); // Shader uniform type: ivec3 + else if (size == 4) glUniform4iv(uniformLoc, 1, value); // Shader uniform type: ivec4 + else TraceLog(WARNING, "Shader value int array size not supported"); + + glUseProgram(0); +#endif +} - int maxLength = 0; - int length; +// Set shader uniform value (matrix 4x4) +void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glUseProgram(shader.id); + + glUniformMatrix4fv(uniformLoc, 1, false, MatrixToFloat(mat)); + + glUseProgram(0); +#endif +} + +// Set blending mode (alpha, additive, multiplied) +// NOTE: Only 3 blending modes predefined +void SetBlendMode(int mode) +{ + if ((blendMode != mode) && (mode < 3)) + { + rlglDraw(); + + switch (mode) + { + case BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + case BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); break; // Alternative: glBlendFunc(GL_ONE, GL_ONE); + case BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); break; + default: break; + } + + blendMode = mode; + } +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Convert image data to OpenGL texture (returns OpenGL valid Id) +// NOTE: Expected compressed image data and POT image +static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat) +{ + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + int blockSize = 0; // Bytes every block + int offset = 0; + + if ((compressedFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT) || + (compressedFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) || +#if defined(GRAPHICS_API_OPENGL_ES2) + (compressedFormat == GL_ETC1_RGB8_OES) || +#endif + (compressedFormat == GL_COMPRESSED_RGB8_ETC2)) blockSize = 8; + else blockSize = 16; + + // Load the mipmap levels + for (int level = 0; level < mipmapCount && (width || height); level++) + { + unsigned int size = 0; + + size = ((width + 3)/4)*((height + 3)/4)*blockSize; + + glCompressedTexImage2D(GL_TEXTURE_2D, level, compressedFormat, width, height, 0, size, data + offset); + + offset += size; + width /= 2; + height /= 2; + + // Security check for NPOT textures + if (width < 1) width = 1; + if (height < 1) height = 1; + } +} + +Texture2D GetDefaultTexture(void) +{ + Texture2D texture; + + texture.id = whiteTexture; + texture.width = 1; + texture.height = 1; + texture.mipmaps = 1; + texture.format = UNCOMPRESSED_R8G8B8A8; + + return texture; +} + +// Load custom shader strings and return program id +static unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + GLuint vertexShader; + GLuint fragmentShader; + + vertexShader = glCreateShader(GL_VERTEX_SHADER); + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + const char *pvs = vShaderStr; + const char *pfs = fShaderStr; + + glShaderSource(vertexShader, 1, &pvs, NULL); + glShaderSource(fragmentShader, 1, &pfs, NULL); + + GLint success = 0; + + glCompileShader(vertexShader); + + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); + + if (success != GL_TRUE) + { + TraceLog(WARNING, "[VSHDR ID %i] Failed to compile vertex shader...", vertexShader); + + int maxLength = 0; + int length; glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength); @@ -2156,7 +2129,17 @@ unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr) glAttachShader(program, vertexShader); glAttachShader(program, fragmentShader); - + + // NOTE: Default attribute shader locations must be binded before linking + glBindAttribLocation(program, 0, "vertexPosition"); + glBindAttribLocation(program, 1, "vertexTexCoord"); + glBindAttribLocation(program, 2, "vertexNormal"); + glBindAttribLocation(program, 3, "vertexColor"); + glBindAttribLocation(program, 4, "vertexTangent"); + glBindAttribLocation(program, 5, "vertexTexCoord2"); + + // NOTE: If some attrib name is no found on the shader, it locations becomes -1 + glLinkProgram(program); // NOTE: All uniform variables are intitialised to 0 when a program links @@ -2190,184 +2173,8 @@ unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr) return program; } -// Unload a custom shader from memory -void UnloadShader(Shader shader) -{ - if (shader.id != 0) - { - rlDeleteShader(shader.id); - TraceLog(INFO, "[SHDR ID %i] Unloaded shader program data", shader.id); - } -} - -// Set custom shader to be used on batch draw -void SetCustomShader(Shader shader) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (currentShader.id != shader.id) - { - rlglDraw(); - currentShader = shader; - } -#endif -} - -// Set default shader to be used in batch draw -void SetDefaultShader(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - SetCustomShader(defaultShader); -#endif -} - -// Link shader to model -void SetModelShader(Model *model, Shader shader) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - model->material.shader = shader; - - if (vaoSupported) glBindVertexArray(model->mesh.vaoId); - - // Enable vertex attributes: position - glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[0]); - glEnableVertexAttribArray(shader.vertexLoc); - glVertexAttribPointer(shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - - // Enable vertex attributes: texcoords - glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[1]); - glEnableVertexAttribArray(shader.texcoordLoc); - glVertexAttribPointer(shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); - - // Enable vertex attributes: normals - glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[2]); - glEnableVertexAttribArray(shader.normalLoc); - glVertexAttribPointer(shader.normalLoc, 3, GL_FLOAT, 0, 0, 0); - - if (vaoSupported) glBindVertexArray(0); // Unbind VAO - -#elif (GRAPHICS_API_OPENGL_11) - TraceLog(WARNING, "Shaders not supported on OpenGL 1.1"); -#endif -} - -// Get shader uniform location -int GetShaderLocation(Shader shader, const char *uniformName) -{ - int location = -1; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - location = glGetUniformLocation(shader.id, uniformName); - - if (location == -1) TraceLog(WARNING, "[SHDR ID %i] Shader location for %s could not be found", shader.id, uniformName); -#endif - return location; -} - -// Set shader uniform value (float) -void SetShaderValue(Shader shader, int uniformLoc, float *value, int size) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(shader.id); - - if (size == 1) glUniform1fv(uniformLoc, 1, value); // Shader uniform type: float - else if (size == 2) glUniform2fv(uniformLoc, 1, value); // Shader uniform type: vec2 - else if (size == 3) glUniform3fv(uniformLoc, 1, value); // Shader uniform type: vec3 - else if (size == 4) glUniform4fv(uniformLoc, 1, value); // Shader uniform type: vec4 - else TraceLog(WARNING, "Shader value float array size not supported"); - - glUseProgram(0); -#endif -} - -// Set shader uniform value (int) -void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(shader.id); - - if (size == 1) glUniform1iv(uniformLoc, 1, value); // Shader uniform type: int - else if (size == 2) glUniform2iv(uniformLoc, 1, value); // Shader uniform type: ivec2 - else if (size == 3) glUniform3iv(uniformLoc, 1, value); // Shader uniform type: ivec3 - else if (size == 4) glUniform4iv(uniformLoc, 1, value); // Shader uniform type: ivec4 - else TraceLog(WARNING, "Shader value int array size not supported"); - - glUseProgram(0); -#endif -} - -// Set shader uniform value (matrix 4x4) -void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(shader.id); - - glUniformMatrix4fv(uniformLoc, 1, false, MatrixToFloat(mat)); - - glUseProgram(0); -#endif -} - -// Set blending mode (alpha, additive, multiplied) -// NOTE: Only 3 blending modes predefined -void SetBlendMode(int mode) -{ - if ((blendMode != mode) && (mode < 3)) - { - rlglDraw(); - - switch (mode) - { - case BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; - case BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); break; // Alternative: glBlendFunc(GL_ONE, GL_ONE); - case BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); break; - default: break; - } - - blendMode = mode; - } -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Convert image data to OpenGL texture (returns OpenGL valid Id) -// NOTE: Expected compressed image data and POT image -static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat) -{ - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - int blockSize = 0; // Bytes every block - int offset = 0; - - if ((compressedFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT) || - (compressedFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) || -#if defined(GRAPHICS_API_OPENGL_ES2) - (compressedFormat == GL_ETC1_RGB8_OES) || -#endif - (compressedFormat == GL_COMPRESSED_RGB8_ETC2)) blockSize = 8; - else blockSize = 16; - - // Load the mipmap levels - for (int level = 0; level < mipmapCount && (width || height); level++) - { - unsigned int size = 0; - - size = ((width + 3)/4)*((height + 3)/4)*blockSize; - - glCompressedTexImage2D(GL_TEXTURE_2D, level, compressedFormat, width, height, 0, size, data + offset); - offset += size; - width /= 2; - height /= 2; - - // Security check for NPOT textures - if (width < 1) width = 1; - if (height < 1) height = 1; - } -} - -// Load default shader (Vertex and Fragment) +// Load default shader (just vertex positioning and texture coloring) // NOTE: This shader program is used for batch buffers (lines, triangles, quads) static Shader LoadDefaultShader(void) { @@ -2436,6 +2243,12 @@ static Shader LoadDefaultShader(void) // NOTE: If any location is not found, loc point becomes -1 static void LoadDefaultShaderLocations(Shader *shader) { + // NOTE: Default shader attrib locations have been fixed before linking: + // vertex position location = 0 + // vertex texcoord location = 1 + // vertex normal location = 2 + // vertex color location = 3 + // Get handles to GLSL input attibute locations shader->vertexLoc = glGetAttribLocation(shader->id, "vertexPosition"); shader->texcoordLoc = glGetAttribLocation(shader->id, "vertexTexCoord"); @@ -2470,7 +2283,7 @@ static void LoadDefaultBuffers(void) // [CPU] Allocate and initialize float array buffers to store vertex data (lines, triangles, quads) //-------------------------------------------------------------------------------------------- - // Initialize lines arrays (vertex position and color data) + // Lines - Initialize arrays (vertex position and color data) lines.vertices = (float *)malloc(sizeof(float)*3*2*MAX_LINES_BATCH); // 3 float by vertex, 2 vertex by line lines.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*2*MAX_LINES_BATCH); // 4 float by color, 2 colors by line @@ -2480,7 +2293,7 @@ static void LoadDefaultBuffers(void) lines.vCounter = 0; lines.cCounter = 0; - // Initialize triangles arrays (vertex position and color data) + // Triangles - Initialize arrays (vertex position and color data) triangles.vertices = (float *)malloc(sizeof(float)*3*3*MAX_TRIANGLES_BATCH); // 3 float by vertex, 3 vertex by triangle triangles.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH); // 4 float by color, 3 colors by triangle @@ -2490,7 +2303,7 @@ static void LoadDefaultBuffers(void) triangles.vCounter = 0; triangles.cCounter = 0; - // Initialize quads arrays (vertex position, texcoord and color data... and indexes) + // Quads - Initialize arrays (vertex position, texcoord, color data and indexes) quads.vertices = (float *)malloc(sizeof(float)*3*4*MAX_QUADS_BATCH); // 3 float by vertex, 4 vertex by quad quads.texcoords = (float *)malloc(sizeof(float)*2*4*MAX_QUADS_BATCH); // 2 float by texcoord, 4 texcoord by quad quads.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*4*MAX_QUADS_BATCH); // 4 float by color, 4 colors by quad @@ -2523,7 +2336,7 @@ static void LoadDefaultBuffers(void) quads.tcCounter = 0; quads.cCounter = 0; - TraceLog(INFO, "Default buffers initialized successfully in CPU (lines, triangles, quads)"); + TraceLog(INFO, "[CPU] Default buffers initialized successfully (lines, triangles, quads)"); //-------------------------------------------------------------------------------------------- // [GPU] Upload vertex data and initialize VAOs/VBOs (lines, triangles, quads) @@ -2541,20 +2354,21 @@ static void LoadDefaultBuffers(void) // Create buffers for our vertex data glGenBuffers(2, linesBuffer); - // Lines - Vertex positions buffer binding and attributes enable + // Lines - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(currentShader.vertexLoc); glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - // Lines - colors buffer + // Vertex color buffer (shader-location = 3) glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(currentShader.colorLoc); glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers (lines) VAO initialized successfully", vaoLines); - else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers (lines) VBOs initialized successfully", linesBuffer[0], linesBuffer[1]); + if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers VAO initialized successfully (lines)", vaoLines); + else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (lines)", linesBuffer[0], linesBuffer[1]); // Upload and link triangles vertex buffers if (vaoSupported) @@ -2567,19 +2381,21 @@ static void LoadDefaultBuffers(void) // Create buffers for our vertex data glGenBuffers(2, trianglesBuffer); - // Enable vertex attributes + // Triangles - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(currentShader.vertexLoc); glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + // Vertex color buffer (shader-location = 3) glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(currentShader.colorLoc); glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers (triangles) VAO initialized successfully", vaoTriangles); - else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers (triangles) VBOs initialized successfully", trianglesBuffer[0], trianglesBuffer[1]); + if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers VAO initialized successfully (triangles)", vaoTriangles); + else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully(triangles)", trianglesBuffer[0], trianglesBuffer[1]); // Upload and link quads vertex buffers if (vaoSupported) @@ -2592,17 +2408,20 @@ static void LoadDefaultBuffers(void) // Create buffers for our vertex data glGenBuffers(4, quadsBuffer); - // Enable vertex attributes + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_QUADS_BATCH, quads.vertices, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(currentShader.vertexLoc); glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + // Vertex texcoord buffer (shader-location = 1) glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_QUADS_BATCH, quads.texcoords, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(currentShader.texcoordLoc); glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + // Vertex color buffer (shader-location = 3) glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*4*MAX_QUADS_BATCH, quads.colors, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(currentShader.colorLoc); @@ -2616,15 +2435,15 @@ static void LoadDefaultBuffers(void) glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short)*6*MAX_QUADS_BATCH, quads.indices, GL_STATIC_DRAW); #endif - if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers (quads) VAO initialized successfully", vaoQuads); - else TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i][VBO ID %i] Default buffers (quads) VBOs initialized successfully", quadsBuffer[0], quadsBuffer[1], quadsBuffer[2], quadsBuffer[3]); + if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers VAO initialized successfully (quads)", vaoQuads); + else TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (quads)", quadsBuffer[0], quadsBuffer[1], quadsBuffer[2], quadsBuffer[3]); // Unbind the current VAO if (vaoSupported) glBindVertexArray(0); //-------------------------------------------------------------------------------------------- } -// Update default buffers (VAOs/VBOs) with vertex array data +// Update default internal buffers (VAOs/VBOs) with vertex array data // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required) static void UpdateDefaultBuffers(void) @@ -2695,7 +2514,165 @@ static void UpdateDefaultBuffers(void) if (vaoSupported) glBindVertexArray(0); } -// Unload default buffers vertex data from CPU and GPU +// Draw default internal buffers vertex data +// NOTE: We draw in this order: lines, triangles, quads +static void DrawDefaultBuffers(void) +{ + // Set current shader and upload current MVP matrix + if ((lines.vCounter > 0) || (triangles.vCounter > 0) || (quads.vCounter > 0)) + { + glUseProgram(currentShader.id); + + // Create modelview-projection matrix + Matrix matMVP = MatrixMultiply(modelview, projection); + + glUniformMatrix4fv(currentShader.mvpLoc, 1, false, MatrixToFloat(matMVP)); + glUniform1i(currentShader.mapDiffuseLoc, 0); + glUniform4f(currentShader.tintColorLoc, 1.0f, 1.0f, 1.0f, 1.0f); + } + + // Draw lines buffers + if (lines.vCounter > 0) + { + glBindTexture(GL_TEXTURE_2D, whiteTexture); + + if (vaoSupported) + { + glBindVertexArray(vaoLines); + } + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + } + + glDrawArrays(GL_LINES, 0, lines.vCounter); + + if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + } + + // Draw triangles buffers + if (triangles.vCounter > 0) + { + glBindTexture(GL_TEXTURE_2D, whiteTexture); + + if (vaoSupported) + { + glBindVertexArray(vaoTriangles); + } + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + } + + glDrawArrays(GL_TRIANGLES, 0, triangles.vCounter); + + if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + } + + // Draw quads buffers + if (quads.vCounter > 0) + { + int quadsCount = 0; + int numIndicesToProcess = 0; + int indicesOffset = 0; + + if (vaoSupported) + { + glBindVertexArray(vaoQuads); + } + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); + glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.texcoordLoc); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadsBuffer[3]); + } + + //TraceLog(DEBUG, "Draws required per frame: %i", drawsCounter); + + for (int i = 0; i < drawsCounter; i++) + { + quadsCount = draws[i].vertexCount/4; + numIndicesToProcess = quadsCount*6; // Get number of Quads * 6 index by Quad + + //TraceLog(DEBUG, "Quads to render: %i - Vertex Count: %i", quadsCount, draws[i].vertexCount); + + glBindTexture(GL_TEXTURE_2D, draws[i].textureId); + + // NOTE: The final parameter tells the GPU the offset in bytes from the start of the index buffer to the location of the first index to process +#if defined(GRAPHICS_API_OPENGL_33) + glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_INT, (GLvoid*) (sizeof(GLuint) * indicesOffset)); +#elif defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_SHORT, (GLvoid*) (sizeof(GLushort) * indicesOffset)); +#endif + //GLenum err; + //if ((err = glGetError()) != GL_NO_ERROR) TraceLog(INFO, "OpenGL error: %i", (int)err); //GL_INVALID_ENUM! + + indicesOffset += draws[i].vertexCount/4*6; + } + + if (!vaoSupported) + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + } + + if (vaoSupported) glBindVertexArray(0); // Unbind VAO + + glUseProgram(0); // Unbind shader program + + // Reset draws counter + drawsCounter = 1; + draws[0].textureId = whiteTexture; + draws[0].vertexCount = 0; + + // Reset vertex counters for next frame + lines.vCounter = 0; + lines.cCounter = 0; + triangles.vCounter = 0; + triangles.cCounter = 0; + quads.vCounter = 0; + quads.tcCounter = 0; + quads.cCounter = 0; + + // Reset depth for next draw + currentDepth = -1.0f; +} + +// Unload default internal buffers vertex data from CPU and GPU static void UnloadDefaultBuffers(void) { // Unbind everything diff --git a/src/rlgl.h b/src/rlgl.h index cd8e6d1db..afc2ab96f 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -280,29 +280,28 @@ unsigned int rlglLoadTexture(void *data, int width, int height, int textureForma RenderTexture2D rlglLoadRenderTexture(int width, int height); // Load a texture to be used for rendering (fbo with color and depth attachments) void rlglUpdateTexture(unsigned int id, int width, int height, int format, void *data); // Update GPU texture with new data void rlglGenerateMipmaps(Texture2D texture); // Generate mipmap data for selected texture - -// NOTE: There is a set of shader related functions that are available to end user, -// to avoid creating function wrappers through core module, they have been directly declared in raylib.h - -Model rlglLoadModel(Mesh mesh); // Upload vertex data into GPU and provided VAO/VBO ids -void rlglDrawModel(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color color, bool wires); +void rlglLoadMesh(Mesh *mesh); // Upload vertex data into GPU and provided VAO/VBO ids +void rlglDrawEx(Mesh mesh, Material material, Matrix transform, bool wires); Vector3 rlglUnproject(Vector3 source, Matrix proj, Matrix view); // Get world coordinates from screen coordinates unsigned char *rlglReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) void *rlglReadTexturePixels(Texture2D texture); // Read texture pixel data +// NOTE: There is a set of shader related functions that are available to end user, +// to avoid creating function wrappers through core module, they have been directly declared in raylib.h + #if defined(RLGL_STANDALONE) //------------------------------------------------------------------------------------ // Shaders System Functions (Module: rlgl) // NOTE: This functions are useless when using OpenGL 1.1 //------------------------------------------------------------------------------------ Shader LoadShader(char *vsFileName, char *fsFileName); // Load a custom shader and bind default locations -unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr); // Load custom shader strings and return program id void UnloadShader(Shader shader); // Unload a custom shader from memory void SetCustomShader(Shader shader); // Set custom shader to be used in batch draw void SetDefaultShader(void); // Set default shader to be used in batch draw -void SetModelShader(Model *model, Shader shader); // Link a shader to a model +Shader GetDefaultShader(void); // Get default shader +Texture2D GetDefaultTexture(void); // Get default texture int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); // Set shader uniform value (float) From eeb151586f12f8694cccaecf12d92af7842338b5 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 7 May 2016 18:28:40 +0200 Subject: [PATCH 04/11] Corrected issues with OpenGL 1.1 backend --- src/rlgl.c | 58 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/rlgl.c b/src/rlgl.c index 02649e305..462ccdec4 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -1080,22 +1080,24 @@ void rlglDrawEx(Mesh mesh, Material material, Matrix transform, bool wires) glBindTexture(GL_TEXTURE_2D, material.texDiffuse.id); // NOTE: On OpenGL 1.1 we use Vertex Arrays to draw model - glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array - glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Enable texture coords array - glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array + glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array + glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Enable texture coords array + glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array - glVertexPointer(3, GL_FLOAT, 0, mesh.vertices); // Pointer to vertex coords array - glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords); // Pointer to texture coords array - glNormalPointer(GL_FLOAT, 0, mesh.normals); // Pointer to normals array + glVertexPointer(3, GL_FLOAT, 0, mesh.vertices); // Pointer to vertex coords array + glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords); // Pointer to texture coords array + glNormalPointer(GL_FLOAT, 0, mesh.normals); // Pointer to normals array //glColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh.colors); // Pointer to colors array (NOT USED) - rlMultMatrixf(MatrixToFloat(transform)); - - glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount); + rlPushMatrix(); + rlMultMatrixf(MatrixToFloat(transform)); + rlColor4ub(material.colDiffuse.r, material.colDiffuse.g, material.colDiffuse.b, material.colDiffuse.a); + glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount); + rlPopMatrix(); - glDisableClientState(GL_VERTEX_ARRAY); // Disable vertex array - glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Disable texture coords array - glDisableClientState(GL_NORMAL_ARRAY); // Disable normals array + glDisableClientState(GL_VERTEX_ARRAY); // Disable vertex array + glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Disable texture coords array + glDisableClientState(GL_NORMAL_ARRAY); // Disable normals array glDisable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); @@ -1865,6 +1867,20 @@ void *rlglReadTexturePixels(Texture2D texture) // NOTE: Those functions are exposed directly to the user in raylib.h //---------------------------------------------------------------------------------- +// Get default internal texture (white texture) +Texture2D GetDefaultTexture(void) +{ + Texture2D texture; + + texture.id = whiteTexture; + texture.width = 1; + texture.height = 1; + texture.mipmaps = 1; + texture.format = UNCOMPRESSED_R8G8B8A8; + + return texture; +} + // Load a custom shader and bind default locations Shader LoadShader(char *vsFileName, char *fsFileName) { @@ -1930,7 +1946,12 @@ void SetDefaultShader(void) // Get default shader Shader GetDefaultShader(void) { +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) return defaultShader; +#else + Shader shader = { 0 }; + return shader; +#endif } // Get shader uniform location @@ -2050,19 +2071,6 @@ static void LoadCompressedTexture(unsigned char *data, int width, int height, in } } -Texture2D GetDefaultTexture(void) -{ - Texture2D texture; - - texture.id = whiteTexture; - texture.width = 1; - texture.height = 1; - texture.mipmaps = 1; - texture.format = UNCOMPRESSED_R8G8B8A8; - - return texture; -} - // Load custom shader strings and return program id static unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr) { From 29761c25190dee31ca3985ae4af602f3b3b53db3 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 7 May 2016 18:29:04 +0200 Subject: [PATCH 05/11] Testing new material usage --- examples/shaders_model_shader.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/shaders_model_shader.c b/examples/shaders_model_shader.c index b302f631b..a1e00671a 100644 --- a/examples/shaders_model_shader.c +++ b/examples/shaders_model_shader.c @@ -37,8 +37,8 @@ int main() Shader shader = LoadShader("resources/shaders/glsl330/base.vs", "resources/shaders/glsl330/grayscale.fs"); // Load model shader - SetModelShader(&dwarf, shader); // Set shader effect to 3d model - SetModelTexture(&dwarf, texture); // Bind texture to model + dwarf.material.shader = shader; // Set shader effect to 3d model + dwarf.material.texDiffuse = texture; // Bind texture to model Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position From 0bcb873cbb3758d67b1d263fafb6be818ddbf067 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 8 May 2016 15:24:02 +0200 Subject: [PATCH 06/11] Improved mesh support Depending on mesh data, it can be loaded and default vertex attribute location points are set, including colors, tangents and texcoords2 --- src/models.c | 120 ++++++++++++++++++++++++++++++++++++++++----------- src/raylib.h | 6 +-- src/rlgl.c | 90 +++++++++++++++++++++++++++++++------- 3 files changed, 171 insertions(+), 45 deletions(-) diff --git a/src/models.c b/src/models.c index 9b139120b..4aff72163 100644 --- a/src/models.c +++ b/src/models.c @@ -575,6 +575,89 @@ Model LoadModelEx(Mesh data) return model; } +// Load a 3d model from rRES file (raylib Resource) +Model LoadModelFromRES(const char *rresName, int resId) +{ + Model model = { 0 }; + bool found = false; + + char id[4]; // rRES file identifier + unsigned char version; // rRES file version and subversion + char useless; // rRES header reserved data + short numRes; + + ResInfoHeader infoHeader; + + FILE *rresFile = fopen(rresName, "rb"); + + if (rresFile == NULL) + { + TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName); + } + else + { + // Read rres file (basic file check - id) + fread(&id[0], sizeof(char), 1, rresFile); + fread(&id[1], sizeof(char), 1, rresFile); + fread(&id[2], sizeof(char), 1, rresFile); + fread(&id[3], sizeof(char), 1, rresFile); + fread(&version, sizeof(char), 1, rresFile); + fread(&useless, sizeof(char), 1, rresFile); + + if ((id[0] != 'r') && (id[1] != 'R') && (id[2] != 'E') &&(id[3] != 'S')) + { + TraceLog(WARNING, "[%s] This is not a valid raylib resource file", rresName); + } + else + { + // Read number of resources embedded + fread(&numRes, sizeof(short), 1, rresFile); + + for (int i = 0; i < numRes; i++) + { + fread(&infoHeader, sizeof(ResInfoHeader), 1, rresFile); + + if (infoHeader.id == resId) + { + found = true; + + // Check data is of valid MODEL type + if (infoHeader.type == 8) + { + // TODO: Load model data + } + else + { + TraceLog(WARNING, "[%s] Required resource do not seem to be a valid MODEL resource", rresName); + } + } + else + { + // Depending on type, skip the right amount of parameters + switch (infoHeader.type) + { + case 0: fseek(rresFile, 6, SEEK_CUR); break; // IMAGE: Jump 6 bytes of parameters + case 1: fseek(rresFile, 6, SEEK_CUR); break; // SOUND: Jump 6 bytes of parameters + case 2: fseek(rresFile, 5, SEEK_CUR); break; // MODEL: Jump 5 bytes of parameters (TODO: Review) + case 3: break; // TEXT: No parameters + case 4: break; // RAW: No parameters + default: break; + } + + // Jump DATA to read next infoHeader + fseek(rresFile, infoHeader.size, SEEK_CUR); + } + } + } + + fclose(rresFile); + } + + if (!found) TraceLog(WARNING, "[%s] Required resource id [%i] could not be found in the raylib resource file", rresName, resId); + + return model; +} + // Load a heightmap image as a 3d model // NOTE: model map size is defined in generic units Model LoadHeightmap(Image heightmap, Vector3 size) @@ -613,18 +696,18 @@ void UnloadModel(Model model) free(model.mesh.vertices); free(model.mesh.texcoords); free(model.mesh.normals); - free(model.mesh.colors); - //if (model.mesh.texcoords2 != NULL) free(model.mesh.texcoords2); // Not used - //if (model.mesh.tangents != NULL) free(model.mesh.tangents); // Not used + if (model.mesh.colors != NULL) free(model.mesh.colors); + if (model.mesh.tangents != NULL) free(model.mesh.tangents); + if (model.mesh.texcoords2 != NULL) free(model.mesh.texcoords2); TraceLog(INFO, "Unloaded model data from RAM (CPU)"); rlDeleteBuffers(model.mesh.vboId[0]); // vertex rlDeleteBuffers(model.mesh.vboId[1]); // texcoords rlDeleteBuffers(model.mesh.vboId[2]); // normals - //rlDeleteBuffers(model.mesh.vboId[3]); // colors (NOT USED) - //rlDeleteBuffers(model.mesh.vboId[4]); // tangents (NOT USED) - //rlDeleteBuffers(model.mesh.vboId[5]); // texcoords2 (NOT USED) + rlDeleteBuffers(model.mesh.vboId[3]); // colors + rlDeleteBuffers(model.mesh.vboId[4]); // tangents + rlDeleteBuffers(model.mesh.vboId[5]); // texcoords2 rlDeleteVertexArrays(model.mesh.vaoId); } @@ -672,7 +755,7 @@ static Mesh GenMeshHeightmap(Image heightmap, Vector3 size) { #define GRAY_VALUE(c) ((c.r+c.g+c.b)/3) - Mesh mesh; + Mesh mesh = { 0 }; int mapX = heightmap.width; int mapZ = heightmap.height; @@ -687,7 +770,7 @@ static Mesh GenMeshHeightmap(Image heightmap, Vector3 size) mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); - mesh.colors = (unsigned char *)malloc(mesh.vertexCount*4*sizeof(unsigned char)); // Not used... + mesh.colors = NULL; int vCounter = 0; // Used to count vertices float by float int tcCounter = 0; // Used to count texcoords float by float @@ -770,16 +853,12 @@ static Mesh GenMeshHeightmap(Image heightmap, Vector3 size) free(pixels); - // Fill color data - // NOTE: Not used any more... just one plain color defined at DrawModel() - for (int i = 0; i < (4*mesh.vertexCount); i++) mesh.colors[i] = 255; - return mesh; } static Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) { - Mesh mesh; + Mesh mesh = { 0 }; Color *cubicmapPixels = GetImageData(cubicmap); @@ -1088,11 +1167,7 @@ static Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); - mesh.colors = (unsigned char *)malloc(mesh.vertexCount*4*sizeof(unsigned char)); // Not used... - - // Fill color data - // NOTE: Not used any more... just one plain color defined at DrawModel() - for (int i = 0; i < (4*mesh.vertexCount); i++) mesh.colors[i] = 255; + mesh.colors = NULL; int fCounter = 0; @@ -1810,7 +1885,7 @@ static Mesh LoadOBJ(const char *fileName) mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.colors = (unsigned char *)malloc(mesh.vertexCount*4*sizeof(unsigned char)); + mesh.colors = NULL; int vCounter = 0; // Used to count vertices float by float int tcCounter = 0; // Used to count texcoords float by float @@ -1909,10 +1984,6 @@ static Mesh LoadOBJ(const char *fileName) // Security check, just in case no normals or no texcoords defined in OBJ if (numTexCoords == 0) for (int i = 0; i < (2*mesh.vertexCount); i++) mesh.texcoords[i] = 0.0f; - - // NOTE: We set all vertex colors to white - // NOTE: Not used any more... just one plain color defined at DrawModel() - for (int i = 0; i < (4*mesh.vertexCount); i++) mesh.colors[i] = 255; // Now we can free temp mid* arrays free(midVertices); @@ -1945,9 +2016,6 @@ static Material LoadMTL(const char *fileName) return material; } - // First reading pass: Get numVertex, numNormals, numTexCoords, numTriangles - // NOTE: vertex, texcoords and normals could be optimized (to be used indexed on faces definition) - // NOTE: faces MUST be defined as TRIANGLES (3 vertex per face) while(!feof(mtlFile)) { fscanf(mtlFile, "%c", &dataType); diff --git a/src/raylib.h b/src/raylib.h index c88a60f45..603844449 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -386,7 +386,7 @@ typedef struct Mesh { typedef struct Shader { unsigned int id; // Shader program id - // Variable attributes locations + // Vertex attributes locations (default locations) int vertexLoc; // Vertex attribute location point (default-location = 0) int texcoordLoc; // Texcoord attribute location point (default-location = 1) int normalLoc; // Normal attribute location point (default-location = 2) @@ -803,14 +803,14 @@ void DrawGizmo(Vector3 position); //------------------------------------------------------------------------------------ Model LoadModel(const char *fileName); // Load a 3d model (.OBJ) Model LoadModelEx(Mesh data); // Load a 3d model (from mesh data) -//Model LoadModelFromRES(const char *rresName, int resId); // TODO: Load a 3d model from rRES file (raylib Resource) +Model LoadModelFromRES(const char *rresName, int resId); // Load a 3d model from rRES file (raylib Resource) Model LoadHeightmap(Image heightmap, Vector3 size); // Load a heightmap image as a 3d model Model LoadCubicmap(Image cubicmap); // Load a map image as a 3d model (cubes based) void UnloadModel(Model model); // Unload 3d model from memory void SetModelTexture(Model *model, Texture2D texture); // Link a texture to a model Material LoadMaterial(const char *fileName); // Load material data (from file) -Material LoadDefaultMaterial(void); // Load default material (uses default models shader) +Material LoadDefaultMaterial(void); // Load default material (uses default models shader) void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters diff --git a/src/rlgl.c b/src/rlgl.c index 462ccdec4..6ffcb8a5d 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -811,9 +811,11 @@ void rlDeleteVertexArrays(unsigned int id) void rlDeleteBuffers(unsigned int id) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDeleteBuffers(1, &id); - - if (!vaoSupported) TraceLog(INFO, "[VBO ID %i] Unloaded model vertex data from VRAM (GPU)", id); + if (id != 0) + { + glDeleteBuffers(1, &id); + if (!vaoSupported) TraceLog(INFO, "[VBO ID %i] Unloaded model vertex data from VRAM (GPU)", id); + } #endif } @@ -1638,6 +1640,7 @@ void rlglGenerateMipmaps(Texture2D texture) } // Upload vertex data into a VAO (if supported) and VBO +// TODO: Consider attributes: color, texcoords2, tangents (if available) void rlglLoadMesh(Mesh *mesh) { mesh->vaoId = 0; // Vertex Array Object @@ -1648,11 +1651,10 @@ void rlglLoadMesh(Mesh *mesh) mesh->vboId[4] = 0; // Vertex tangent VBO mesh->vboId[5] = 0; // Vertex texcoord2 VBO - // TODO: Consider attributes: color, texcoords2, tangents (if available) - + #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) GLuint vaoId = 0; // Vertex Array Objects (VAO) - GLuint vboId[3]; // Vertex Buffer Objects (VBOs) + GLuint vboId[6]; // Vertex Buffer Objects (VBOs) if (vaoSupported) { @@ -1661,36 +1663,92 @@ void rlglLoadMesh(Mesh *mesh) glBindVertexArray(vaoId); } - // Create buffers for our vertex data (positions, texcoords, normals) - glGenBuffers(3, vboId); - // NOTE: Attributes must be uploaded considering default locations points // Enable vertex attributes: position (shader-location = 0) + glGenBuffers(1, &vboId[0]); glBindBuffer(GL_ARRAY_BUFFER, vboId[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, 0, 0, 0); glEnableVertexAttribArray(0); // Enable vertex attributes: texcoords (shader-location = 1) + glGenBuffers(1, &vboId[1]); glBindBuffer(GL_ARRAY_BUFFER, vboId[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh->vertexCount, mesh->texcoords, GL_STATIC_DRAW); glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0); glEnableVertexAttribArray(1); // Enable vertex attributes: normals (shader-location = 2) - glBindBuffer(GL_ARRAY_BUFFER, vboId[2]); - glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->normals, GL_STATIC_DRAW); - glVertexAttribPointer(2, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(2); + if (mesh->normals != NULL) + { + glGenBuffers(1, &vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, vboId[2]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->normals, GL_STATIC_DRAW); + glVertexAttribPointer(2, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(2); + } + else + { + // Default color vertex attribute set to WHITE + glVertexAttrib3f(2, 1.0f, 1.0f, 1.0f); + glDisableVertexAttribArray(2); + } // Default color vertex attribute (shader-location = 3) - glVertexAttrib4f(3, 1.0f, 1.0f, 1.0f, 1.0f); // Color vertex attribute set to default: WHITE - glDisableVertexAttribArray(3); + if (mesh->colors != NULL) + { + glGenBuffers(1, &vboId[3]); + glBindBuffer(GL_ARRAY_BUFFER, vboId[3]); + glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*mesh->vertexCount, mesh->colors, GL_STATIC_DRAW); + glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(3); + } + else + { + // Default color vertex attribute set to WHITE + glVertexAttrib4f(3, 1.0f, 1.0f, 1.0f, 1.0f); + glDisableVertexAttribArray(3); + } + + // Default tangent vertex attribute (shader-location = 4) + if (mesh->tangents != NULL) + { + glGenBuffers(1, &vboId[4]); + glBindBuffer(GL_ARRAY_BUFFER, vboId[4]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->tangents, GL_STATIC_DRAW); + glVertexAttribPointer(4, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(4); + } + else + { + // Default tangents vertex attribute + glVertexAttrib3f(4, 0.0f, 0.0f, 0.0f); + glDisableVertexAttribArray(4); + } + + // Default texcoord2 vertex attribute (shader-location = 5) + if (mesh->texcoords2 != NULL) + { + glGenBuffers(1, &vboId[5]); + glBindBuffer(GL_ARRAY_BUFFER, vboId[5]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh->vertexCount, mesh->texcoords2, GL_STATIC_DRAW); + glVertexAttribPointer(5, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(5); + } + else + { + // Default tangents vertex attribute + glVertexAttrib2f(5, 0.0f, 0.0f); + glDisableVertexAttribArray(5); + } mesh->vboId[0] = vboId[0]; // Vertex position VBO mesh->vboId[1] = vboId[1]; // Texcoords VBO mesh->vboId[2] = vboId[2]; // Normals VBO + mesh->vboId[3] = vboId[3]; // Colors VBO + mesh->vboId[4] = vboId[4]; // Tangents VBO + mesh->vboId[5] = vboId[5]; // Texcoords2 VBO if (vaoSupported) { @@ -1703,7 +1761,7 @@ void rlglLoadMesh(Mesh *mesh) } else { - TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vboId[0], mesh->vboId[1], mesh->vboId[2]); + TraceLog(INFO, "[VBOs] Mesh uploaded successfully to VRAM (GPU)"); } #endif } From f7d4951165e8bece5ae58cd4c92b08e4f29cbef7 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 8 May 2016 23:50:35 +0200 Subject: [PATCH 07/11] Improved vertex attribs support for models --- src/models.c | 2 +- src/raylib.h | 14 ++++++++------ src/rlgl.c | 45 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/models.c b/src/models.c index 4aff72163..ee04b449f 100644 --- a/src/models.c +++ b/src/models.c @@ -695,7 +695,7 @@ void UnloadModel(Model model) // Unload mesh data free(model.mesh.vertices); free(model.mesh.texcoords); - free(model.mesh.normals); + if (model.mesh.normals != NULL) free(model.mesh.normals); if (model.mesh.colors != NULL) free(model.mesh.colors); if (model.mesh.tangents != NULL) free(model.mesh.tangents); if (model.mesh.texcoords2 != NULL) free(model.mesh.texcoords2); diff --git a/src/raylib.h b/src/raylib.h index 603844449..1258bffc7 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -369,12 +369,12 @@ typedef struct BoundingBox { // Vertex data definning a mesh typedef struct Mesh { int vertexCount; // num vertices - float *vertices; // vertex position (XYZ - 3 components per vertex) - float *texcoords; // vertex texture coordinates (UV - 2 components per vertex) - float *texcoords2; // vertex second texture coordinates (useful for lightmaps) - float *normals; // vertex normals (XYZ - 3 components per vertex) - float *tangents; // vertex tangents (XYZ - 3 components per vertex) - unsigned char *colors; // vertex colors (RGBA - 4 components per vertex) + float *vertices; // vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *texcoords2; // vertex second texture coordinates (useful for lightmaps) (shader-location = 5) + float *normals; // vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float *tangents; // vertex tangents (XYZ - 3 components per vertex) (shader-location = 4) + unsigned char *colors; // vertex colors (RGBA - 4 components per vertex) (shader-location = 3) BoundingBox bounds; // mesh limits defined by min and max points @@ -391,6 +391,8 @@ typedef struct Shader { int texcoordLoc; // Texcoord attribute location point (default-location = 1) int normalLoc; // Normal attribute location point (default-location = 2) int colorLoc; // Color attibute location point (default-location = 3) + int tangentLoc; // Tangent attribute location point (default-location = 4) + int texcoord2Loc; // Texcoord2 attribute location point (default-location = 5) // Uniform locations int mvpLoc; // ModelView-Projection matrix uniform location point (vertex shader) diff --git a/src/rlgl.c b/src/rlgl.c index 6ffcb8a5d..2e4601df7 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -1084,12 +1084,13 @@ void rlglDrawEx(Mesh mesh, Material material, Matrix transform, bool wires) // NOTE: On OpenGL 1.1 we use Vertex Arrays to draw model glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Enable texture coords array - glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array + if (mesh.normals != NULL) glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array + if (mesh.colors != NULL) glEnableClientState(GL_COLOR_ARRAY); // Enable colors array glVertexPointer(3, GL_FLOAT, 0, mesh.vertices); // Pointer to vertex coords array glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords); // Pointer to texture coords array - glNormalPointer(GL_FLOAT, 0, mesh.normals); // Pointer to normals array - //glColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh.colors); // Pointer to colors array (NOT USED) + if (mesh.normals != NULL) glNormalPointer(GL_FLOAT, 0, mesh.normals); // Pointer to normals array + if (mesh.colors != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh.colors); // Pointer to colors array rlPushMatrix(); rlMultMatrixf(MatrixToFloat(transform)); @@ -1099,7 +1100,8 @@ void rlglDrawEx(Mesh mesh, Material material, Matrix transform, bool wires) glDisableClientState(GL_VERTEX_ARRAY); // Disable vertex array glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Disable texture coords array - glDisableClientState(GL_NORMAL_ARRAY); // Disable normals array + if (mesh.normals != NULL) glDisableClientState(GL_NORMAL_ARRAY); // Disable normals array + if (mesh.colors != NULL) glDisableClientState(GL_NORMAL_ARRAY); // Disable colors array glDisable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); @@ -1170,7 +1172,29 @@ void rlglDrawEx(Mesh mesh, Material material, Matrix transform, bool wires) glEnableVertexAttribArray(material.shader.normalLoc); } - // TODO: Bind mesh VBO data: colors, tangents, texcoords2 (if available) + // Bind mesh VBO data: vertex colors (shader-location = 3, if available) , tangents, texcoords2 (if available) + if (material.shader.colorLoc != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[3]); + glVertexAttribPointer(material.shader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(material.shader.colorLoc); + } + + // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) + if (material.shader.tangentLoc != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[4]); + glVertexAttribPointer(material.shader.tangentLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.tangentLoc); + } + + // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) + if (material.shader.texcoord2Loc != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]); + glVertexAttribPointer(material.shader.texcoord2Loc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.texcoord2Loc); + } } // Draw call! @@ -1640,16 +1664,15 @@ void rlglGenerateMipmaps(Texture2D texture) } // Upload vertex data into a VAO (if supported) and VBO -// TODO: Consider attributes: color, texcoords2, tangents (if available) void rlglLoadMesh(Mesh *mesh) { mesh->vaoId = 0; // Vertex Array Object mesh->vboId[0] = 0; // Vertex positions VBO mesh->vboId[1] = 0; // Vertex texcoords VBO mesh->vboId[2] = 0; // Vertex normals VBO - mesh->vboId[3] = 0; // Vertex color VBO - mesh->vboId[4] = 0; // Vertex tangent VBO - mesh->vboId[5] = 0; // Vertex texcoord2 VBO + mesh->vboId[3] = 0; // Vertex colors VBO + mesh->vboId[4] = 0; // Vertex tangents VBO + mesh->vboId[5] = 0; // Vertex texcoords2 VBO #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) @@ -2314,12 +2337,16 @@ static void LoadDefaultShaderLocations(Shader *shader) // vertex texcoord location = 1 // vertex normal location = 2 // vertex color location = 3 + // vertex tangent location = 4 + // vertex texcoord2 location = 5 // Get handles to GLSL input attibute locations shader->vertexLoc = glGetAttribLocation(shader->id, "vertexPosition"); shader->texcoordLoc = glGetAttribLocation(shader->id, "vertexTexCoord"); shader->normalLoc = glGetAttribLocation(shader->id, "vertexNormal"); shader->colorLoc = glGetAttribLocation(shader->id, "vertexColor"); + shader->tangentLoc = glGetAttribLocation(shader->id, "vertexTangent"); + shader->texcoord2Loc = glGetAttribLocation(shader->id, "vertexTexCoord2"); // Get handles to GLSL uniform locations (vertex shader) shader->mvpLoc = glGetUniformLocation(shader->id, "mvpMatrix"); From dc4d5dabcdf8f35f32acb9c5b48a24b1141c460f Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 May 2016 01:18:46 +0200 Subject: [PATCH 08/11] Added MTL loading info --- src/models.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/models.c b/src/models.c index ee04b449f..7b6deb5c6 100644 --- a/src/models.c +++ b/src/models.c @@ -2001,7 +2001,26 @@ static Material LoadMTL(const char *fileName) { Material material = { 0 }; - // TODO: Load mtl file + // TODO: Load mtl file (multiple variations of .mtl format) + /* + newmtl string Material newmtl (material name). Begins a new material description. + Ka float float float Ambient color Ka (red) (green) (blue) + Kd float float float Diffuse color Kd (red) (green) (blue) + Ks float float float Specular color Ks (red) (green) (blue) + Ke float float float Emmisive color + d float Tr float Dissolve factor. Transparency Tr (alpha). d is inverse of Tr + Ns int Shininess Ns (specular power). Ranges from 0 to 1000. Specular exponent. + Ni int Refraction index. + illum int Illumination model illum (1 / 2); 1 if specular disabled, 2 if specular enabled (lambertian model) + map_Kd string Texture map_Kd (filename) + map_Kd string Diffuse color texture map. + map_Ks string Specular color texture map. + map_Ka string Ambient color texture map. + map_Bump string Bump texture map. Alternative: bump string / map_bump string + map_d string Opacity texture map. + disp string Displacement map + refl Reflection type and map + */ char dataType; char comments[200]; From 3d0208223ad5b79cb4c3d6cd367d39f9d4e51662 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 May 2016 12:40:59 +0200 Subject: [PATCH 09/11] First implementation of MTL loading Not tested yet --- src/models.c | 156 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 132 insertions(+), 24 deletions(-) diff --git a/src/models.c b/src/models.c index 7b6deb5c6..dff6b224f 100644 --- a/src/models.c +++ b/src/models.c @@ -1997,33 +1997,16 @@ static Mesh LoadOBJ(const char *fileName) } // Load MTL material data +// NOTE: Texture map parameters are not supported static Material LoadMTL(const char *fileName) { - Material material = { 0 }; + #define MAX_BUFFER_SIZE 128 - // TODO: Load mtl file (multiple variations of .mtl format) - /* - newmtl string Material newmtl (material name). Begins a new material description. - Ka float float float Ambient color Ka (red) (green) (blue) - Kd float float float Diffuse color Kd (red) (green) (blue) - Ks float float float Specular color Ks (red) (green) (blue) - Ke float float float Emmisive color - d float Tr float Dissolve factor. Transparency Tr (alpha). d is inverse of Tr - Ns int Shininess Ns (specular power). Ranges from 0 to 1000. Specular exponent. - Ni int Refraction index. - illum int Illumination model illum (1 / 2); 1 if specular disabled, 2 if specular enabled (lambertian model) - map_Kd string Texture map_Kd (filename) - map_Kd string Diffuse color texture map. - map_Ks string Specular color texture map. - map_Ka string Ambient color texture map. - map_Bump string Bump texture map. Alternative: bump string / map_bump string - map_d string Opacity texture map. - disp string Displacement map - refl Reflection type and map - */ + Material material = { 0 }; // LoadDefaultMaterial(); - char dataType; - char comments[200]; + char buffer[MAX_BUFFER_SIZE]; + Vector3 color = { 1.0f, 1.0f, 1.0f }; + char *mapFileName; FILE *mtlFile; @@ -2037,7 +2020,132 @@ static Material LoadMTL(const char *fileName) while(!feof(mtlFile)) { - fscanf(mtlFile, "%c", &dataType); + fgets(buffer, MAX_BUFFER_SIZE, mtlFile); + + switch (buffer[0]) + { + case 'n': // newmtl string Material name. Begins a new material description. + { + // TODO: Support multiple materials in a single .mtl + sscanf(buffer, "newmtl %s", mapFileName); + + TraceLog(INFO, "[%s] Loading material...", mapFileName); + } + case 'i': // illum int Illumination model + { + // illum = 1 if specular disabled + // illum = 2 if specular enabled (lambertian model) + // ... + } + case 'K': // Ka, Kd, Ks, Ke + { + switch (buffer[1]) + { + case 'a': // Ka float float float Ambient color (RGB) + { + sscanf(buffer, "Ka %f %f %f", &color.x, &color.y, &color.z); + material.colAmbient.r = (unsigned char)(color.x*255); + material.colAmbient.g = (unsigned char)(color.y*255); + material.colAmbient.b = (unsigned char)(color.z*255); + } break; + case 'd': // Kd float float float Diffuse color (RGB) + { + sscanf(buffer, "Kd %f %f %f", &color.x, &color.y, &color.z); + material.colDiffuse.r = (unsigned char)(color.x*255); + material.colDiffuse.g = (unsigned char)(color.y*255); + material.colDiffuse.b = (unsigned char)(color.z*255); + } break; + case 's': // Ks float float float Specular color (RGB) + { + sscanf(buffer, "Ks %f %f %f", &color.x, &color.y, &color.z); + material.colSpecular.r = (unsigned char)(color.x*255); + material.colSpecular.g = (unsigned char)(color.y*255); + material.colSpecular.b = (unsigned char)(color.z*255); + } break; + case 'e': // Ke float float float Emmisive color (RGB) + { + // TODO: Support Ke ? + } break; + default: break; + } + } break; + case 'N': // Ns, Ni + { + if (buffer[1] == 's') // Ns int Shininess (specular exponent). Ranges from 0 to 1000. + { + sscanf(buffer, "Ns %i", &material.glossiness); + } + else if (buffer[1] == 'i') // Ni int Refraction index. + { + // Not supported... + } + } break; + case 'm': // map_Kd, map_Ks, map_Ka, map_Bump, map_d + { + switch (buffer[4]) + { + case 'K': // Color texture maps + { + if (buffer[5] == 'd') // map_Kd string Diffuse color texture map. + { + sscanf(buffer, "map_Kd %s", mapFileName); + if (mapFileName != NULL) material.texDiffuse = LoadTexture(mapFileName); + } + else if (buffer[5] == 's') // map_Ks string Specular color texture map. + { + sscanf(buffer, "map_Ks %s", mapFileName); + if (mapFileName != NULL) material.texSpecular = LoadTexture(mapFileName); + } + else if (buffer[5] == 'a') // map_Ka string Ambient color texture map. + { + // Not supported... + } + } break; + case 'B': // map_Bump string Bump texture map. + { + sscanf(buffer, "map_Bump %s", mapFileName); + if (mapFileName != NULL) material.texNormal = LoadTexture(mapFileName); + } break; + case 'b': // map_bump string Bump texture map. + { + sscanf(buffer, "map_bump %s", mapFileName); + if (mapFileName != NULL) material.texNormal = LoadTexture(mapFileName); + } break; + case 'd': // map_d string Opacity texture map. + { + // Not supported... + } break; + default: break; + } + } break; + case 'd': // d, disp + { + if (buffer[1] == ' ') // d float Dissolve factor. d is inverse of Tr + { + float alpha = 1.0f; + sscanf(buffer, "d %f", &alpha); + material.colDiffuse.a = (unsigned char)(alpha*255); + } + else if (buffer[1] == 'i') // disp string Displacement map + { + // Not supported... + } + } break; + case 'b': // bump string Bump texture map + { + sscanf(buffer, "bump %s", mapFileName); + if (mapFileName != NULL) material.texNormal = LoadTexture(mapFileName); + } break; + case 'T': // Tr float Transparency Tr (alpha). Tr is inverse of d + { + float ialpha = 0.0f; + sscanf(buffer, "Tr %f", &ialpha); + material.colDiffuse.a = (unsigned char)((1.0f - ialpha)*255); + + } break; + case 'r': // refl string Reflection texture map + default: break; + } } fclose(mtlFile); From c85cd290491baa7be2b107bdb72c4b039dfd8cb3 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 May 2016 12:41:53 +0200 Subject: [PATCH 10/11] Added defines for default shader names --- src/rlgl.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/rlgl.c b/src/rlgl.c index 2e4601df7..33f12debc 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -115,6 +115,15 @@ #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 #endif + +// Default vertex attribute names on shader to set location points +#define DEFAULT_ATTRIB_POSITION_NAME "vertexPosition" // shader-location = 0 +#define DEFAULT_ATTRIB_TEXCOORD_NAME "vertexTexCoord" // shader-location = 1 +#define DEFAULT_ATTRIB_NORMAL_NAME "vertexNormal" // shader-location = 2 +#define DEFAULT_ATTRIB_COLOR_NAME "vertexColor" // shader-location = 3 +#define DEFAULT_ATTRIB_TANGENT_NAME "vertexTangent" // shader-location = 4 +#define DEFAULT_ATTRIB_TEXCOORD2_NAME "vertexTexCoord2" // shader-location = 5 + //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -2220,12 +2229,12 @@ static unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr) glAttachShader(program, fragmentShader); // NOTE: Default attribute shader locations must be binded before linking - glBindAttribLocation(program, 0, "vertexPosition"); - glBindAttribLocation(program, 1, "vertexTexCoord"); - glBindAttribLocation(program, 2, "vertexNormal"); - glBindAttribLocation(program, 3, "vertexColor"); - glBindAttribLocation(program, 4, "vertexTangent"); - glBindAttribLocation(program, 5, "vertexTexCoord2"); + glBindAttribLocation(program, 0, DEFAULT_ATTRIB_POSITION_NAME); + glBindAttribLocation(program, 1, DEFAULT_ATTRIB_TEXCOORD_NAME); + glBindAttribLocation(program, 2, DEFAULT_ATTRIB_NORMAL_NAME); + glBindAttribLocation(program, 3, DEFAULT_ATTRIB_COLOR_NAME); + glBindAttribLocation(program, 4, DEFAULT_ATTRIB_TANGENT_NAME); + glBindAttribLocation(program, 5, DEFAULT_ATTRIB_TEXCOORD2_NAME); // NOTE: If some attrib name is no found on the shader, it locations becomes -1 From ac44db26a2a2bed901c7e75d96e215fa09bd3705 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 May 2016 13:16:44 +0200 Subject: [PATCH 11/11] Added reference --- src/models.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models.c b/src/models.c index dff6b224f..7d24e383e 100644 --- a/src/models.c +++ b/src/models.c @@ -1996,7 +1996,7 @@ static Mesh LoadOBJ(const char *fileName) return mesh; } -// Load MTL material data +// Load MTL material data (specs: http://paulbourke.net/dataformats/mtl/) // NOTE: Texture map parameters are not supported static Material LoadMTL(const char *fileName) {