diff --git a/include/CubemapTexture.h b/include/CubemapTexture.h new file mode 100644 index 0000000..1500db0 --- /dev/null +++ b/include/CubemapTexture.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class CubemapTexture +{ +public: + CubemapTexture(const std::vector& faces); + void bind(); + +private: + unsigned int textureID; +}; diff --git a/include/Skybox.h b/include/Skybox.h new file mode 100644 index 0000000..f355802 --- /dev/null +++ b/include/Skybox.h @@ -0,0 +1,20 @@ +#pragma once + +namespace djalim { + class ShaderProgram; +} + +#include "CubemapTexture.h" +#include "Camera.h" + +class Skybox +{ +public: + Skybox(); + void load(); + void draw(djalim::ShaderProgram* shader, Camera& camera, const glm::mat4& projection); + +private: + unsigned int skyboxVAO, skyboxVBO; + CubemapTexture* cubemapTexture; +}; diff --git a/include/engine.h b/include/engine.h index 363b9a5..475bfc9 100644 --- a/include/engine.h +++ b/include/engine.h @@ -10,6 +10,7 @@ #include #include "Mesh3D.h" #include "Lighting.h" +#include "Skybox.h" #include #include #include @@ -41,15 +42,18 @@ namespace djalim { int windowHeight; const double ZOOM_SENSITIVITY = -3.0; - const float MOVE_SPEED = 5.0; // units per second + const float MOVE_SPEED = 20.0; // units per second const float MOUSE_SENSITIVITY = 0.1f; double previousSeconds = 0.0; double elapsedSeconds; int frameCount = 0; ShaderProgram shaderProgram; - FPSCamera Camera = FPSCamera(glm::vec3(0.0f, 2.0f, 10.0f)); + ShaderProgram skyboxShaderProgram; + FPSCamera Camera = FPSCamera(); + glm::mat4 projection; Lighting lighting; + Skybox* skybox; Objects3D objects; @@ -61,8 +65,8 @@ namespace djalim { void onUpdate(); void loadHints(); void cameraUpdate(); - void createObject(std::string name, std::string textures, std::string filepath); - void createObject(std::string name, std::string textures, std::string filepath, glm::vec3 position, glm::vec3 rotation, glm::vec3 scale); + void createObject(std::string name, std::string textures, std::string filepath,bool flip = false); + void createObject(std::string name, std::string textures, std::string filepath, glm::vec3 position, glm::vec3 rotation, glm::vec3 scale,bool flip = false); void loadCallbacks(); void showFps(); void onCreate(); diff --git a/src/core/CubemapTexture.cpp b/src/core/CubemapTexture.cpp new file mode 100644 index 0000000..05bca5b --- /dev/null +++ b/src/core/CubemapTexture.cpp @@ -0,0 +1,39 @@ +#include "CubemapTexture.h" +#include "engine.h" +#include "stb_image/stb_image.h" +#include + +CubemapTexture::CubemapTexture(const std::vector& faces) +{ + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); + + int width, height, nrChannels; + for (unsigned int i = 0; i < faces.size(); i++) + { + unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0); + if (data) + { + std::cout << "Cubemap texture loaded: " << faces[i] << " width: " << width << " height: " << height << " nrChannels: " << nrChannels << std::endl; + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, + 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data + ); + stbi_image_free(data); + } + else + { + std::cout << "Cubemap texture failed to load at path: " << faces[i] << " reason: " << stbi_failure_reason() << std::endl; + stbi_image_free(data); + } + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); +} + +void CubemapTexture::bind() +{ + glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); +} diff --git a/src/core/Lighting.cpp b/src/core/Lighting.cpp index daed1bc..85baa34 100644 --- a/src/core/Lighting.cpp +++ b/src/core/Lighting.cpp @@ -1,13 +1,15 @@ #include "Lighting.h" #include "ShaderProgram.h" +#include #include Lighting::Lighting() { // Directional light - sunLight.direction = glm::vec3(0.0f, -0.9f, -0.17f); - sunLight.ambient = glm::vec3(0.1f, 0.1f, 0.1f); - sunLight.diffuse = glm::vec3(0.1f, 0.1f, 0.1f); - sunLight.specular = glm::vec3(0.1f, 0.1f, 0.1f); + glm::vec3 power = glm::vec3(1.0f, 1.0f, 1.0f); + sunLight.direction = glm::normalize(glm::vec3(-0.3f, -1.0f, -0.2f)); // léger angle + sunLight.ambient = glm::vec3(0.25f, 0.22f, 0.18f); // lumière ambiante chaude + sunLight.diffuse = glm::vec3(0.9f, 0.85f, 0.8f); // lumière du soleil douce + sunLight.specular = glm::vec3(0.3f, 0.25f, 0.2f); // reflets légers et chauds // Point Light 1 pointLights[0].position = glm::vec3(-5.0f, 3.8f, 0.0f); diff --git a/src/core/Skybox.cpp b/src/core/Skybox.cpp new file mode 100644 index 0000000..9e40c4c --- /dev/null +++ b/src/core/Skybox.cpp @@ -0,0 +1,90 @@ +#include "Skybox.h" +#include "ShaderProgram.h" +#include "engine.h" + +Skybox::Skybox() { } + +void Skybox::load() +{ + + stbi_set_flip_vertically_on_load(false); + std::vector faces + { + "../assets/textures/skybox/right.jpg", + "../assets/textures/skybox/left.jpg", + "../assets/textures/skybox/top.jpg", + "../assets/textures/skybox/bottom.jpg", + "../assets/textures/skybox/front.jpg", + "../assets/textures/skybox/back.jpg" + }; + cubemapTexture = new CubemapTexture(faces); + + float skyboxVertices[] = { + // positions + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + -1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f + }; + + glGenVertexArrays(1, &skyboxVAO); + glGenBuffers(1, &skyboxVBO); + glBindVertexArray(skyboxVAO); + glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + stbi_set_flip_vertically_on_load(true); +} + +void Skybox::draw(djalim::ShaderProgram* shader, Camera& camera, const glm::mat4& projection) +{ + glDepthFunc(GL_LEQUAL); + shader->use(); + glm::mat4 view = glm::mat4(glm::mat3(camera.getViewMatrix())); + shader->setUniform("view", view); + shader->setUniform("projection", projection); + + glBindVertexArray(skyboxVAO); + cubemapTexture->bind(); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + glDepthFunc(GL_LESS); +} diff --git a/src/core/callbacks.cpp b/src/core/callbacks.cpp index 97ea5a2..8e17daf 100644 --- a/src/core/callbacks.cpp +++ b/src/core/callbacks.cpp @@ -4,57 +4,58 @@ extern bool gWireframe; // TODO Try to make callbacks more flexible // there should not be a single function with 100+ if elses 💀 -void mainCallback(GLFWwindow* window, int key, int scancode, int action, int mods) -{ - //TODO Extract this in a exitCallBack Function +void mainCallback(GLFWwindow *window, int key, int scancode, int action, + int mods) { + auto *engine = + static_cast(glfwGetWindowUserPointer(window)); + + // Quit if ((key == GLFW_KEY_ESCAPE || key == GLFW_KEY_Q) && action == GLFW_PRESS) { glfwSetWindowShouldClose(window, GL_TRUE); } - // end todo - //TODO Extract this in a wireframeCallBack Function - if (key == GLFW_KEY_W && action == GLFW_PRESS) + // Toggle wireframe + if (key == GLFW_KEY_E && action == GLFW_PRESS) gWireframe = !gWireframe; if (gWireframe) - - glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); else - glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); - // end todo + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + // Toggle spot light + if (key == GLFW_KEY_F && action == GLFW_PRESS) { + engine->lighting.spotLight.on = !engine->lighting.spotLight.on; + } } -void glfw_onFramebufferSize(GLFWwindow* window, int width, int height) -{ - auto* engine = static_cast(glfwGetWindowUserPointer(window)); +void glfw_onFramebufferSize(GLFWwindow *window, int width, int height) { + auto *engine = + static_cast(glfwGetWindowUserPointer(window)); - engine->windowWidth = width; - engine->windowHeight = height; - - - // Define the viewport dimensions - int w, h; - glfwGetFramebufferSize( engine->window, &w, &h); // For retina display - glViewport(0, 0, w, h); - // glViewport(0, 0, gWindowWidth, gWindowHeight); + engine->windowWidth = width; + engine->windowHeight = height; + // Define the viewport dimensions + int w, h; + glfwGetFramebufferSize(engine->window, &w, &h); // For retina display + glViewport(0, 0, w, h); + // glViewport(0, 0, gWindowWidth, gWindowHeight); } //----------------------------------------------------------------------------- // Called by GLFW when the mouse wheel is rotated //----------------------------------------------------------------------------- -void glfw_onMouseScroll(GLFWwindow* window, double deltaX, double deltaY) -{ - auto* engine = static_cast(glfwGetWindowUserPointer(window)); - double fov = engine->Camera.getFOV() + deltaY * engine->ZOOM_SENSITIVITY; +void glfw_onMouseScroll(GLFWwindow *window, double deltaX, double deltaY) { + auto *engine = + static_cast(glfwGetWindowUserPointer(window)); + double fov = engine->Camera.getFOV() + deltaY * engine->ZOOM_SENSITIVITY; - fov = glm::clamp(fov, 1.0, 120.0); + fov = glm::clamp(fov, 1.0, 120.0); - engine->Camera.setFOV((float)fov); + engine->Camera.setFOV((float)fov); } - -void djalim::OpenGlEngine::loadCallbacks(){ +void djalim::OpenGlEngine::loadCallbacks() { glfwSetKeyCallback(window, mainCallback); glfwSetFramebufferSizeCallback(window, glfw_onFramebufferSize); glfwSetScrollCallback(window, glfw_onMouseScroll); diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 385ec04..064098d 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -1,6 +1,7 @@ #include "engine.h" #include "Lighting.h" #include "Mesh3D.h" +#include "Skybox.h" #include #include #include @@ -51,21 +52,28 @@ djalim::OpenGlEngine::OpenGlEngine(const char* title, int width, int height) { exit(1); } + loaded = skyboxShaderProgram.loadShaders("../assets/shaders/skybox.vert", "../assets/shaders/skybox.frag"); + if (!loaded) { + std::cerr << "Failed to load skybox shaders correctly" << std::endl; + exit(1); + } + onCreate(); + skybox = new Skybox(); + } -void djalim::OpenGlEngine::createObject(std::string name, std::string textures, std::string filepath, glm::vec3 position, glm::vec3 rotation, glm::vec3 scale){ - createObject(name,textures,filepath); +void djalim::OpenGlEngine::createObject(std::string name, std::string textures, std::string filepath, glm::vec3 position, glm::vec3 rotation, glm::vec3 scale, bool flip){ + createObject(name,textures,filepath,flip); (objects[name])->position = position; (objects[name])->rotation = rotation; (objects[name])->scale = scale; } -void djalim::OpenGlEngine::createObject(std::string name, std::string textures, std::string filepath){ +void djalim::OpenGlEngine::createObject(std::string name, std::string textures, std::string filepath,bool flip){ // Load the mesh from the file - bool flip = (name == "mimikyu"); objects[name] = std::make_unique(Mesh3D(filepath, flip)); bool textureLoaded = objects[name]->texture.loadTexture(textures); @@ -105,12 +113,15 @@ void djalim::OpenGlEngine::createObject(std::string name, std::string textures, } djalim::OpenGlEngine::~OpenGlEngine() { + delete skybox; onDestroy(); glfwDestroyWindow(window); glfwTerminate(); } void djalim::OpenGlEngine::start(){ + skybox->load(); + projection = glm::perspective(glm::radians(Camera.getFOV()), (float)windowWidth / (float)windowHeight, 0.1f, 2000.0f); // Main loop double currentSeconds; @@ -125,6 +136,7 @@ void djalim::OpenGlEngine::start(){ //glClearColor(0, 0.5, 0.5, 1.0f); onUpdate(); + skybox->draw(&skyboxShaderProgram, Camera, projection); cameraUpdate(); glfwSwapBuffers(this->window); @@ -170,6 +182,9 @@ void djalim::OpenGlEngine::cameraUpdate(){ // Clamp mouse cursor to center of screen glfwSetCursorPos(window, windowWidth / 2.0, windowHeight / 2.0); + //std::cout << "Camera position: " << Camera.getPosition().x << ", " << Camera.getPosition().y << ", " << Camera.getPosition().z << std::endl; + //std::cout << "Camera orientation: " << Camera.getLook().x << ", " << Camera.getLook().y << ", " << Camera.getLook().z << std::endl; + // Camera FPS movement // Forward/backward diff --git a/src/onCreate.cpp b/src/onCreate.cpp index 67eb567..04ffac1 100644 --- a/src/onCreate.cpp +++ b/src/onCreate.cpp @@ -1,28 +1,43 @@ +#include "Camera.h" #include "engine.h" #include "Texture2D.h" +#include +#include +#include +#include void djalim::OpenGlEngine::onCreate(){ glEnable(GL_DEPTH_TEST); stbi_set_flip_vertically_on_load(true); - shaderProgram.loadShaders("../assets/shaders/lighting.vert", "../assets/shaders/lighting.frag"); lighting = Lighting(); - createObject("mimikyu", - "../assets/textures/crate.jpg", - "../assets/models/destiny island.obj", + createObject("island", + "../assets/textures/FloatingIsland.png", + "../assets/models/FloatingIsland.obj", + glm::vec3(1.0,1.0,1.0), + glm::vec3(0.0,0.0,0.0), + glm::vec3(100.0,100.0,100.0) + ); + + createObject("tower", + "../assets/textures/prof.png", + "../assets/models/castle.obj", + glm::vec3(1.0,65.0,1.0), glm::vec3(0.0,0.0,0.0), - glm::vec3(0.0,0.0,0.0), - glm::vec3(.01,.01,.01) + glm::vec3(4,4,4) ); - createObject("cube", - "../assets/textures/crate.jpg", - "../assets/models/crate.obj", + + createObject("tree", + "../assets/textures/tree.jpg", + "../assets/models/tree.obj", glm::vec3(1.0,1.0,1.0), - glm::vec3(0.0,1.0,0.0), + glm::vec3(0.0,0.0,0.0), glm::vec3(1.0,1.0,1.0) - ); + ); + + Camera.setPosition(glm::vec3(104.0,119.0,73.0)); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); } diff --git a/src/onUpdate.cpp b/src/onUpdate.cpp index 8890bbe..664b383 100644 --- a/src/onUpdate.cpp +++ b/src/onUpdate.cpp @@ -9,22 +9,18 @@ void djalim::OpenGlEngine::onUpdate(){ shaderProgram.use(); shaderProgram.setUniform("material.diffuseMap", 0); - glm::mat4 model(1.0), view(1.0), projection(1.0); + glm::mat4 model(1.0), view(1.0); // Create the View matrix view = Camera.getViewMatrix(); - // Create the projection matrix - projection = glm::perspective(glm::radians(Camera.getFOV()), (float)windowWidth / (float)windowHeight, 0.1f, 200.0f); - // Matrice de Projection shaderProgram.setUniform("projection", projection); shaderProgram.setUniform("view", view); lighting.update(shaderProgram, Camera); - draw((objects["cube"]).get()); - draw((objects["mimikyu"]).get()); - - + for (auto const& [key, val] : objects) { + draw(val.get()); + } }