From 36886e343781bf0ddf7458d5c6db5b5724c918e4 Mon Sep 17 00:00:00 2001
From: HampusM <hampus@hampusmat.com>
Date: Thu, 23 May 2024 22:47:30 +0200
Subject: feat(engine): add directional light

---
 engine/fragment.glsl          |  51 ++++++++++++--
 engine/light.glsl             |  36 ++++++----
 engine/src/lighting.rs        |  21 ++++++
 engine/src/renderer/opengl.rs | 159 +++++++++++++++++++++++++++++++++---------
 4 files changed, 212 insertions(+), 55 deletions(-)

(limited to 'engine')

diff --git a/engine/fragment.glsl b/engine/fragment.glsl
index cc46f36..5bf5ff2 100644
--- a/engine/fragment.glsl
+++ b/engine/fragment.glsl
@@ -3,7 +3,7 @@
 #preinclude "light.glsl"
 #preinclude "vertex_data.glsl"
 
-#define MAX_POINT_LIGHT_CNT 64
+#define MAX_LIGHT_CNT 64
 
 out vec4 FragColor;
 
@@ -12,23 +12,62 @@ in VertexData vertex_data;
 uniform vec3 view_pos;
 uniform sampler2D input_texture;
 uniform Material material;
-uniform PointLight point_lights[MAX_POINT_LIGHT_CNT];
+
+uniform PointLight point_lights[MAX_LIGHT_CNT];
 uniform int point_light_cnt;
 
+uniform DirectionalLight directional_lights[MAX_LIGHT_CNT];
+uniform int directional_light_cnt;
+
 void main()
 {
 	vec3 ambient_light = calc_ambient_light(material, vertex_data.texture_coords);
 
+	vec3 directional_light_sum = vec3(0.0, 0.0, 0.0);
+
+	for (int dl_index = 0; dl_index < directional_light_cnt; dl_index++) {
+		CalculatedLight calculated_dir_light;
+
+		calc_light(
+			// Negated since we want the light to point from the light direction
+			normalize(-directional_lights[dl_index].direction),
+			directional_lights[dl_index].phong,
+			vertex_data,
+			view_pos,
+			material,
+			calculated_dir_light
+		);
+
+		directional_light_sum +=
+			calculated_dir_light.diffuse + calculated_dir_light.specular;
+	}
+
 	vec3 point_light_sum = vec3(0.0, 0.0, 0.0);
 
 	for (int pl_index = 0; pl_index < point_light_cnt; pl_index++) {
-		point_light_sum += calc_point_light(
-			point_lights[pl_index],
+		vec3 light_direction =
+			normalize(point_lights[pl_index].position - vertex_data.world_space_pos);
+
+		CalculatedLight calculated_point_light;
+
+		calc_light(
+			light_direction,
+			point_lights[pl_index].phong,
 			vertex_data,
 			view_pos,
-			material
+			material,
+			calculated_point_light
 		);
+
+		float attenuation =
+			calc_attenuation(point_lights[pl_index], vertex_data.world_space_pos);
+
+		calculated_point_light.diffuse *= attenuation;
+		calculated_point_light.specular *= attenuation;
+
+		point_light_sum +=
+			calculated_point_light.diffuse + calculated_point_light.specular;
 	}
 
-	FragColor = vec4((ambient_light + point_light_sum), 1.0);
+	FragColor = vec4((ambient_light + directional_light_sum + point_light_sum), 1.0);
 }
diff --git a/engine/light.glsl b/engine/light.glsl
index 92d3ec3..1bc23a4 100644
--- a/engine/light.glsl
+++ b/engine/light.glsl
@@ -36,6 +36,18 @@ struct PointLight
 	AttenuationProperties attenuation_props;
 };
 
+struct DirectionalLight
+{
+	LightPhong phong;
+	vec3 direction;
+};
+
+struct CalculatedLight
+{
+	vec3 diffuse;
+	vec3 specular;
+};
+
 vec3 calc_ambient_light(in Material material, in vec2 texture_coords)
 {
 	return vec3(texture(material.ambient_map, texture_coords)) * material.ambient;
@@ -88,40 +100,34 @@ float calc_attenuation(in PointLight point_light, in vec3 position)
 		* pow(light_distance, 2));
 }
 
-vec3 calc_point_light(
-	in PointLight light,
+void calc_light(
+	in vec3 light_direction,
+	in LightPhong light_phong,
 	in VertexData vertex_data,
 	in vec3 view_pos,
-	in Material material
+	in Material material,
+	out CalculatedLight calculated_light
 )
 {
-	vec3 light_direction = normalize(light.position - vertex_data.world_space_pos);
 	vec3 norm = normalize(vertex_data.world_space_normal);
 
-	vec3 diffuse_light = calc_diffuse_light(
+	calculated_light.diffuse = calc_diffuse_light(
 		material,
-		light.phong,
+		light_phong,
 		light_direction,
 		norm,
 		vertex_data.texture_coords
 	);
 
-	vec3 specular_light = calc_specular_light(
+	calculated_light.specular = calc_specular_light(
 		material,
-		light.phong,
+		light_phong,
 		light_direction,
 		norm,
 		view_pos,
 		vertex_data.world_space_pos,
 		vertex_data.texture_coords
 	);
-
-	float attenuation = calc_attenuation(light, vertex_data.world_space_pos);
-
-	diffuse_light *= attenuation;
-	specular_light *= attenuation;
-
-	return diffuse_light + specular_light;
 }
 
 #endif
diff --git a/engine/src/lighting.rs b/engine/src/lighting.rs
index ffa2645..398161f 100644
--- a/engine/src/lighting.rs
+++ b/engine/src/lighting.rs
@@ -1,6 +1,7 @@
 use ecs::{Component, Sole};
 
 use crate::color::Color;
+use crate::data_types::vector::Vec3;
 use crate::util::builder;
 
 builder! {
@@ -64,6 +65,26 @@ impl Default for AttenuationParams
     }
 }
 
+builder! {
+#[builder(name = DirectionalLightBuilder, derives = (Debug, Default, Clone))]
+#[derive(Debug, Default, Clone, Component)]
+#[non_exhaustive]
+pub struct DirectionalLight
+{
+    pub diffuse: Color<f32>,
+    pub specular: Color<f32>,
+    pub direction: Vec3<f32>,
+}
+}
+
+impl DirectionalLight
+{
+    pub fn builder() -> DirectionalLightBuilder
+    {
+        DirectionalLightBuilder::default()
+    }
+}
+
 builder! {
 /// Global light properties.
 #[builder(name = GlobalLightBuilder, derives = (Debug, Clone, Default))]
diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs
index 89f920c..3fe3388 100644
--- a/engine/src/renderer/opengl.rs
+++ b/engine/src/renderer/opengl.rs
@@ -11,9 +11,10 @@ use ecs::system::{Into as _, System};
 use ecs::{Component, Query};
 
 use crate::camera::Camera;
+use crate::color::Color;
 use crate::data_types::dimens::Dimens;
 use crate::event::{Present as PresentEvent, Start as StartEvent};
-use crate::lighting::{GlobalLight, PointLight};
+use crate::lighting::{DirectionalLight, GlobalLight, PointLight};
 use crate::material::{Flags as MaterialFlags, Material};
 use crate::matrix::Matrix;
 use crate::mesh::Mesh;
@@ -100,6 +101,7 @@ fn render(
         Transform,
     )>,
     point_light_query: Query<(PointLight, Transform)>,
+    directional_lights: Query<(DirectionalLight,)>,
     camera_query: Query<(Camera,)>,
     window: Single<Window>,
     global_light: Single<GlobalLight>,
@@ -125,6 +127,8 @@ fn render(
         .map(|(point_light, transform)| ((*point_light).clone(), (*transform).clone()))
         .collect::<Vec<_>>();
 
+    let directional_lights = directional_lights.iter().collect::<Vec<_>>();
+
     let GlObjects {
         shader_programs: gl_shader_programs,
         textures: gl_textures,
@@ -154,6 +158,11 @@ fn render(
             &global_light,
             shader_program,
             point_lights.as_slice(),
+            directional_lights
+                .iter()
+                .map(|(dir_light,)| &**dir_light)
+                .collect::<Vec<_>>()
+                .as_slice(),
             &camera,
         );
 
@@ -358,6 +367,7 @@ fn apply_light(
     global_light: &GlobalLight,
     gl_shader_program: &mut GlShaderProgram,
     point_lights: &[(PointLight, Transform)],
+    directional_lights: &[&DirectionalLight],
     camera: &Camera,
 )
 {
@@ -366,15 +376,55 @@ fn apply_light(
         "Shader cannot handle more than 64 point lights"
     );
 
+    debug_assert!(
+        directional_lights.len() < 64,
+        "Shader cannot handle more than 64 directional lights"
+    );
+
+    for (dir_light_index, dir_light) in directional_lights.iter().enumerate() {
+        gl_shader_program.set_uniform_vec_3fv(
+            &create_light_uniform_name(
+                "directional_lights",
+                dir_light_index,
+                "direction",
+            ),
+            &dir_light.direction,
+        );
+
+        set_light_phong_uniforms(
+            gl_shader_program,
+            "directional_lights",
+            dir_light_index,
+            *dir_light,
+        );
+    }
+
+    gl_shader_program.set_uniform_1i(
+        cstr!("directional_light_cnt"),
+        directional_lights.len() as i32,
+    );
+
     for (point_light_index, (point_light, point_light_transform)) in
         point_lights.iter().enumerate()
     {
-        set_point_light_uniforms(
+        gl_shader_program.set_uniform_vec_3fv(
+            &create_light_uniform_name("point_lights", point_light_index, "position"),
+            &point_light_transform.position,
+        );
+
+        set_light_phong_uniforms(
             gl_shader_program,
+            "point_lights",
             point_light_index,
             point_light,
-            point_light_transform,
-        )
+        );
+
+        set_light_attenuation_uniforms(
+            gl_shader_program,
+            "point_lights",
+            point_light_index,
+            point_light,
+        );
     }
 
     gl_shader_program.set_uniform_1i(cstr!("point_light_cnt"), point_lights.len() as i32);
@@ -421,55 +471,96 @@ fn apply_light(
     gl_shader_program.set_uniform_vec_3fv(cstr!("view_pos"), &camera.position);
 }
 
-fn set_point_light_uniforms(
+fn set_light_attenuation_uniforms(
     gl_shader_program: &mut GlShaderProgram,
-    point_light_index: usize,
-    point_light: &PointLight,
-    point_light_transform: &Transform,
+    light_array: &str,
+    light_index: usize,
+    light: &PointLight,
 )
 {
-    gl_shader_program.set_uniform_vec_3fv(
-        &create_point_light_uniform_name(point_light_index, "position"),
-        &point_light_transform.position,
-    );
-
-    gl_shader_program.set_uniform_vec_3fv(
-        &create_point_light_uniform_name(point_light_index, "phong.diffuse"),
-        &point_light.diffuse.clone().into(),
-    );
-
-    gl_shader_program.set_uniform_vec_3fv(
-        &create_point_light_uniform_name(point_light_index, "phong.specular"),
-        &point_light.specular.clone().into(),
-    );
-
     gl_shader_program.set_uniform_1fv(
-        &create_point_light_uniform_name(point_light_index, "attenuation_props.constant"),
-        point_light.attenuation_params.constant,
+        &create_light_uniform_name(
+            light_array,
+            light_index,
+            "attenuation_props.constant",
+        ),
+        light.attenuation_params.constant,
     );
 
     gl_shader_program.set_uniform_1fv(
-        &create_point_light_uniform_name(point_light_index, "attenuation_props.linear"),
-        point_light.attenuation_params.linear,
+        &create_light_uniform_name(light_array, light_index, "attenuation_props.linear"),
+        light.attenuation_params.linear,
     );
 
     gl_shader_program.set_uniform_1fv(
-        &create_point_light_uniform_name(
-            point_light_index,
+        &create_light_uniform_name(
+            light_array,
+            light_index,
             "attenuation_props.quadratic",
         ),
-        point_light.attenuation_params.quadratic,
+        light.attenuation_params.quadratic,
+    );
+}
+
+fn set_light_phong_uniforms(
+    gl_shader_program: &mut GlShaderProgram,
+    light_array: &str,
+    light_index: usize,
+    light: &impl Light,
+)
+{
+    gl_shader_program.set_uniform_vec_3fv(
+        &create_light_uniform_name(light_array, light_index, "phong.diffuse"),
+        &light.diffuse().clone().into(),
+    );
+
+    gl_shader_program.set_uniform_vec_3fv(
+        &create_light_uniform_name(light_array, light_index, "phong.specular"),
+        &light.specular().clone().into(),
     );
 }
 
-fn create_point_light_uniform_name(
-    point_light_index: usize,
-    point_light_field: &str,
+trait Light
+{
+    fn diffuse(&self) -> &Color<f32>;
+    fn specular(&self) -> &Color<f32>;
+}
+
+impl Light for PointLight
+{
+    fn diffuse(&self) -> &Color<f32>
+    {
+        &self.diffuse
+    }
+
+    fn specular(&self) -> &Color<f32>
+    {
+        &self.specular
+    }
+}
+
+impl Light for DirectionalLight
+{
+    fn diffuse(&self) -> &Color<f32>
+    {
+        &self.diffuse
+    }
+
+    fn specular(&self) -> &Color<f32>
+    {
+        &self.specular
+    }
+}
+
+fn create_light_uniform_name(
+    light_array: &str,
+    light_index: usize,
+    light_field: &str,
 ) -> CString
 {
     unsafe {
         CString::from_vec_with_nul_unchecked(
-            format!("point_lights[{point_light_index}].{point_light_field}\0").into(),
+            format!("{light_array}[{light_index}].{light_field}\0").into(),
         )
     }
 }
-- 
cgit v1.2.3-18-g5258