From aba3450f2e4756096138d0454f184509332a9976 Mon Sep 17 00:00:00 2001 From: NADAL Jean-Baptiste Date: Tue, 20 Feb 2024 09:14:01 +0100 Subject: [PATCH] [FEAT] ADD lighting method --- raytracing/src/color.cpp | 49 ++++++++-- raytracing/src/color.h | 6 ++ raytracing/src/material.cpp | 51 ++++++++++ raytracing/src/material.h | 4 + raytracing/src/tuple.cpp | 12 +-- raytracing/src/tuple.h | 12 +-- tests/06_light_shading.cpp | 183 ++++++++++++++++++++++++++++++++---- 7 files changed, 278 insertions(+), 39 deletions(-) diff --git a/raytracing/src/color.cpp b/raytracing/src/color.cpp index 37aff07..9a953c4 100644 --- a/raytracing/src/color.cpp +++ b/raytracing/src/color.cpp @@ -45,15 +45,13 @@ Color::Color(void) : m_red(0), m_green(0), m_blue(0) /* ------------------------------------------------------------------------- */ -Color::Color(const Color &a_copy) : - m_red(a_copy.m_red), m_green(a_copy.m_green), m_blue(a_copy.m_blue) +Color::Color(const Color &a_copy) : m_red(a_copy.m_red), m_green(a_copy.m_green), m_blue(a_copy.m_blue) { } /* ------------------------------------------------------------------------- */ -Color::Color(double a_red, double a_green, double a_blue) : - m_red(a_red), m_green(a_green), m_blue(a_blue) +Color::Color(double a_red, double a_green, double a_blue) : m_red(a_red), m_green(a_green), m_blue(a_blue) { } @@ -79,9 +77,9 @@ const Color &Color::operator=(const Color &a_color) return *this; } - m_red = a_color.m_red; + m_red = a_color.m_red; m_green = a_color.m_green; - m_blue = a_color.m_blue; + m_blue = a_color.m_blue; return *this; } @@ -114,9 +112,9 @@ const Color Color::operator*(const Color &a_color) const // Using the hadamard product. double the_red, the_green, the_blue; - the_red = m_red * a_color.m_red; + the_red = m_red * a_color.m_red; the_green = m_green * a_color.m_green; - the_blue = m_blue * a_color.m_blue; + the_blue = m_blue * a_color.m_blue; return Color(the_red, the_green, the_blue); } @@ -217,3 +215,38 @@ uint8_t Color::color_to_integer(double a_color) const return the_value; } + +/* ------------------------------------------------------------------------- */ + +Color Color::Black(void) +{ + return Color(0, 0, 0); +} + +/* ------------------------------------------------------------------------- */ + +Color Color::White(void) +{ + return Color(1, 1, 1); +} + +/* ------------------------------------------------------------------------- */ + +Color Color::Red(void) +{ + return Color(1, 0, 0); +} + +/* ------------------------------------------------------------------------- */ + +Color Color::Green(void) +{ + return Color(0, 1, 0); +} + +/* ------------------------------------------------------------------------- */ + +Color blue(void) +{ + return Color(0, 0, 1); +} diff --git a/raytracing/src/color.h b/raytracing/src/color.h index 4ec9f84..2479b29 100644 --- a/raytracing/src/color.h +++ b/raytracing/src/color.h @@ -63,6 +63,12 @@ namespace Raytracer uint8_t green_to_integer(void) const; uint8_t blue_to_integer(void) const; + static Color Black(void); + static Color White(void); + static Color Red(void); + static Color Green(void); + static Color Blue(void); + private: uint8_t color_to_integer(double a_color) const; diff --git a/raytracing/src/material.cpp b/raytracing/src/material.cpp index 3ea7b14..b26b216 100644 --- a/raytracing/src/material.cpp +++ b/raytracing/src/material.cpp @@ -28,6 +28,8 @@ /* ------------------------------------------------------------------------- */ +#include + #include "common.h" #include "material.h" @@ -111,3 +113,52 @@ void Material::set_shininess(double a_value) { m_shininess = a_value; } + +/* ------------------------------------------------------------------------- */ + +Color Material::lighting(const PointLight &a_light, const Tuple &a_point, const Tuple &an_eyev, const Tuple &a_normalv) +{ + Color the_effective_color; + Tuple the_light_v, the_reflect_v; + Color the_ambient, the_diffuse, the_specular; + + // Combine the surface color with the light's color + the_effective_color = m_color * a_light.intensity(); + + // Find the direction to the light source + the_light_v = (a_light.position() - a_point).normalize(); + + // Compute the ambient contribution + the_ambient = the_effective_color * m_ambient; + + // light_dot_normal represents the cosine of the angle between the light vector and the normal vector. + // a negative number means the light is on the other side of the surface. + double the_light_dot_normal = the_light_v.dot(a_normalv); + + if (the_light_dot_normal < 0) + { + the_diffuse = Color::Black(); + the_specular = Color::Black(); + } + else + { + the_diffuse = the_effective_color * m_diffuse * the_light_dot_normal; + + // reflect_dot_eye represent the consine of the angle between reflection vector and the eye vector. + // A negative number means the light reflects away from the eye. + the_reflect_v = (-the_light_v).reflect(a_normalv); + double the_reflect_dot_eye = the_reflect_v.dot(an_eyev); + if (the_reflect_dot_eye <= 0) + { + the_specular = Color::Black(); + } + else + { + // Compute the specular contribution + double the_factor = pow(the_reflect_dot_eye, m_shininess); + the_specular = a_light.intensity() * m_specular * the_factor; + } + } + + return the_ambient + the_diffuse + the_specular; +} diff --git a/raytracing/src/material.h b/raytracing/src/material.h index 4efd17d..ce70c71 100644 --- a/raytracing/src/material.h +++ b/raytracing/src/material.h @@ -29,6 +29,8 @@ /* ------------------------------------------------------------------------- */ #include "color.h" +#include "point-light.h" +#include "tuple.h" /* ------------------------------------------------------------------------- */ @@ -55,6 +57,8 @@ namespace Raytracer const double &shininess(void) const; void set_shininess(double a_value); + Color lighting(const PointLight &a_light, const Tuple &a_point, const Tuple &an_eyev, const Tuple &a_normalv); + private: Color m_color; double m_ambient; diff --git a/raytracing/src/tuple.cpp b/raytracing/src/tuple.cpp index 6a8b1c6..b893bfd 100644 --- a/raytracing/src/tuple.cpp +++ b/raytracing/src/tuple.cpp @@ -286,14 +286,14 @@ void Tuple::set_w(double a_w) /* ------------------------------------------------------------------------- */ -bool Tuple::is_point(void) +bool Tuple::is_point(void) const { return double_equal(m_w, kRaytracerTuplePoint); } /* ------------------------------------------------------------------------- */ -bool Tuple::is_vector(void) +bool Tuple::is_vector(void) const { return double_equal(m_w, kRaytracerTupleVector); } @@ -307,7 +307,7 @@ double Tuple::magnitude(void) const /* ------------------------------------------------------------------------- */ -Tuple Tuple::normalize(void) +Tuple Tuple::normalize(void) const { double the_magnitude = magnitude(); @@ -316,14 +316,14 @@ Tuple Tuple::normalize(void) /* ------------------------------------------------------------------------- */ -double Tuple::dot(const Tuple &a_tuple) +double Tuple::dot(const Tuple &a_tuple) const { return m_x * a_tuple.m_x + m_y * a_tuple.m_y + m_z * a_tuple.m_z + m_w * a_tuple.m_w; } /* ------------------------------------------------------------------------- */ -Tuple Tuple::cross(const Tuple &a_tuple) +Tuple Tuple::cross(const Tuple &a_tuple) const { return Vector(m_y * a_tuple.m_z - m_z * a_tuple.m_y, m_z * a_tuple.m_x - m_x * a_tuple.m_z, m_x * a_tuple.m_y - m_y * a_tuple.m_x); @@ -331,7 +331,7 @@ Tuple Tuple::cross(const Tuple &a_tuple) /* ------------------------------------------------------------------------- */ -Tuple Tuple::reflect(const Tuple &a_normal) +Tuple Tuple::reflect(const Tuple &a_normal) const { return *this - a_normal * 2 * dot(a_normal); } diff --git a/raytracing/src/tuple.h b/raytracing/src/tuple.h index 1146329..b9212b5 100644 --- a/raytracing/src/tuple.h +++ b/raytracing/src/tuple.h @@ -78,16 +78,16 @@ namespace Raytracer void set_z(double a_z); void set_w(double a_w); - bool is_point(void); - bool is_vector(void); + bool is_point(void) const; + bool is_vector(void) const; double magnitude(void) const; - Tuple normalize(void); + Tuple normalize(void) const; - double dot(const Tuple &a_tuple); - Tuple cross(const Tuple &a_tuple); + double dot(const Tuple &a_tuple) const; + Tuple cross(const Tuple &a_tuple) const; - Tuple reflect(const Tuple &a_normal); + Tuple reflect(const Tuple &a_normal) const; private: void set_at_index(uint8_t an_index, double a_value); diff --git a/tests/06_light_shading.cpp b/tests/06_light_shading.cpp index 400e387..88c2ab2 100644 --- a/tests/06_light_shading.cpp +++ b/tests/06_light_shading.cpp @@ -38,10 +38,10 @@ SCENARIO("The normal on a sphere at point a on the x axis", "[features/spheres.f GIVEN("s <- sphere()") { Sphere s; - WHEN("n <- normal_at(s, point(1,0,0))") + WHEN("n <- normal_at(s, point(1, 0, 0))") { Tuple n = s.normal_at(Tuple::Point(1, 0, 0)); - THEN("n = vector(1,0,0)") + THEN("n = vector(1, 0, 0)") { REQUIRE(n == Tuple::Vector(1, 0, 0)); } @@ -56,10 +56,10 @@ SCENARIO("The normal on a sphere at point a on the y axis", "[features/spheres.f GIVEN("s <- sphere()") { Sphere s; - WHEN("n <- normal_at(s, point(0,1,0))") + WHEN("n <- normal_at(s, point(0, 1, 0))") { Tuple n = s.normal_at(Tuple::Point(0, 1, 0)); - THEN("n = vector(0,1,0)") + THEN("n = vector(0, 1, 0)") { REQUIRE(n == Tuple::Vector(0, 1, 0)); } @@ -74,10 +74,10 @@ SCENARIO("The normal on a sphere at point a on the z axis", "[features/spheres.f GIVEN("s <- sphere()") { Sphere s; - WHEN("n <- normal_at(s, point(0,0,1))") + WHEN("n <- normal_at(s, point(0, 0, 1))") { Tuple n = s.normal_at(Tuple::Point(0, 0, 1)); - THEN("n = vector(0,0,1)") + THEN("n = vector(0, 0, 1)") { REQUIRE(n == Tuple::Vector(0, 0, 1)); } @@ -92,10 +92,10 @@ SCENARIO("The normal on a sphere at a nonaxial point", "[features/spheres.featur GIVEN("s <- sphere()") { Sphere s; - WHEN("n <- normal_at(s, point(sqrt(3)/3,sqrt(3)/3,sqrt(3)/3))") + WHEN("n <- normal_at(s, point(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3))") { Tuple n = s.normal_at(Tuple::Point(sqrt(3) / 3, sqrt(3) / 3, sqrt(3) / 3)); - THEN("n = vector(sqrt(3)/3,sqrt(3)/3,sqrt(3)/3))") + THEN("n = vector(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3))") { REQUIRE(n == Tuple::Vector(sqrt(3) / 3, sqrt(3) / 3, sqrt(3) / 3)); } @@ -110,7 +110,7 @@ SCENARIO("The normal is a normalized vector", "[features/spheres.feature]") GIVEN("s <- sphere()") { Sphere s; - WHEN("n <- normal_at(s, point(sqrt(3)/3,sqrt(3)/3,sqrt(3)/3))") + WHEN("n <- normal_at(s, point(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3))") { Tuple n = s.normal_at(Tuple::Point(sqrt(3) / 3, sqrt(3) / 3, sqrt(3) / 3)); THEN("n = normalize(n)") @@ -128,13 +128,13 @@ SCENARIO("Computing the normal on a translated sphere", "[features/spheres.featu GIVEN("s <- sphere()") { Sphere s; - AND_GIVEN("set_transform(s, translation(0,1,0))") + AND_GIVEN("set_transform(s, translation(0, 1, 0))") { s.set_transform(Matrix::translation(0, 1, 0)); - WHEN("n <- normal_at(s,point(0,1.70711,-0.70711))") + WHEN("n <- normal_at(s,point(0, 1.70711, -0.70711))") { Tuple n = s.normal_at(Tuple::Point(0, 1.70711, -0.70711)); - THEN("n = vector(0,0.70711, -0.70711)") + THEN("n = vector(0, 0.70711, -0.70711)") { REQUIRE(n == Tuple::Vector(0, 0.70711, -0.70711)); } @@ -150,13 +150,13 @@ SCENARIO("Computing the normal on a transformed sphere", "[features/spheres.feat GIVEN("s <- sphere()") { Sphere s; - AND_GIVEN("m <- scaling(1,0.5,1) * rotation_z(pi/5)") + AND_GIVEN("m <- scaling(1, 0. 5,1) * rotation_z(pi/5)") { Matrix m = Matrix::scaling(1, 0.5, 1) * Matrix::rotation_z(std::numbers::pi / 5); AND_GIVEN("set_transform(s, m)") { s.set_transform(m); - WHEN("n <- normal_at(s,point(0,sqrt(2)/2,sqrt(2)/2))") + WHEN("n <- normal_at(s,point(0, sqrt(2)/2, sqrt(2)/2))") { Tuple n = s.normal_at(Tuple::Point(0, sqrt(2) / 2, -sqrt(2) / 2)); THEN("n = vector(0,97014, -0.24254)") @@ -179,10 +179,10 @@ SCENARIO("Reflecting a vector approaching at 45°", "[features/tuples.feature]") AND_GIVEN("n <-vector(0, 1, 0)") { Tuple n = Tuple::Vector(0, 1, 0); - WHEN("r <- reflect(v,n)") + WHEN("r <- reflect(v, n)") { Tuple r = v.reflect(n); - THEN("r = vector(1,1,0)") + THEN("r = vector(1, 1, 0)") { REQUIRE(r == Tuple::Vector(1, 1, 0)); } @@ -201,10 +201,10 @@ SCENARIO("Reflecting a vector off a slanted surface", "[features/tuples.feature] AND_GIVEN("n <-vector(sqrt(2)/2, sqrt(2)/2, 0)") { Tuple n = Tuple::Vector(sqrt(2) / 2, sqrt(2) / 2, 0); - WHEN("r <- reflect(v,n)") + WHEN("r <- reflect(v, n)") { Tuple r = v.reflect(n); - THEN("r = vector(1,0,0)") + THEN("r = vector(1, 0, 0)") { REQUIRE(r == Tuple::Vector(1, 0, 0)); } @@ -246,7 +246,7 @@ SCENARIO("The default material", "[features/materials.feature]") GIVEN("m <- material()") { Material m; - THEN("m.color = color(1,1,1)") + THEN("m.color = color(1, 1, 1)") { REQUIRE(m.color() == Color(1, 1, 1)); } @@ -312,3 +312,148 @@ SCENARIO("A sphere may be assigned a material", "[features/spheres.feature]") } } } + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Lighting with the eye between the light and the surface", "[features/materials.feature]") +{ + Tuple position = Tuple::Point(0, 0, 0); + Material m; + + GIVEN("eyev <- vector(0, 0, -1)") + { + Tuple eyev = Tuple::Vector(0, 0, -1); + AND_GIVEN("normalv <- vector(0, 0, -1)") + { + Tuple normalv = Tuple::Vector(0, 0, -1); + AND_GIVEN("light <- point_light(point(0, 0, -10),color(1, 1, 1))") + { + PointLight light = PointLight(Tuple::Point(0, 0, -10), Color(1, 1, 1)); + WHEN("result <- lighting(m, light, position, eyev, normalv)") + { + Color result = m.lighting(light, position, eyev, normalv); + THEN("result = color(1.9, 1.9, 1.9)") + { + REQUIRE(result == Color(1.9, 1.9, 1.9)); + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Lighting with eye between the light & surface, eye offset 45°", "[features/materials.feature]") +{ + Tuple position = Tuple::Point(0, 0, 0); + Material m; + + GIVEN("eyev <- vector(0, sqrt(2)/2, -sqrt(2)/2)") + { + Tuple eyev = Tuple::Vector(0, sqrt(2) / 2, -sqrt(2) / 2); + AND_GIVEN("normalv <- vector(0, 0, -1)") + { + Tuple normalv = Tuple::Vector(0, 0, -1); + AND_GIVEN("light <- point_light(point(0, 0, -10),color(1, 1, 1))") + { + PointLight light = PointLight(Tuple::Point(0, 0, -10), Color(1, 1, 1)); + WHEN("result <- lighting(m, light, position, eyev, normalv)") + { + Color result = m.lighting(light, position, eyev, normalv); + THEN("result = color(1.0, 1.0, 1.0)") + { + REQUIRE(result == Color(1.0, 1.0, 1.0)); + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Lighting with the eye opposite surface, light offset 45°", "[features/materials.feature]") +{ + Tuple position = Tuple::Point(0, 0, 0); + Material m; + + GIVEN("eyev <- vector(0, 0, -1") + { + Tuple eyev = Tuple::Vector(0, 0, -1); + AND_GIVEN("normalv <- vector(0, 0, -1)") + { + Tuple normalv = Tuple::Vector(0, 0, -1); + AND_GIVEN("light <- point_light(point(0, 10, -10),color(1, 1, 1))") + { + PointLight light = PointLight(Tuple::Point(0, 10, -10), Color(1, 1, 1)); + WHEN("result <- lighting(m, light, position, eyev, normalv)") + { + Color result = m.lighting(light, position, eyev, normalv); + THEN("result = color(0.7364, 0.7364, 0.7364)") + { + REQUIRE(result == Color(0.7364, 0.7364, 0.7364)); + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Lighting with the eye in the path of the reflection vector", "[features/materials.feature]") +{ + Tuple position = Tuple::Point(0, 0, 0); + Material m; + + GIVEN("eyev <- vector(0, -sqrt(2)/2, -sqrt(2)/2") + { + Tuple eyev = Tuple::Vector(0, -sqrt(2) / 2, -sqrt(2) / 2); + AND_GIVEN("normalv <- vector(0, 0, -1)") + { + Tuple normalv = Tuple::Vector(0, 0, -1); + AND_GIVEN("light <- point_light(point(0, 10, -10),color(1, 1, 1))") + { + PointLight light = PointLight(Tuple::Point(0, 10, -10), Color(1, 1, 1)); + WHEN("result <- lighting(m, light, position, eyev, normalv)") + { + Color result = m.lighting(light, position, eyev, normalv); + THEN("result = color(1.6364, 1.6364, 1.6364)") + { + REQUIRE(result == Color(1.6364, 1.6364, 1.6364)); + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Lighting with the light behind the surface", "[features/materials.feature]") +{ + Tuple position = Tuple::Point(0, 0, 0); + Material m; + + GIVEN("eyev <- vector(0, 0, -1") + { + Tuple eyev = Tuple::Vector(0, 0, -1); + AND_GIVEN("normalv <- vector(0, 0, -1)") + { + Tuple normalv = Tuple::Vector(0, 0, -1); + AND_GIVEN("light <- point_light(point(0, 0, 10),color(1, 1, 1))") + { + PointLight light = PointLight(Tuple::Point(0, 0, 10), Color(1, 1, 1)); + WHEN("result <- lighting(m, light, position, eyev, normalv)") + { + Color result = m.lighting(light, position, eyev, normalv); + THEN("result = color(0.1, 0.1, 0.1)") + { + REQUIRE(result == Color(0.1, 0.1, 0.1)); + } + } + } + } + } +}