From 6acb8649e0b92ce4bd07ceb16c2087a1cd302e98 Mon Sep 17 00:00:00 2001 From: NADAL Jean-Baptiste Date: Tue, 5 Mar 2024 23:12:10 +0100 Subject: [PATCH] [wIP] Reflection is working --- apps/CMakeLists.txt | 3 + apps/chapter_11.cpp | 118 +++++++++++++++++++++++++ data/chapter_11.png | Bin 0 -> 46002 bytes raytracing/src/core/color.cpp | 7 ++ raytracing/src/core/common.cpp | 2 +- raytracing/src/core/common.h | 2 +- raytracing/src/renderer/world.cpp | 21 +++-- raytracing/src/renderer/world.h | 8 +- tests/11_reflection_refraction.cpp | 134 ++++++++++++++++++++++++++++- 9 files changed, 281 insertions(+), 14 deletions(-) create mode 100644 apps/chapter_11.cpp create mode 100644 data/chapter_11.png diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index dfb3b2d..41268b3 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -19,3 +19,6 @@ target_link_libraries(chapter_09 PRIVATE raytracing gcov OpenMP::OpenMP_CXX) add_executable(chapter_10 chapter_10.cpp) target_link_libraries(chapter_10 PRIVATE raytracing gcov) + +add_executable(chapter_11 chapter_11.cpp) +target_link_libraries(chapter_11 PRIVATE raytracing gcov) diff --git a/apps/chapter_11.cpp b/apps/chapter_11.cpp new file mode 100644 index 0000000..193dab0 --- /dev/null +++ b/apps/chapter_11.cpp @@ -0,0 +1,118 @@ +/*! + * chapter_11.cpp + * + * Copyright (c) 2024, NADAL Jean-Baptiste. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * @Author: NADAL Jean-Baptiste + * @Date: 05/03/2024 + * + */ + +// This is an independent project of an individual developer. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com + +#include +#include + +#include + +/* ------------------------------------------------------------------------- */ + +using namespace Raytracer; +using namespace std; + +/* ------------------------------------------------------------------------- */ + +int main(void) +{ + World the_world; + Camera the_camera; + Canvas the_canvas; + Plane *the_floor, *the_left_wall, *the_right_wall; + Sphere *the_middle, *the_right, *the_left; + chrono::time_point the_start, the_end; + + printf("Chapter 11 example.\n"); + + // Floor is an extremely flattened sphere with a matte texture. + the_floor = new Plane(); + Material &the_floor_material = the_floor->material(); + the_floor_material.set_color(Color(1, 0.9, 0.9)); + the_floor_material.set_specular(0); + the_floor_material.set_reflective(0.5); + the_floor_material.set_pattern(new CheckersPattern(Color(0.52, 0.52, 0.52), Color::Black())); + the_world.add_object(the_floor); + + // Left Wall + the_left_wall = new Plane(); + the_left_wall->set_transform(Matrix::translation(0, 0, 10) * + Matrix::rotation_y(-std::numbers::pi / 4) * + Matrix::rotation_x(std::numbers::pi / 2)); + Material &the_left_wall_material = the_left_wall->material(); + the_left_wall_material.set_specular(0); + the_left_wall_material.set_pattern(new StripePattern(Color(0.52, 0.52, 0.52), Color::Black())); + the_left_wall_material.pattern()->set_transform(Matrix::rotation_y(std::numbers::pi / 2)); + the_world.add_object(the_left_wall); + + // Right Wall + the_right_wall = new Plane(); + the_right_wall->set_transform(Matrix::translation(0, 5, 10) * + Matrix::rotation_y(std::numbers::pi / 4) * + Matrix::rotation_x(std::numbers::pi / 2)); + Material &the_right_wall_material = the_right_wall->material(); + the_right_wall_material.set_specular(0); + the_right_wall_material.set_pattern(new StripePattern(Color(0.52, 0.52, 0.52), Color::Black())); + the_right_wall_material.pattern()->set_transform(Matrix::rotation_y(std::numbers::pi / 2)); + the_world.add_object(the_right_wall); + + // The large sphere in the middle is a unit sphere, translated upward slightly and colored green. + the_middle = new Sphere(); + the_middle->set_transform(Matrix::translation(-0.25, 1, 1.5) * + Matrix::rotation_y(-std::numbers::pi / 1.5) * + Matrix::rotation_z(-std::numbers::pi / 6)); + + Material &the_middle_material = the_middle->material(); + the_middle_material.set_color(Color(0.75, 0.24, 0.14)); + the_middle_material.set_diffuse(0.7); + the_middle_material.set_specular(0.3); + the_world.add_object(the_middle); + + // The Light source is white, shining from above and to the left + the_world.set_light(PointLight(Tuple::Point(-10, 10, -10), Color(1, 1, 1))); + + // Configure the camera. + // the_camera = Camera(100, 50, std::numbers::pi / 3); + // the_camera = Camera(320, 200, std::numbers::pi / 3); + the_camera = Camera(640, 480, std::numbers::pi / 3); + the_camera.set_transform( + Matrix::view_transform(Tuple::Point(0, 1.5, -5), Tuple::Point(0, 1, 0), Tuple::Vector(0, 1, 0))); + + the_start = chrono::high_resolution_clock::now(); + the_canvas = the_camera.render(the_world); + the_end = chrono::high_resolution_clock::now(); + + the_canvas.save_to_file("chapter11.ppm"); + + chrono::duration the_elapsed_time = the_end - the_start; + printf("Execution Time: %f secondes\n", the_elapsed_time.count()); + + return 0; +} + +// Chapter 11 example. +// Execution Time: 904.052568 secondes \ No newline at end of file diff --git a/data/chapter_11.png b/data/chapter_11.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b9b3d3bf5877eacf864ac3ce768e3e93c48ff6 GIT binary patch literal 46002 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 literal 0 HcmV?d00001 diff --git a/raytracing/src/core/color.cpp b/raytracing/src/core/color.cpp index 0c79330..a9d6b5a 100644 --- a/raytracing/src/core/color.cpp +++ b/raytracing/src/core/color.cpp @@ -62,6 +62,13 @@ Color::Color(double a_red, double a_green, double a_blue) : bool Color::operator==(const Color &a_color) const { +#if 0 + bool the_eq_red = double_equal(m_red, a_color.m_red); + bool the_eq_green = double_equal(m_green, a_color.m_green); + bool the_eq_blue = double_equal(m_blue, a_color.m_blue); + +#endif + if (double_equal(m_red, a_color.m_red) && double_equal(m_green, a_color.m_green) && double_equal(m_blue, a_color.m_blue)) { diff --git a/raytracing/src/core/common.cpp b/raytracing/src/core/common.cpp index 30f3b1e..f3dc629 100644 --- a/raytracing/src/core/common.cpp +++ b/raytracing/src/core/common.cpp @@ -38,7 +38,7 @@ bool Raytracer::double_equal(double a, double b) { #if 0 double the_fabs = std::fabs(a - b); - bool the_test = the_fabs < kEpsilon; + bool the_test = the_fabs < kEpsilon; #endif return std::fabs(a - b) < kEpsilon; } diff --git a/raytracing/src/core/common.h b/raytracing/src/core/common.h index 08b0b6d..2c377aa 100644 --- a/raytracing/src/core/common.h +++ b/raytracing/src/core/common.h @@ -26,7 +26,7 @@ #ifndef _RAYTRACER_COMMON_H #define _RAYTRACER_COMMON_H -#define kEpsilon 0.00001 +#define kEpsilon 0.0001 namespace Raytracer { diff --git a/raytracing/src/renderer/world.cpp b/raytracing/src/renderer/world.cpp index 0a7ee5c..5f85198 100644 --- a/raytracing/src/renderer/world.cpp +++ b/raytracing/src/renderer/world.cpp @@ -158,31 +158,38 @@ Intersections World::intersect_world(const Ray &a_ray) const /* ------------------------------------------------------------------------- */ -Color World::shade_hit(const IntersectionData &an_intersection_data) const +Color World::shade_hit(const IntersectionData &an_intersection_data, uint32_t a_remainging) const { bool the_shadowed = is_shadowed(an_intersection_data.over_point()); Shape *the_object = an_intersection_data.object(); - return the_object->material().lighting(the_object, m_light, an_intersection_data.over_point(), - an_intersection_data.eyev(), an_intersection_data.normalv(), the_shadowed); + Color the_surface = the_object->material().lighting(the_object, m_light, an_intersection_data.over_point(), + an_intersection_data.eyev(), an_intersection_data.normalv(), the_shadowed); + Color the_reflected = reflected_color(an_intersection_data, a_remainging); + return the_surface + the_reflected; } /* ------------------------------------------------------------------------- */ -Color World::reflected_color(const IntersectionData &a_data) const +Color World::reflected_color(const IntersectionData &a_data, uint32_t a_remainging) const { + if (a_remainging <= 0) + { + return Color(0, 0, 0); + } + if (a_data.object()->material().reflective() == 0.0) { return Color(0, 0, 0); } Ray the_reflected_ray(a_data.over_point(), a_data.reflectv()); - Color the_color = color_at(the_reflected_ray); + Color the_color = color_at(the_reflected_ray, a_remainging - 1); return the_color * a_data.object()->material().reflective(); } /* ------------------------------------------------------------------------- */ -Color World::color_at(const Ray &a_ray) const +Color World::color_at(const Ray &a_ray, uint32_t a_remainging) const { Color the_color = Color::Black(); @@ -200,7 +207,7 @@ Color World::color_at(const Ray &a_ray) const IntersectionData the_comps = the_intersec.prepare_computations(a_ray); - the_color = shade_hit(the_comps); + the_color = shade_hit(the_comps, a_remainging); return the_color; } diff --git a/raytracing/src/renderer/world.h b/raytracing/src/renderer/world.h index acb5ec0..3f799ce 100644 --- a/raytracing/src/renderer/world.h +++ b/raytracing/src/renderer/world.h @@ -39,6 +39,8 @@ /* ------------------------------------------------------------------------- */ +#define kRemainingDefaultDepth 4 + namespace Raytracer { class Shape; @@ -63,9 +65,9 @@ namespace Raytracer bool contains(const Shape &a_shape); Intersections intersect_world(const Ray &a_ray) const; - Color shade_hit(const IntersectionData &an_intersection_data) const; - Color reflected_color(const IntersectionData &an_intersection_data) const; - Color color_at(const Ray &a_ray) const; + Color shade_hit(const IntersectionData &an_intersection_data, uint32_t a_remainging = kRemainingDefaultDepth) const; + Color reflected_color(const IntersectionData &an_intersection_data, uint32_t a_remainging = kRemainingDefaultDepth) const; + Color color_at(const Ray &a_ray, uint32_t a_remainging = kRemainingDefaultDepth) const; bool is_shadowed(const Tuple &a_point) const; diff --git a/tests/11_reflection_refraction.cpp b/tests/11_reflection_refraction.cpp index b81fb47..859c1c0 100644 --- a/tests/11_reflection_refraction.cpp +++ b/tests/11_reflection_refraction.cpp @@ -129,7 +129,7 @@ SCENARIO("The reflected color for a reflective material", "[features/world.featu AND_GIVEN("ray(point(0, 0, -3), vector(0, -sqrt(2) / 2, sqrt(2) / 2)") { Ray r(Tuple::Point(0, 0, -3), Tuple::Vector(0, -sqrt(2) / 2, sqrt(2) / 2)); - AND_GIVEN("i <- intersection(1, shape)") + AND_GIVEN("i <- intersection(sqrt(2), shape)") { Intersection i(sqrt(2), &shape); WHEN("comps <- prepare_computation(i, r)") @@ -138,7 +138,7 @@ SCENARIO("The reflected color for a reflective material", "[features/world.featu AND_WHEN("color <- reflected_color(w, comps)") { Color color = w.reflected_color(comps); - THEN("color = color(0, 0, 0)") + THEN("color = color(0.19032, 0.2379, 0.14274)") { REQUIRE(color == Color(0.19032, 0.2379, 0.14274)); } @@ -150,3 +150,133 @@ SCENARIO("The reflected color for a reflective material", "[features/world.featu } } } + +/* ------------------------------------------------------------------------- */ + +SCENARIO("shade_it() with a reflective material", "[features/world.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("shape <- plane() with:") + // | material.reflective | 0.5 | + // | transform | translation(0, -1, 0) | + { + Plane shape; + shape.material().set_reflective(0.5); + shape.set_transform(Matrix::translation(0, -1, 0)); + AND_GIVEN("shape is added to w") + { + w.add_object(&shape); + AND_GIVEN("ray(point(0, 0, -3), vector(0, -sqrt(2) / 2, sqrt(2) / 2)") + { + Ray r(Tuple::Point(0, 0, -3), Tuple::Vector(0, -sqrt(2) / 2, sqrt(2) / 2)); + AND_GIVEN("i <- intersection(sqrt(2), shape)") + { + Intersection i(sqrt(2), &shape); + WHEN("comps <- prepare_computation(i, r)") + { + IntersectionData comps = i.prepare_computations(r); + AND_WHEN("color <- shade_hit(w, comps)") + { + Color color = w.shade_hit(comps); + THEN("color = color(0.87677, 0.92436, 0.82918)") + { + REQUIRE(color == Color(0.87677, 0.92436, 0.82918)); + } + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("color_at() with mutually reflective surfaces", "[features/world.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("w.light <- point_light(point(0, 0, 0), color(1, 1, 1))") + { + w.set_light(PointLight(Tuple::Point(0, 0, 0), Color(1, 1, 1))); + AND_GIVEN("lower <- plane() with:") + // | material.reflective | 1 | + // | transform | translation(0, -1, 0) | + { + Plane lower; + lower.material().set_reflective(1); + lower.set_transform(Matrix::translation(0, -1, 0)); + AND_GIVEN("lower is added to w") + { + w.add_object(&lower); + AND_GIVEN("upper <- plane() with:") + // | material.reflective | 1 | + // | transform | translation(0, 1, 0) | + { + Plane upper; + upper.material().set_reflective(1); + upper.set_transform(Matrix::translation(0, 1, 0)); + AND_GIVEN("upper is added to w") + { + w.add_object(&upper); + AND_GIVEN("ray(point(0, 0, 0), vector(0, 1, 0)") + { + Ray r(Tuple::Point(0, 0, 0), Tuple::Vector(0, 1, 0)); + THEN("color_at(w, r) terminate successfully") + { + w.color_at(r); + } + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The reflected color at the maximum recursive depth", "[features/world.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("shape <- plane() with:") + // | material.reflective | 0.5 | + // | transform | translation(0, -1, 0) | + { + Plane shape; + shape.material().set_reflective(0.5); + shape.set_transform(Matrix::translation(0, -1, 0)); + AND_GIVEN("shape is added to w") + { + w.add_object(&shape); + AND_GIVEN("ray(point(0, 0, -3), vector(0, -sqrt(2) / 2, sqrt(2) / 2)") + { + Ray r(Tuple::Point(0, 0, -3), Tuple::Vector(0, -sqrt(2) / 2, sqrt(2) / 2)); + AND_GIVEN("i <- intersection(sqrt(2), shape)") + { + Intersection i(sqrt(2), &shape); + WHEN("comps <- prepare_computation(i, r)") + { + IntersectionData comps = i.prepare_computations(r); + AND_WHEN("color <- reflected_color(w, comps, 0)") + { + Color color = w.reflected_color(comps, 0); + THEN("color = color(0.19032, 0.2379, 0.14274)") + { + REQUIRE(color == Color(0, 0, 0)); + } + } + } + } + } + } + } + } +}