1 Commits

Author SHA1 Message Date
50a4f56e75 feat: add lights 2025-10-14 11:45:46 +02:00
14 changed files with 355 additions and 10 deletions

View File

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

View File

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

View File

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

56
src/light.cpp Normal file
View File

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

67
src/light.h Normal file
View File

@@ -0,0 +1,67 @@
#ifndef LIGHT_H_
#define LIGHT_H_
#include <memory>
#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>& shader) const;
private:
Type type_;
glm::vec3 color_;
float intensity_;
float linear_attenuation_;
};
#endif // LIGHT_H_

View File

@@ -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<Texture>& tex,
const std::shared_ptr<Shader>& 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<float>(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<int>(state::lights.size()));
// Prepare each light
for (size_t i = 0; i < state::lights.size(); ++i) {
state::lights[i]->prepare(static_cast<int>(i), active_shader);
}
// Set texture-related uniforms
const int use_texture_loc =
active_shader->uniform_location("useTexture");

View File

@@ -3,6 +3,8 @@
#include <memory>
#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> shader_;
std::shared_ptr<Texture> texture_;
glm::vec4 color_;
uint8_t shininess_;
};
#endif // MATERIAL_H_

View File

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

View File

@@ -73,6 +73,14 @@ void Shader::setup_attribs() const
loc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
reinterpret_cast<const void*>(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<const void*>(offsetof(Vertex, normal)));
}
}
int Shader::uniform_location(const char* key) const

View File

@@ -2,9 +2,11 @@
#define STATE_H_
#include <memory>
#include <vector>
#include "../lib/glm/glm.hpp"
class Shader;
class Light;
namespace state {
@@ -12,6 +14,8 @@ inline std::shared_ptr<Shader> 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<std::shared_ptr<Light>> lights;
inline glm::vec3 ambient = glm::vec3(0.0f, 0.0f, 0.0f);
} // namespace state

View File

@@ -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_

View File

@@ -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>& entity)
{
@@ -17,10 +19,20 @@ void World::add_entity(const std::shared_ptr<Entity>& 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> light = std::dynamic_pointer_cast<Light>(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>& entity)
@@ -43,6 +55,16 @@ void World::remove_entity(const std::shared_ptr<Entity>& entity)
Logger::info("Camera removed from world");
}
}
// Check if entity is a light and remove from lights list
std::shared_ptr<Light> light = std::dynamic_pointer_cast<Light>(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

View File

@@ -4,8 +4,11 @@
#include <memory>
#include <vector>
#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<std::shared_ptr<Entity>> entities_;
std::vector<std::shared_ptr<Camera>> cameras_;
std::vector<std::shared_ptr<Light>> lights_;
glm::vec3 ambient_{0.0f, 0.0f, 0.0f};
};
#endif // WORLD_H_

View File

@@ -164,6 +164,7 @@
<ClInclude Include="src\camera.h" />
<ClInclude Include="src\engine.h" />
<ClInclude Include="src\entity.h" />
<ClInclude Include="src\light.h" />
<ClInclude Include="src\logger.h" />
<ClInclude Include="src\material.h" />
<ClInclude Include="src\mesh.h" />
@@ -180,6 +181,7 @@
<ClCompile Include="src\camera.cpp" />
<ClCompile Include="src\engine.cpp" />
<ClCompile Include="src\entity.cpp" />
<ClCompile Include="src\light.cpp" />
<ClCompile Include="src\logger.cpp" />
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\material.cpp" />