From 50a4f56e7574df31365b2031e70a2d24aa880d90 Mon Sep 17 00:00:00 2001 From: Daniel Poveda Date: Tue, 14 Oct 2025 11:45:46 +0200 Subject: [PATCH] feat: add lights --- data/shaders/fragment.glsl | 60 ++++++++++++++++++++++++++++++++-- data/shaders/vertex.glsl | 7 ++++ src/engine.cpp | 36 ++++++++++++++++++++ src/light.cpp | 56 +++++++++++++++++++++++++++++++ src/light.h | 67 ++++++++++++++++++++++++++++++++++++++ src/material.cpp | 40 ++++++++++++++++++++++- src/material.h | 22 +++++++++++++ src/mesh.cpp | 8 +++++ src/shader.cpp | 8 +++++ src/state.h | 12 ++++--- src/vertex.h | 1 + src/world.cpp | 32 ++++++++++++++++-- src/world.h | 14 ++++++++ ugine3d.vcxproj | 2 ++ 14 files changed, 355 insertions(+), 10 deletions(-) create mode 100644 src/light.cpp create mode 100644 src/light.h diff --git a/data/shaders/fragment.glsl b/data/shaders/fragment.glsl index 396a596..3989d52 100644 --- a/data/shaders/fragment.glsl +++ b/data/shaders/fragment.glsl @@ -1,12 +1,68 @@ uniform int useTexture; uniform sampler2D texSampler; +uniform int numLights; +uniform vec4 materialColor; +uniform float shininess; +uniform vec3 ambientLight; + +struct Light { + vec4 vector; + vec3 color; + float intensity; + float linear_att; +}; + +uniform Light lights[10]; + varying vec3 fcolor; varying vec2 ftexcoord; +varying vec3 fnormal; +varying vec4 fposition; void main() { + vec3 diffuseAccum = vec3(0.0, 0.0, 0.0); + vec3 specularAccum = vec3(0.0, 0.0, 0.0); + + if (numLights > 0) { + diffuseAccum = ambientLight; + + for (int i = 0; i < 10; i++) { + if (i >= numLights) break; + + vec3 N = normalize(fnormal); + vec3 L = lights[i].vector.xyz; + float attenuation = 1.0; + + if (lights[i].vector.w == 1.0) { + L = L - fposition.xyz; + float distance = length(L); + attenuation = 1.0 / (1.0 + lights[i].linear_att * distance); + } + + L = normalize(L); + float NdotL = max(dot(N, L), 0.0); + + diffuseAccum += NdotL * lights[i].color * lights[i].intensity * attenuation; + + if (shininess > 0.0 && NdotL > 0.0) { + vec3 V = normalize(-fposition.xyz); + vec3 H = normalize(L + V); + float NdotH = max(dot(N, H), 0.0); + specularAccum += pow(NdotH, shininess) * attenuation * lights[i].color * lights[i].intensity; + } + } + } + + vec4 fragmentColor; if (useTexture == 1) { - gl_FragColor = texture2D(texSampler, ftexcoord); + fragmentColor = texture2D(texSampler, ftexcoord); } else { - gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + fragmentColor = materialColor; + } + + if (numLights > 0) { + gl_FragColor = vec4(fragmentColor.rgb * diffuseAccum + specularAccum, fragmentColor.a); + } else { + gl_FragColor = fragmentColor; } } diff --git a/data/shaders/vertex.glsl b/data/shaders/vertex.glsl index 6ca3e2b..a769912 100644 --- a/data/shaders/vertex.glsl +++ b/data/shaders/vertex.glsl @@ -1,12 +1,19 @@ uniform mat4 mvp; +uniform mat4 modelView; +uniform mat4 normalMatrix; attribute vec3 vpos; attribute vec3 vcolor; attribute vec2 vtexcoord; +attribute vec3 vnormal; varying vec3 fcolor; varying vec2 ftexcoord; +varying vec3 fnormal; +varying vec4 fposition; void main() { gl_Position = mvp * vec4(vpos, 1); fcolor = vcolor; ftexcoord = vtexcoord; + fnormal = vec3(normalMatrix * vec4(vnormal, 0.0)); + fposition = modelView * vec4(vpos, 1); } \ No newline at end of file diff --git a/src/engine.cpp b/src/engine.cpp index 9552592..8c7bcb0 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -9,6 +9,7 @@ #include "buffer.h" #include "camera.h" +#include "light.h" #include "logger.h" #include "material.h" #include "mesh.h" @@ -19,6 +20,10 @@ #include "vertex.h" #include "world.h" +// Instance variables +std::shared_ptr point_light; + +// Constants constexpr int screen_width = 800; constexpr int screen_height = 600; constexpr double fps_limit = 1.0 / 60.0; @@ -140,6 +145,7 @@ void Engine::setup() // Create world world_ = std::make_unique(); + world_->set_ambient(glm::vec3(0.2f, 0.2f, 0.2f)); Logger::info("World created"); // Create camera @@ -180,6 +186,26 @@ void Engine::setup() Logger::info("Gunslinger model loaded and added to world"); } + // Create directional light + auto directional_light = std::make_shared(); + directional_light->set_type(Light::Type::DIRECTIONAL); + directional_light->set_color(glm::vec3(1.0f, 1.0f, 1.0f)); + directional_light->set_intensity(1.0f); + directional_light->set_position(glm::normalize( + glm::vec3(1.0f, 1.0f, 1.0f))); // Use position as direction + world_->add_entity(directional_light); + Logger::info("Directional light created and added to world"); + + // Create point light + point_light = std::make_shared(); + point_light->set_type(Light::Type::POINT); + point_light->set_color(glm::vec3(1.0f, 0.0f, 0.0f)); + point_light->set_intensity(2.0f); + point_light->set_linear_attenuation(0.2f); + point_light->set_position(glm::vec3(5.0f, 0.0f, 0.0f)); + world_->add_entity(point_light); + Logger::info("Point light created and added to world"); + Logger::info("Scene setup complete"); } @@ -227,6 +253,16 @@ void Engine::process_input(const double delta_time) void Engine::update(const double delta_time) { + // Update angle for orbiting light + angle_ += delta_time; + + // Make point light orbit around the model + if (point_light) { + constexpr float radius = 7.5f; + point_light->set_position(glm::vec3(radius * cos(angle_), 2.0f, + radius * sin(angle_))); + } + // Update world world_->update(static_cast(delta_time)); } diff --git a/src/light.cpp b/src/light.cpp new file mode 100644 index 0000000..e5e78c5 --- /dev/null +++ b/src/light.cpp @@ -0,0 +1,56 @@ +#include "light.h" + +#include "../lib/glm/gtc/matrix_transform.hpp" + +#include "shader.h" +#include "state.h" + +Light::Light() + : Entity() + , type_(Type::DIRECTIONAL) + , color_(1.0f, 1.0f, 1.0f) + , intensity_(1.0f) + , linear_attenuation_(0.0f) +{ +} + +void Light::prepare(int index, std::shared_ptr& shader) const +{ + if (!shader) + return; + + // Build uniform names for this light index + std::string light_vector = + "lights[" + std::to_string(index) + "].vector"; + std::string light_color = "lights[" + std::to_string(index) + "].color"; + std::string light_intensity = + "lights[" + std::to_string(index) + "].intensity"; + std::string light_linear_att = + "lights[" + std::to_string(index) + "].linear_att"; + + // Get uniform locations + int vector_loc = shader->uniform_location(light_vector.c_str()); + int color_loc = shader->uniform_location(light_color.c_str()); + int intensity_loc = shader->uniform_location(light_intensity.c_str()); + int linear_att_loc = shader->uniform_location(light_linear_att.c_str()); + + // Calculate light vector in view space + glm::vec4 light_vector_view; + if (type_ == Type::DIRECTIONAL) { + // For directional lights, w = 0 + glm::vec3 direction = + glm::normalize(position_); // Use position as direction + light_vector_view = + state::view_matrix * glm::vec4(direction, 0.0f); + } else { + // For point lights, w = 1 + light_vector_view = + state::view_matrix * glm::vec4(position_, 1.0f); + } + + // Set uniforms + Shader::set_vec4(vector_loc, light_vector_view); + Shader::set_vec3(color_loc, color_); + Shader::set_float(intensity_loc, intensity_); + Shader::set_float(linear_att_loc, linear_attenuation_); +} diff --git a/src/light.h b/src/light.h new file mode 100644 index 0000000..2307825 --- /dev/null +++ b/src/light.h @@ -0,0 +1,67 @@ +#ifndef LIGHT_H_ +#define LIGHT_H_ + +#include + +#include "../lib/glm/glm.hpp" + +#include "entity.h" + +class Shader; + +class Light : public Entity { +public: + enum class Type { + DIRECTIONAL, + POINT + }; + + Light(); + ~Light() override = default; + + [[nodiscard]] Type type() const + { + return type_; + } + void set_type(Type type) + { + type_ = type; + } + + [[nodiscard]] const glm::vec3& color() const + { + return color_; + } + void set_color(const glm::vec3& color) + { + color_ = color; + } + + [[nodiscard]] float intensity() const + { + return intensity_; + } + void set_intensity(float intensity) + { + intensity_ = intensity; + } + + [[nodiscard]] float linear_attenuation() const + { + return linear_attenuation_; + } + void set_linear_attenuation(float att) + { + linear_attenuation_ = att; + } + + void prepare(int index, std::shared_ptr& shader) const; + +private: + Type type_; + glm::vec3 color_; + float intensity_; + float linear_attenuation_; +}; + +#endif // LIGHT_H_ diff --git a/src/material.cpp b/src/material.cpp index cc25dbc..8e878b1 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -1,14 +1,19 @@ #include "material.h" #include "../lib/glm/glm.hpp" +#include "../lib/glm/gtc/matrix_transform.hpp" +#include "light.h" #include "shader.h" #include "state.h" #include "texture.h" Material::Material(const std::shared_ptr& tex, const std::shared_ptr& shader) - : shader_(shader), texture_(tex) + : shader_(shader) + , texture_(tex) + , color_(1.0f, 1.0f, 1.0f, 1.0f) + , shininess_(0) { } @@ -42,6 +47,39 @@ void Material::prepare() const int mvp_loc = active_shader->uniform_location("mvp"); Shader::set_mat4(mvp_loc, mvp); + // Set ModelView matrix + const glm::mat4 model_view = state::view_matrix * state::model_matrix; + const int model_view_loc = active_shader->uniform_location("modelView"); + Shader::set_mat4(model_view_loc, model_view); + + // Set Normal matrix (transpose of inverse of ModelView) + const glm::mat4 normal_matrix = + glm::transpose(glm::inverse(model_view)); + const int normal_matrix_loc = + active_shader->uniform_location("normalMatrix"); + Shader::set_mat4(normal_matrix_loc, normal_matrix); + + // Set material properties + const int material_color_loc = + active_shader->uniform_location("materialColor"); + Shader::set_vec4(material_color_loc, color_); + + const int shininess_loc = active_shader->uniform_location("shininess"); + Shader::set_float(shininess_loc, static_cast(shininess_)); + + // Set ambient light + const int ambient_loc = active_shader->uniform_location("ambientLight"); + Shader::set_vec3(ambient_loc, state::ambient); + + // Set number of lights + const int num_lights_loc = active_shader->uniform_location("numLights"); + Shader::set_int(num_lights_loc, static_cast(state::lights.size())); + + // Prepare each light + for (size_t i = 0; i < state::lights.size(); ++i) { + state::lights[i]->prepare(static_cast(i), active_shader); + } + // Set texture-related uniforms const int use_texture_loc = active_shader->uniform_location("useTexture"); diff --git a/src/material.h b/src/material.h index b7bf9d4..b197953 100644 --- a/src/material.h +++ b/src/material.h @@ -3,6 +3,8 @@ #include +#include "../lib/glm/glm.hpp" + class Shader; class Texture; @@ -25,11 +27,31 @@ public: texture_ = tex; } + [[nodiscard]] const glm::vec4& color() const + { + return color_; + } + void set_color(const glm::vec4& color) + { + color_ = color; + } + + [[nodiscard]] uint8_t shininess() const + { + return shininess_; + } + void set_shininess(uint8_t shininess) + { + shininess_ = shininess; + } + void prepare(); private: std::shared_ptr shader_; std::shared_ptr texture_; + glm::vec4 color_; + uint8_t shininess_; }; #endif // MATERIAL_H_ diff --git a/src/mesh.cpp b/src/mesh.cpp index 3348d4c..5b83808 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -46,6 +46,14 @@ Vertex create_vertex(const tinyobj::attrib_t& attrib, vertex.tex_coord.y = attrib.texcoords[2 * idx.texcoord_index + 1]; } + // Normal + vertex.normal = glm::vec3(0.0f, 0.0f, 1.0f); + if (idx.normal_index >= 0) { + vertex.normal.x = attrib.normals[3 * idx.normal_index + 0]; + vertex.normal.y = attrib.normals[3 * idx.normal_index + 1]; + vertex.normal.z = attrib.normals[3 * idx.normal_index + 2]; + } + return vertex; } diff --git a/src/shader.cpp b/src/shader.cpp index e2023e0..0c88315 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -73,6 +73,14 @@ void Shader::setup_attribs() const loc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, tex_coord))); } + + loc = glGetAttribLocation(program_id_, "vnormal"); + if (loc != -1) { + glEnableVertexAttribArray(loc); + glVertexAttribPointer( + loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), + reinterpret_cast(offsetof(Vertex, normal))); + } } int Shader::uniform_location(const char* key) const diff --git a/src/state.h b/src/state.h index c611ba6..667a1c8 100644 --- a/src/state.h +++ b/src/state.h @@ -2,16 +2,20 @@ #define STATE_H_ #include +#include #include "../lib/glm/glm.hpp" class Shader; +class Light; namespace state { -inline std::shared_ptr default_shader = nullptr; -inline glm::mat4 projection_matrix = glm::mat4(1.0f); -inline glm::mat4 view_matrix = glm::mat4(1.0f); -inline glm::mat4 model_matrix = glm::mat4(1.0f); +inline std::shared_ptr default_shader = nullptr; +inline glm::mat4 projection_matrix = glm::mat4(1.0f); +inline glm::mat4 view_matrix = glm::mat4(1.0f); +inline glm::mat4 model_matrix = glm::mat4(1.0f); +inline std::vector> lights; +inline glm::vec3 ambient = glm::vec3(0.0f, 0.0f, 0.0f); } // namespace state diff --git a/src/vertex.h b/src/vertex.h index eaa268a..4de3eae 100644 --- a/src/vertex.h +++ b/src/vertex.h @@ -7,6 +7,7 @@ struct Vertex { glm::vec3 position{0.0f, 0.0f, 0.0f}; glm::vec3 color{0.0f, 0.0f, 0.0f}; glm::vec2 tex_coord{0.0f, 0.0f}; + glm::vec3 normal{0.0f, 0.0f, 0.0f}; }; #endif // VERTEX_H_ diff --git a/src/world.cpp b/src/world.cpp index a21816c..d690dd2 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -4,7 +4,9 @@ #include "camera.h" #include "entity.h" +#include "light.h" #include "logger.h" +#include "state.h" void World::add_entity(const std::shared_ptr& entity) { @@ -17,10 +19,20 @@ void World::add_entity(const std::shared_ptr& entity) cameras_.push_back(camera); Logger::info(sstr("Camera added to world (total cameras: ", cameras_.size(), ")")); - } else { - Logger::info(sstr("Entity added to world (total entities: ", - entities_.size(), ")")); + return; } + + // Check if entity is a light + std::shared_ptr light = std::dynamic_pointer_cast(entity); + if (light) { + lights_.push_back(light); + Logger::info(sstr("Light added to world (total lights: ", + lights_.size(), ")")); + return; + } + + Logger::info(sstr( + "Entity added to world (total entities: ", entities_.size(), ")")); } void World::remove_entity(const std::shared_ptr& entity) @@ -43,6 +55,16 @@ void World::remove_entity(const std::shared_ptr& entity) Logger::info("Camera removed from world"); } } + + // Check if entity is a light and remove from lights list + std::shared_ptr light = std::dynamic_pointer_cast(entity); + if (light) { + auto lightIt = std::find(lights_.begin(), lights_.end(), light); + if (lightIt != lights_.end()) { + lights_.erase(lightIt); + Logger::info("Light removed from world"); + } + } } void World::update(float delta_time) @@ -54,6 +76,10 @@ void World::update(float delta_time) void World::draw() { + // Update state with world lighting information + state::ambient = ambient_; + state::lights = lights_; + // Draw for each camera for (auto& camera : cameras_) { // Prepare the camera (sets viewport, projection, view, clears diff --git a/src/world.h b/src/world.h index bf8dbf4..48abc04 100644 --- a/src/world.h +++ b/src/world.h @@ -4,8 +4,11 @@ #include #include +#include "../lib/glm/glm.hpp" + class Entity; class Camera; +class Light; class World { public: @@ -27,12 +30,23 @@ public: return entities_[index]; } + [[nodiscard]] const glm::vec3& ambient() const + { + return ambient_; + } + void set_ambient(const glm::vec3& ambient) + { + ambient_ = ambient; + } + void update(float delta_time); void draw(); private: std::vector> entities_; std::vector> cameras_; + std::vector> lights_; + glm::vec3 ambient_{0.0f, 0.0f, 0.0f}; }; #endif // WORLD_H_ diff --git a/ugine3d.vcxproj b/ugine3d.vcxproj index b5d25bc..91077dc 100644 --- a/ugine3d.vcxproj +++ b/ugine3d.vcxproj @@ -164,6 +164,7 @@ + @@ -180,6 +181,7 @@ +