diff --git a/src/5.advanced_lighting/4.normal_mapping/4.normal_mapping.fs b/src/5.advanced_lighting/4.normal_mapping/4.normal_mapping.fs index f8133c4..12e435e 100644 --- a/src/5.advanced_lighting/4.normal_mapping/4.normal_mapping.fs +++ b/src/5.advanced_lighting/4.normal_mapping/4.normal_mapping.fs @@ -3,11 +3,7 @@ out vec4 FragColor; in VS_OUT { vec3 FragPos; - vec3 Normal; vec2 TexCoords; - vec3 Tangent; - vec3 Bitangent; - mat3 TBN; vec3 TangentLightPos; vec3 TangentViewPos; vec3 TangentFragPos; @@ -19,30 +15,22 @@ uniform sampler2D normalMap; uniform vec3 lightPos; uniform vec3 viewPos; -uniform bool normalMapping; - void main() { - vec3 normal = fs_in.Normal; - mat3 tbn; - // Obtain normal from normal map in range [0,1] - normal = texture(normalMap, fs_in.TexCoords).rgb; - // Transform normal vector to range [-1,1] - normal = normalize(normal * 2.0 - 1.0); - // Then transform normal in tangent space to world-space via TBN matrix - // tbn = mat3(fs_in.Tangent, fs_in.Bitangent, fs_in.Normal); // TBN calculated in fragment shader - // normal = normalize(tbn * normal); // This works! - // normal = normalize(fs_in.TBN * normal); // This gives incorrect results + // obtain normal from normal map in range [0,1] + vec3 normal = texture(normalMap, fs_in.TexCoords).rgb; + // transform normal vector to range [-1,1] + normal = normalize(normal * 2.0 - 1.0); // this normal is in tangent space - // Get diffuse color + // get diffuse color vec3 color = texture(diffuseMap, fs_in.TexCoords).rgb; - // Ambient + // ambient vec3 ambient = 0.1 * color; - // Diffuse + // diffuse vec3 lightDir = normalize(fs_in.TangentLightPos - fs_in.TangentFragPos); float diff = max(dot(lightDir, normal), 0.0); vec3 diffuse = diff * color; - // Specular + // specular vec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos); vec3 reflectDir = reflect(-lightDir, normal); vec3 halfwayDir = normalize(lightDir + viewDir); diff --git a/src/5.advanced_lighting/4.normal_mapping/4.normal_mapping.vs b/src/5.advanced_lighting/4.normal_mapping/4.normal_mapping.vs index 10968be..c30b44f 100644 --- a/src/5.advanced_lighting/4.normal_mapping/4.normal_mapping.vs +++ b/src/5.advanced_lighting/4.normal_mapping/4.normal_mapping.vs @@ -1,17 +1,13 @@ #version 330 core -layout (location = 0) in vec3 position; -layout (location = 1) in vec3 normal; -layout (location = 2) in vec2 texCoords; -layout (location = 3) in vec3 tangent; -layout (location = 4) in vec3 bitangent; +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoords; +layout (location = 3) in vec3 aTangent; +layout (location = 4) in vec3 aBitangent; out VS_OUT { vec3 FragPos; - vec3 Normal; vec2 TexCoords; - vec3 Tangent; - vec3 Bitangent; - mat3 TBN; vec3 TangentLightPos; vec3 TangentViewPos; vec3 TangentFragPos; @@ -26,24 +22,19 @@ uniform vec3 viewPos; void main() { - gl_Position = projection * view * model * vec4(position, 1.0f); - vs_out.FragPos = vec3(model * vec4(position, 1.0)); - vs_out.TexCoords = texCoords; + vs_out.FragPos = vec3(model * vec4(aPos, 1.0)); + vs_out.TexCoords = aTexCoords; mat3 normalMatrix = transpose(inverse(mat3(model))); - vs_out.Normal = normalize(normalMatrix * normal); - - vec3 T = normalize(normalMatrix * tangent); - vec3 N = normalize(normalMatrix * normal); + vec3 T = normalize(normalMatrix * aTangent); + vec3 N = normalize(normalMatrix * aNormal); T = normalize(T - dot(T, N) * N); vec3 B = cross(N, T); - mat3 TBN = transpose(mat3(T, B, N)); - vs_out.TBN = TBN; + mat3 TBN = transpose(mat3(T, B, N)); vs_out.TangentLightPos = TBN * lightPos; vs_out.TangentViewPos = TBN * viewPos; vs_out.TangentFragPos = TBN * vs_out.FragPos; - - vs_out.Tangent = T; - vs_out.Bitangent = B; + + gl_Position = projection * view * model * vec4(aPos, 1.0); } \ No newline at end of file diff --git a/src/5.advanced_lighting/4.normal_mapping/normal_mapping.cpp b/src/5.advanced_lighting/4.normal_mapping/normal_mapping.cpp index 7b50f4f..0774fae 100644 --- a/src/5.advanced_lighting/4.normal_mapping/normal_mapping.cpp +++ b/src/5.advanced_lighting/4.normal_mapping/normal_mapping.cpp @@ -1,163 +1,175 @@ -// Std. Includes -#include - -// GLEW -#define GLEW_STATIC -#include - -// GLFW +#include #include +#include -// GL includes -#include -#include -#include - -// GLM Mathemtics #include #include #include -// Other Libs -#include #include +#include +#include +#include -// Properties -const GLuint SCR_WIDTH = 800, SCR_HEIGHT = 600; +#include -// Function prototypes -void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode); -void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); +void framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xpos, double ypos); -void Do_Movement(); -GLuint loadTexture(GLchar const * path); -void RenderQuad(); +void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); +void processInput(GLFWwindow *window); +unsigned int loadTexture(const char *path); +void renderQuad(); -// Camera +// settings +const unsigned int SCR_WIDTH = 1280; +const unsigned int SCR_HEIGHT = 720; + +// camera Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); +float lastX = (float)SCR_WIDTH / 2.0; +float lastY = (float)SCR_HEIGHT / 2.0; +bool firstMouse = true; -GLfloat deltaTime = 0.0f; -GLfloat lastFrame = 0.0f; +// timing +float deltaTime = 0.0f; +float lastFrame = 0.0f; -// The MAIN function, from here we start our application and run our Game loop int main() { - // Init GLFW + // glfw: initialize and configure + // ------------------------------ glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed + // glfw window creation + // -------------------- + GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); glfwMakeContextCurrent(window); - - // Set the required callback functions - glfwSetKeyCallback(window, key_callback); + if (window == NULL) + { + std::cout << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + return -1; + } + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetCursorPosCallback(window, mouse_callback); glfwSetScrollCallback(window, scroll_callback); - // Options + // tell GLFW to capture our mouse glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - // Initialize GLEW to setup the OpenGL Function pointers - glewExperimental = GL_TRUE; - glewInit(); + // glad: load all OpenGL function pointers + // --------------------------------------- + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + std::cout << "Failed to initialize GLAD" << std::endl; + return -1; + } - // Define the viewport dimensions - glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); - - // Setup some OpenGL options + // configure global opengl state + // ----------------------------- glEnable(GL_DEPTH_TEST); - // Setup and compile our shaders - Shader shader("normal_mapping.vs", "normal_mapping.frag"); + // build and compile shaders + // ------------------------- + Shader shader("4.normal_mapping.vs", "4.normal_mapping.fs"); - // Load textures - GLuint diffuseMap = loadTexture(FileSystem::getPath("resources/textures/brickwall.jpg").c_str()); - GLuint normalMap = loadTexture(FileSystem::getPath("resources/textures/brickwall_normal.jpg").c_str()); + // load textures + // ------------- + unsigned int diffuseMap = loadTexture(FileSystem::getPath("resources/textures/brickwall.jpg").c_str()); + unsigned int normalMap = loadTexture(FileSystem::getPath("resources/textures/brickwall_normal.jpg").c_str()); - // Set texture units - shader.Use(); - glUniform1i(glGetUniformLocation(shader.Program, "diffuseMap"), 0); - glUniform1i(glGetUniformLocation(shader.Program, "normalMap"), 1); + // shader configuration + // -------------------- + shader.use(); + shader.setInt("diffuseMap", 0); + shader.setInt("normalMap", 1); - // Light position + // lighting info + // ------------- glm::vec3 lightPos(0.5f, 1.0f, 0.3f); - // Game loop + // render loop + // ----------- while (!glfwWindowShouldClose(window)) { - // Set frame time - GLfloat currentFrame = glfwGetTime(); + // per-frame time logic + // -------------------- + float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; - // Check and call events - glfwPollEvents(); - Do_Movement(); + // input + // ----- + processInput(window); - // Clear the colorbuffer + // render + // ------ glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // Configure view/projection matrices - shader.Use(); - glm::mat4 view = camera.GetViewMatrix(); + // configure view/projection matrices glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); - glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view)); - glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); - // Render normal-mapped quad + glm::mat4 view = camera.GetViewMatrix(); + shader.use(); + shader.setMat4("projection", projection); + shader.setMat4("view", view); + // render normal-mapped quad glm::mat4 model; - model = glm::rotate(model, (GLfloat)glfwGetTime() * -0.1f, glm::normalize(glm::vec3(1.0, 0.0, 1.0))); // Rotates the quad to show normal mapping works in all directions - glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); - glUniform3fv(glGetUniformLocation(shader.Program, "lightPos"), 1, &lightPos[0]); - glUniform3fv(glGetUniformLocation(shader.Program, "viewPos"), 1, &camera.Position[0]); + model = glm::rotate(model, glm::radians((float)glfwGetTime() * -10.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0))); // Rotates the quad to show normal mapping works in all directions + shader.setMat4("model", model); + shader.setVec3("viewPos", camera.Position); + shader.setVec3("lightPos", lightPos); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, diffuseMap); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, normalMap); - RenderQuad(); + renderQuad(); // render light source (simply re-renders a smaller plane at the light's position for debugging/visualization) model = glm::mat4(); model = glm::translate(model, lightPos); model = glm::scale(model, glm::vec3(0.1f)); - glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); - RenderQuad(); + shader.setMat4("model", model); + renderQuad(); - // Swap the buffers + // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) + // ------------------------------------------------------------------------------- glfwSwapBuffers(window); + glfwPollEvents(); } glfwTerminate(); return 0; } -// RenderQuad() Renders a 1x1 quad in NDC -GLuint quadVAO = 0; -GLuint quadVBO; -void RenderQuad() +// renderQuad() renders a 1x1 quad in NDC +unsigned int quadVAO = 0; +unsigned int quadVBO; +void renderQuad() { if (quadVAO == 0) { // positions - glm::vec3 pos1(-1.0, 1.0, 0.0); - glm::vec3 pos2(-1.0, -1.0, 0.0); - glm::vec3 pos3(1.0, -1.0, 0.0); - glm::vec3 pos4(1.0, 1.0, 0.0); + glm::vec3 pos1(-1.0f, 1.0f, 0.0f); + glm::vec3 pos2(-1.0f, -1.0f, 0.0f); + glm::vec3 pos3( 1.0f, -1.0f, 0.0f); + glm::vec3 pos4( 1.0f, 1.0f, 0.0f); // texture coordinates - glm::vec2 uv1(0.0, 1.0); - glm::vec2 uv2(0.0, 0.0); - glm::vec2 uv3(1.0, 0.0); - glm::vec2 uv4(1.0, 1.0); + glm::vec2 uv1(0.0f, 1.0f); + glm::vec2 uv2(0.0f, 0.0f); + glm::vec2 uv3(1.0f, 0.0f); + glm::vec2 uv4(1.0f, 1.0f); // normal vector - glm::vec3 nm(0.0, 0.0, 1.0); + glm::vec3 nm(0.0f, 0.0f, 1.0f); // calculate tangent/bitangent vectors of both triangles glm::vec3 tangent1, bitangent1; glm::vec3 tangent2, bitangent2; - // - triangle 1 + // triangle 1 + // ---------- glm::vec3 edge1 = pos2 - pos1; glm::vec3 edge2 = pos3 - pos1; glm::vec2 deltaUV1 = uv2 - uv1; @@ -175,7 +187,8 @@ void RenderQuad() bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z); bitangent1 = glm::normalize(bitangent1); - // - triangle 2 + // triangle 2 + // ---------- edge1 = pos3 - pos1; edge2 = pos4 - pos1; deltaUV1 = uv3 - uv1; @@ -195,8 +208,8 @@ void RenderQuad() bitangent2 = glm::normalize(bitangent2); - GLfloat quadVertices[] = { - // Positions // normal // TexCoords // Tangent // Bitangent + float quadVertices[] = { + // positions // normal // texcoords // tangent // bitangent pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z, pos2.x, pos2.y, pos2.z, nm.x, nm.y, nm.z, uv2.x, uv2.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z, pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z, @@ -205,92 +218,58 @@ void RenderQuad() pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z, pos4.x, pos4.y, pos4.z, nm.x, nm.y, nm.z, uv4.x, uv4.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z }; - // Setup plane VAO + // configure plane VAO glGenVertexArrays(1, &quadVAO); glGenBuffers(1, &quadVBO); glBindVertexArray(quadVAO); glBindBuffer(GL_ARRAY_BUFFER, quadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat))); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)(6 * sizeof(float))); glEnableVertexAttribArray(3); - glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(8 * sizeof(GLfloat))); + glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)(8 * sizeof(float))); glEnableVertexAttribArray(4); - glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(11 * sizeof(GLfloat))); + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)(11 * sizeof(float))); } glBindVertexArray(quadVAO); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); } -// This function loads a texture from file. Note: texture loading functions like these are usually -// managed by a 'Resource Manager' that manages all resources (like textures, models, audio). -// For learning purposes we'll just define it as a utility function. -GLuint loadTexture(GLchar const * path) +// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly +// --------------------------------------------------------------------------------------------------------- +void processInput(GLFWwindow *window) { - //Generate texture ID and load texture data - GLuint textureID; - glGenTextures(1, &textureID); - int width, height; - unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB); - // Assign texture to ID - glBindTexture(GL_TEXTURE_2D, textureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); - glGenerateMipmap(GL_TEXTURE_2D); + if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) + glfwSetWindowShouldClose(window, true); - // Parameters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glBindTexture(GL_TEXTURE_2D, 0); - SOIL_free_image_data(image); - return textureID; -} - -#pragma region "User input" - -bool keys[1024]; -bool keysPressed[1024]; -// Moves/alters the camera positions based on user input -void Do_Movement() -{ - // Camera controls - if (keys[GLFW_KEY_W]) + float cameraSpeed = 2.5 * deltaTime; + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.ProcessKeyboard(FORWARD, deltaTime); - if (keys[GLFW_KEY_S]) + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(BACKWARD, deltaTime); - if (keys[GLFW_KEY_A]) + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(LEFT, deltaTime); - if (keys[GLFW_KEY_D]) + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(RIGHT, deltaTime); } -// Is called whenever a key is pressed/released via GLFW -void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) +// glfw: whenever the window size changed (by OS or user resize) this callback function executes +// --------------------------------------------------------------------------------------------- +void framebuffer_size_callback(GLFWwindow* window, int width, int height) { - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) - glfwSetWindowShouldClose(window, GL_TRUE); - - if (key >= 0 && key <= 1024) - { - if (action == GLFW_PRESS) - keys[key] = true; - else if (action == GLFW_RELEASE) - { - keys[key] = false; - keysPressed[key] = false; - } - } + // make sure the viewport matches the new window dimensions; note that width and + // height will be significantly larger than specified on retina displays. + glViewport(0, 0, width, height); } -GLfloat lastX = 400, lastY = 300; -bool firstMouse = true; -// Moves/alters the camera positions based on user input + +// glfw: whenever the mouse moves, this callback is called +// ------------------------------------------------------- void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) @@ -300,8 +279,8 @@ void mouse_callback(GLFWwindow* window, double xpos, double ypos) firstMouse = false; } - GLfloat xoffset = xpos - lastX; - GLfloat yoffset = lastY - ypos; + float xoffset = xpos - lastX; + float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top lastX = xpos; lastY = ypos; @@ -309,9 +288,48 @@ void mouse_callback(GLFWwindow* window, double xpos, double ypos) camera.ProcessMouseMovement(xoffset, yoffset); } +// glfw: whenever the mouse scroll wheel scrolls, this callback is called +// ---------------------------------------------------------------------- void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(yoffset); } -#pragma endregion \ No newline at end of file +// utility function for loading a 2D texture from file +// --------------------------------------------------- +unsigned int loadTexture(char const * path) +{ + unsigned int textureID; + glGenTextures(1, &textureID); + + int width, height, nrComponents; + unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0); + if (data) + { + GLenum format; + if (nrComponents == 1) + format = GL_RED; + else if (nrComponents == 3) + format = GL_RGB; + else if (nrComponents == 4) + format = GL_RGBA; + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT); // for this tutorial: use GL_CLAMP_TO_EDGE to prevent semi-transparent borders. Due to interpolation it takes texels from next repeat + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + stbi_image_free(data); + } + else + { + std::cout << "Texture failed to load at path: " << path << std::endl; + stbi_image_free(data); + } + + return textureID; +} \ No newline at end of file