Add skybox rendering with cubemap textures; integrate into engine

This commit is contained in:
Djalim Simaila 2025-10-31 16:03:53 +01:00
parent 652577d0e6
commit 11fde0d25a
10 changed files with 257 additions and 61 deletions

14
include/CubemapTexture.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <string>
#include <vector>
class CubemapTexture
{
public:
CubemapTexture(const std::vector<std::string>& faces);
void bind();
private:
unsigned int textureID;
};

20
include/Skybox.h Normal file
View File

@ -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;
};

View File

@ -10,6 +10,7 @@
#include <string>
#include "Mesh3D.h"
#include "Lighting.h"
#include "Skybox.h"
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
@ -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();

View File

@ -0,0 +1,39 @@
#include "CubemapTexture.h"
#include "engine.h"
#include "stb_image/stb_image.h"
#include <iostream>
CubemapTexture::CubemapTexture(const std::vector<std::string>& 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);
}

View File

@ -1,13 +1,15 @@
#include "Lighting.h"
#include "ShaderProgram.h"
#include <glm/fwd.hpp>
#include <glm/gtc/matrix_transform.hpp>
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);

90
src/core/Skybox.cpp Normal file
View File

@ -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<std::string> 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);
}

View File

@ -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<djalim::OpenGlEngine *>(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<djalim::OpenGlEngine*>(glfwGetWindowUserPointer(window));
void glfw_onFramebufferSize(GLFWwindow *window, int width, int height) {
auto *engine =
static_cast<djalim::OpenGlEngine *>(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<djalim::OpenGlEngine*>(glfwGetWindowUserPointer(window));
double fov = engine->Camera.getFOV() + deltaY * engine->ZOOM_SENSITIVITY;
void glfw_onMouseScroll(GLFWwindow *window, double deltaX, double deltaY) {
auto *engine =
static_cast<djalim::OpenGlEngine *>(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);

View File

@ -1,6 +1,7 @@
#include "engine.h"
#include "Lighting.h"
#include "Mesh3D.h"
#include "Skybox.h"
#include <GLFW/glfw3.h>
#include <cstdlib>
#include <glm/ext/vector_float3.hpp>
@ -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>(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

View File

@ -1,28 +1,43 @@
#include "Camera.h"
#include "engine.h"
#include "Texture2D.h"
#include <glm/fwd.hpp>
#include <string>
#include <vector>
#include <map>
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);
}

View File

@ -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());
}
}