From c65a3ba314ac6f93db2a48c4e8a115036e4ee30a Mon Sep 17 00:00:00 2001
From: Abdelrahman Said <said.abdelrahman89@gmail.com>
Date: Mon, 23 Dec 2024 23:41:23 +0000
Subject: [PATCH] Create a scene with multiple lights

---
 shaders/frag.glsl | 82 ++++++++++++++++++++++++++++++++++++-----
 src/main.cc       | 93 +++++++++++++++++++++++++++++++++++------------
 2 files changed, 143 insertions(+), 32 deletions(-)

diff --git a/shaders/frag.glsl b/shaders/frag.glsl
index 2750366..f433323 100644
--- a/shaders/frag.glsl
+++ b/shaders/frag.glsl
@@ -1,12 +1,31 @@
 #version 330 core
 
+#define POINT_LIGHT_COUNT 4
+
 struct Material {
   sampler2D diffuse;
   sampler2D specular;
   float shininess;
 };
 
-struct Light {
+struct DirLight {
+  vec3 direction;
+  vec3 ambient;
+  vec3 diffuse;
+  vec3 specular;
+};
+
+struct PointLight {
+  vec3 position;
+  vec3 ambient;
+  vec3 diffuse;
+  vec3 specular;
+  float constant;
+  float linear;
+  float quadratic;
+};
+
+struct SpotLight {
   vec3 position;
   vec3 direction;
   vec3 ambient;
@@ -23,19 +42,64 @@ in vec2 uv_coords;
 out vec4 color;
 
 uniform Material material;
-uniform Light light;
+uniform DirLight directional_light;
+uniform PointLight point_lights[POINT_LIGHT_COUNT];
+uniform SpotLight spot_light;
 uniform vec3 camera_position;
 
+vec3 calc_dir_light(DirLight light, vec3 normal, vec3 view_direction);
+vec3 calc_point_light(PointLight light, vec3 normal, vec3 frag_position, vec3 view_direction);
+vec3 calc_spot_light(SpotLight light, vec3 normal, vec3 frag_position, vec3 view_direction);
+
 void main() {
-  float ambient_strength   = 0.15;
-  float specular_strength  = 0.5;
-  vec3  normal             = normalize(vert_normal);
+  vec3  normal         = normalize(vert_normal);
+  vec3  view_direction = normalize(camera_position - frag_position);
+
+  vec3 result = calc_dir_light(directional_light, normal, view_direction);
+
+  for (int i = 0; i < POINT_LIGHT_COUNT; ++i) {
+    result += calc_point_light(point_lights[i], normal, frag_position, view_direction);
+  }
+
+  result += calc_spot_light(spot_light, normal, frag_position, view_direction);
+
+  color = vec4(result, 1.0);
+};
+
+vec3 calc_dir_light(DirLight light, vec3 normal, vec3 view_direction) {
+  vec3 light_direction     = normalize(-light.direction);
+  vec3  reflect_direction  = reflect(-light_direction, normal);
+  float diff               = max(dot(normal, light_direction), 0.0);
+  vec3  diff_tex           = vec3(texture(material.diffuse, uv_coords));
+  float spec               = pow(max(dot(reflect_direction, view_direction), 0.0), material.shininess);
+  vec3  ambient            = light.ambient * diff_tex;
+  vec3  diffuse            = light.diffuse * (diff * diff_tex);
+  vec3  specular           = light.specular * (spec * vec3(texture(material.specular, uv_coords)));
+
+  return ambient + diffuse + specular;
+}
+
+vec3 calc_point_light(PointLight light, vec3 normal, vec3 frag_position, vec3 view_direction) {
   vec3 light_direction     = normalize(light.position - frag_position);
+  vec3  reflect_direction  = reflect(-light_direction, normal);
+  float distance           = length(light.position - frag_position);
+  float attenuation        = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
+  float diff               = max(dot(normal, light_direction), 0.0);
+  vec3  diff_tex           = vec3(texture(material.diffuse, uv_coords));
+  float spec               = pow(max(dot(reflect_direction, view_direction), 0.0), material.shininess);
+  vec3  ambient            = light.ambient * diff_tex * attenuation;
+  vec3  diffuse            = light.diffuse * (diff * diff_tex) * attenuation;
+  vec3  specular           = light.specular * (spec * vec3(texture(material.specular, uv_coords))) * attenuation;
+
+  return ambient + diffuse + specular;
+}
+
+vec3 calc_spot_light(SpotLight light, vec3 normal, vec3 frag_position, vec3 view_direction) {
+  vec3 light_direction     = normalize(light.position - frag_position);
+  vec3  reflect_direction  = reflect(-light_direction, normal);
   float theta              = dot(light_direction, normalize(-light.direction));
   float epsilon            = light.cutoff - light.outer_cutoff;
   float intensity          = clamp((theta - light.outer_cutoff) / epsilon, 0.0, 1.0);
-  vec3  view_direction     = normalize(camera_position - frag_position);
-  vec3  reflect_direction  = reflect(-light_direction, normal);
   float diff               = max(dot(normal, light_direction), 0.0);
   float spec               = pow(max(dot(reflect_direction, view_direction), 0.0), material.shininess);
   vec3  diff_tex           = vec3(texture(material.diffuse, uv_coords));
@@ -43,5 +107,5 @@ void main() {
   vec3  diffuse            = light.diffuse * (diff * diff_tex) * intensity;
   vec3  specular           = light.specular * (spec * vec3(texture(material.specular, uv_coords))) * intensity;
 
-  color = vec4(ambient + diffuse + specular, 1.0);
-};
+  return ambient + diffuse + specular;
+}
diff --git a/src/main.cc b/src/main.cc
index 598230e..edf01fb 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -205,8 +205,9 @@ int main() {
   glm::vec3 camera_position  = glm::vec3(-2.0f, 0.0f, 6.0f);
   glm::vec3 camera_forward   = glm::vec3(0.0f);
   glm::vec3 world_up         = glm::vec3(0.0f, 1.0f, 0.0f);
-  glm::vec3 light_pos_dir    = glm::vec3(1.0f,  0.5f, 0.0f);
+  glm::vec3 light_ambient    = glm::vec3(0.2f, 0.2f, 0.2f);
   glm::vec3 light_diffuse    = glm::vec3(0.75f, 0.75f, 0.75f);
+  glm::vec3 light_specular   = glm::vec3(1.0f, 1.0f, 1.0f);
 
   float yaw   = -70.0f;
   float pitch =   0.0f;
@@ -219,12 +220,7 @@ int main() {
   main_shader.set_mat4 ("projection", projection);
   main_shader.set_vec3 ("material.specular", glm::vec3(0.5f, 0.5f, 0.5f));
   main_shader.set_float("material.shininess", 32.0f);
-  main_shader.set_vec3 ("light.ambient", glm::vec3(0.2f, 0.2f, 0.2f));
-  main_shader.set_vec3 ("light.diffuse", light_diffuse);
-  main_shader.set_vec3 ("light.specular", glm::vec3(1.0f, 1.0f, 1.0f));
-  main_shader.set_float("light.constant", 1.0f);
-  main_shader.set_float("light.linear", 0.07f);
-  main_shader.set_float("light.quadratic", 0.017f);
+
   light_shader.set_mat4("projection", projection);
 
   std::vector<glm::vec3> cube_positions = {
@@ -240,7 +236,60 @@ int main() {
       glm::vec3(-3.3f,  2.0f, -1.5f)
   };
 
-  const float light_radius  = 6.0f;
+  std::vector<glm::vec3> point_light_positions = {
+    glm::vec3( 0.7f,  0.2f,  2.0f),
+    glm::vec3( 2.3f, -3.3f, -4.0f),
+    glm::vec3(-4.0f,  2.0f, -12.0f),
+    glm::vec3( 0.0f,  0.0f, -3.0f)
+  };
+
+  // Setup lights
+  main_shader.set_vec3("directional_light.direction", glm::vec3(-0.2f, -1.0f, -0.3f));
+  main_shader.set_vec3("directional_light.ambient", light_ambient);
+  main_shader.set_vec3("directional_light.diffuse", light_diffuse);
+  main_shader.set_vec3("directional_light.specular", light_specular);
+
+  main_shader.set_vec3("spot_light.ambient", light_ambient);
+  main_shader.set_vec3("spot_light.diffuse", light_diffuse);
+  main_shader.set_vec3("spot_light.specular", light_specular);
+
+  for (int i = 0; i < point_light_positions.size(); ++i) {
+    char base[256]      = {0};
+    char position[512]  = {0};
+    char ambient[512]   = {0};
+    char diffuse[512]   = {0};
+    char specular[512]  = {0};
+    char constant[512]  = {0};
+    char linear[512]    = {0};
+    char quadratic[512] = {0};
+
+    snprintf(base, sizeof(base) - 1, "point_lights[%d]", i);
+    snprintf(position, sizeof(position) - 1, "%s.position", base);
+    snprintf(ambient, sizeof(ambient) - 1, "%s.ambient", base);
+    snprintf(diffuse, sizeof(diffuse) - 1, "%s.diffuse", base);
+    snprintf(specular, sizeof(specular) - 1, "%s.specular", base);
+    snprintf(constant, sizeof(constant) - 1, "%s.constant", base);
+    snprintf(linear, sizeof(linear) - 1, "%s.linear", base);
+    snprintf(quadratic, sizeof(quadratic) - 1, "%s.quadratic", base);
+
+    main_shader.set_vec3(position, point_light_positions[i]);
+    main_shader.set_vec3(ambient, light_ambient);
+    main_shader.set_vec3(diffuse, light_diffuse);
+    main_shader.set_vec3(specular, light_specular);
+    main_shader.set_float(constant, 1.0f);
+    main_shader.set_float(linear, 0.09f);
+    main_shader.set_float(quadratic, 0.032f);
+
+    memset(base,      0, sizeof(base));
+    memset(position,  0, sizeof(position));
+    memset(ambient,   0, sizeof(ambient));
+    memset(diffuse,   0, sizeof(diffuse));
+    memset(specular,  0, sizeof(specular));
+    memset(constant,  0, sizeof(constant));
+    memset(linear,    0, sizeof(linear));
+    memset(quadratic, 0, sizeof(quadratic));
+  }
+
   const float sensitivity   = 0.1f;
   int last_mouse_x          = WINDOW_HALF_WIDTH;
   int last_mouse_y          = WINDOW_HALF_HEIGHT;
@@ -251,9 +300,7 @@ int main() {
 
   while (running) {
     uint32_t ticks = SDL_GetTicks();
-    float seconds  = ticks * 0.001f;
     delta          = (ticks - last_frame) * 0.001f;
-    light_pos_dir  = glm::vec3(sin(seconds) * light_radius, light_pos_dir.y, cos(seconds) * light_radius);
     last_frame     = ticks;
 
     while (SDL_PollEvent(&event)) {
@@ -318,10 +365,10 @@ int main() {
 
     view = glm::lookAt(camera_position, camera_position + camera_forward, world_up);
     main_shader.set_vec3("camera_position", camera_position);
-    main_shader.set_vec3("light.position", camera_position);
-    main_shader.set_vec3("light.direction", camera_forward);
-    main_shader.set_float("light.cutoff", glm::cos(glm::radians(12.5)));
-    main_shader.set_float("light.outer_cutoff", glm::cos(glm::radians(17.5)));
+    main_shader.set_vec3("spot_light.position", camera_position);
+    main_shader.set_vec3("spot_light.direction", camera_forward);
+    main_shader.set_float("spot_light.cutoff", glm::cos(glm::radians(12.5)));
+    main_shader.set_float("spot_light.outer_cutoff", glm::cos(glm::radians(17.5)));
     main_shader.set_mat4("view", view);
     light_shader.set_mat4("view", view);
 
@@ -333,8 +380,6 @@ int main() {
       model = glm::rotate(model, glm::radians(10.0f * i), glm::vec3(1.0f, 0.3f, 0.5f));
       normal_mat = glm::transpose(glm::inverse(model));
       main_shader.activate();
-      main_shader.set_vec4("light.pos_dir", glm::vec4(light_pos_dir, 1.0f));
-      main_shader.set_vec3("light.diffuse", light_diffuse);
       main_shader.set_mat4("model", model);
       main_shader.set_mat3("normal_mat", normal_mat);
       diffuse_map.activate();
@@ -344,13 +389,15 @@ int main() {
     }
 
     // Draw light source
-    // model = glm::translate(glm::mat4(1.0f), glm::vec3(light_pos_dir));
-    // model = glm::scale(model, glm::vec3(0.2f));
-    // light_shader.activate();
-    // light_shader.set_mat4("model", model);
-    // light_shader.set_vec3("light_diffuse", light_diffuse);
-    // glBindVertexArray(light_vao);
-    // glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, (void *)0);
+    for (int i = 0; i < point_light_positions.size(); ++i) {
+      model = glm::translate(glm::mat4(1.0f), point_light_positions[i]);
+      model = glm::scale(model, glm::vec3(0.2f));
+      light_shader.activate();
+      light_shader.set_mat4("model", model);
+      light_shader.set_vec3("light_diffuse", light_diffuse);
+      glBindVertexArray(light_vao);
+      glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, (void *)0);
+    }
 
     SDL_GL_SwapWindow(window);
   }