diff --git a/source/camera.cpp b/source/camera.cpp new file mode 100644 index 0000000..d6fb29a --- /dev/null +++ b/source/camera.cpp @@ -0,0 +1,74 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Camera implementation + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include +#include + +Camera::Camera(uint32_t hsize, uint32_t vsize, double fov) : verticalSize(vsize), horizontalSize(hsize), fieldOfView(fov) +{ + double aspectRatio = (double)hsize / (double)vsize; + double halfView = tan(fov / 2.0); + + if (aspectRatio >= 1) + { + this->halfWidth = halfView; + this->halfHeight = halfView / aspectRatio; + } + else + { + this->halfWidth = halfView * aspectRatio; + this->halfHeight = halfView; + } + + this->pixelSize = (this->halfWidth * 2) / this->horizontalSize; + + this->setTransform(Matrix4().identity()); + +} + +void Camera::setTransform(Matrix transform) +{ + this->transformMatrix = transform; + this->inverseTransform = transform.inverse(); +} + +Ray Camera::rayForPixel(uint32_t pixelX, uint32_t pixelY) +{ + double xOffset = ((double)pixelX + 0.5) * this->pixelSize; + double yOffset = ((double)pixelY + 0.5) * this->pixelSize; + + double worldX = this->halfWidth - xOffset; + double worldY = this->halfHeight - yOffset; + + Tuple pixel = this->inverseTransform * Point(worldX, worldY, -1); + Tuple origin = this->inverseTransform * Point(0, 0, 0); + Tuple direction = (pixel - origin).normalise(); + + return Ray(origin, direction); +} + +Canvas Camera::render(World world) +{ + uint32_t x, y; + Canvas image = Canvas(this->horizontalSize, this->verticalSize); + + for(y = 0; y < this->verticalSize; y++) + { + for(x = 0; x < this->horizontalSize; x++) + { + Ray r = this->rayForPixel(x, y); + Tuple colour = world.colourAt(r); + image.putPixel(x, y, colour); + } + } + + return image; +} \ No newline at end of file diff --git a/source/include/camera.h b/source/include/camera.h new file mode 100644 index 0000000..2b30256 --- /dev/null +++ b/source/include/camera.h @@ -0,0 +1,38 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Camera header + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#ifndef DORAYME_CAMERA_H +#define DORAYME_CAMERA_H + +#include +#include +#include +#include +#include + +class Camera +{ +private: + double halfWidth; + double halfHeight; +public: + uint32_t verticalSize; + uint32_t horizontalSize; + double fieldOfView; + double pixelSize; + Matrix transformMatrix; + Matrix inverseTransform; + +public: + Camera(uint32_t hsize, uint32_t vsize, double fov); + void setTransform(Matrix transform); + Ray rayForPixel(uint32_t pixelX, uint32_t pixelY); + Canvas render(World w); +}; + +#endif /* DORAYME_CAMERA_H */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bf52b63..38580e3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,7 +4,7 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) set(TESTS_SRC tuple_test.cpp colour_test.cpp canvas_test.cpp matrix_test.cpp transformation_test.cpp ray_test.cpp - intersect_test.cpp sphere_test.cpp light_test.cpp material_test.cpp world_test.cpp) + intersect_test.cpp sphere_test.cpp light_test.cpp material_test.cpp world_test.cpp camera_test.cpp) add_executable(testMyRays) target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) @@ -28,5 +28,11 @@ target_include_directories(ch6_test PUBLIC ../source/include) target_sources(ch6_test PRIVATE ch6_test.cpp) target_link_libraries(ch6_test rayonnement) +add_executable(ch7_test) +target_include_directories(ch6_test PUBLIC ../source/include) +target_sources(ch7_test PRIVATE ch7_test.cpp) +target_link_libraries(ch7_test rayonnement) + add_test(NAME Chapter05_Test COMMAND $) -add_test(NAME Chapter06_Test COMMAND $) \ No newline at end of file +add_test(NAME Chapter06_Test COMMAND $) +add_test(NAME Chapter07_Test COMMAND $) \ No newline at end of file diff --git a/tests/camera_test.cpp b/tests/camera_test.cpp new file mode 100644 index 0000000..9aa9eee --- /dev/null +++ b/tests/camera_test.cpp @@ -0,0 +1,111 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Camera unit tests + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST(CameraTest, Constructing_a_camera) +{ + uint32_t hsize = 160; + uint32_t vsize = 120; + double field_of_view = M_PI / 2; + + Camera c = Camera(hsize, vsize, field_of_view); + + ASSERT_EQ(c.horizontalSize, 160); + ASSERT_EQ(c.verticalSize, 120); + ASSERT_TRUE(double_equal(c.fieldOfView, M_PI / 2)); + ASSERT_EQ(c.transformMatrix, Matrix4().identity()); +} + +TEST(CameraTest, Pixel_size_for_a_horizontal_canvas) +{ + Camera c = Camera(200, 125, M_PI / 2); + + ASSERT_TRUE(double_equal(c.pixelSize, 0.01)); +} + +TEST(CameraTest, Pixel_size_for_a_vertical_canvas) +{ + Camera c = Camera(125, 200, M_PI / 2); + + ASSERT_TRUE(double_equal(c.pixelSize, 0.01)); +} + +TEST(CameraTest, Constructing_a_ray_through_the_center_of_the_canvas) +{ + Camera c = Camera(201, 101, M_PI / 2); + Ray r = c.rayForPixel(100, 50); + + ASSERT_EQ(r.origin, Point(0, 0, 0)); + ASSERT_EQ(r.direction, Vector(0, 0, -1)); +} + +TEST(CameraTest, Constructing_a_ray_through_a_corner_of_the_canvas) +{ + Camera c = Camera(201, 101, M_PI / 2); + Ray r = c.rayForPixel(0, 0); + + ASSERT_EQ(r.origin, Point(0, 0, 0)); + + /* Temporary lower the precision */ + set_equal_precision(0.00001); + + ASSERT_EQ(r.direction, Vector(0.66519, 0.33259, -0.66851)); + + set_equal_precision(FLT_EPSILON); +} + +TEST(CameraTest, Constructing_a_ray_when_the_camera_is_transformed) +{ + Camera c = Camera(201, 101, M_PI / 2); + c.setTransform(rotationY(M_PI / 4) * translation(0, -2, 5)); + Ray r = c.rayForPixel(100, 50); + + ASSERT_EQ(r.origin, Point(0, 2, -5)); + ASSERT_EQ(r.direction, Vector(sqrt(2)/2, 0, -sqrt(2)/2)); +} + +TEST(CameraTest, Rendering_a_world_with_a_camera) +{ + World w = DefaultWorld(); + Camera c = Camera(11, 11, M_PI / 2); + + Tuple from = Point(0, 0, -5); + Tuple to = Point(0, 0, 0); + Tuple up = Vector(0, 1, 0); + + c.setTransform(viewTransform(from, to, up)); + + Canvas image = c.render(w); + + /* Temporary lower the precision */ + /* We need to lower a lot as Canvas is not keeping the + * floating point value, but a value between 0 and 255 per channel, + * as it is storing the actual frame buffer, so there is a more big different + * between the value. + */ + set_equal_precision(0.005); + + Colour col = image.getPixel(5, 5); + + ASSERT_EQ(col, Colour(0.38066, 0.47583, 0.2855)); + + set_equal_precision(FLT_EPSILON); +} \ No newline at end of file diff --git a/tests/ch7_test.cpp b/tests/ch7_test.cpp new file mode 100644 index 0000000..18112ff --- /dev/null +++ b/tests/ch7_test.cpp @@ -0,0 +1,83 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Render test for chapter 5 "Put it together". + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + /* First we need to construct the world */ + Sphere floor = Sphere(); + floor.setTransform(scaling(10, 0.01, 10)); + floor.material.colour = Colour(1, 0.9, 0.9); + floor.material.specular = 0; + + Sphere left_wall = Sphere(); + left_wall.setTransform(translation(0, 0, 5) * + rotationY(-M_PI / 4) * rotationX((M_PI / 2)) * + scaling(10, 0.01, 10)); + left_wall.material = floor.material; + + Sphere right_wall = Sphere(); + right_wall.setTransform(translation(0, 0, 5) * + rotationY(M_PI / 4) * rotationX((M_PI / 2)) * + scaling(10, 0.01, 10)); + right_wall.material = floor.material; + + Sphere middle = Sphere(); + middle.setTransform(translation(-0.5, 1, 0.5)); + middle.material.colour = Colour(0.1, 1, 0.5); + middle.material.diffuse = 0.7; + middle.material.specular = 0.3; + + Sphere right = Sphere(); + right.setTransform(translation(1.5, 0.5, -0.5) * scaling(0.5, 0.5, 0.5)); + right.material.colour = Colour(0.5, 1, 0.1); + right.material.diffuse = 0.7; + right.material.specular = 0.3; + + Sphere left = Sphere(); + left.setTransform(translation(-1.5, 0.33, -0.75) * scaling(0.33, 0.33, 0.33)); + left.material.colour = Colour(1, 0.8, 0.1); + left.material.diffuse = 0.7; + left.material.specular = 0.3; + + World w = World(); + + w.addObject(&floor); + w.addObject(&left_wall); + w.addObject(&right_wall); + w.addObject(&middle); + w.addObject(&left); + w.addObject(&right); + + /* Add light */ + Light light = Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1)); + + w.addLight(&light); + + /* Set the camera */ + Camera camera = Camera(1000, 500, M_PI / 3); + camera.setTransform(viewTransform(Point(0, 1.5, -5), + Point(0, 1, 0), + Vector(0, 1, 0))); + + /* Now render it */ + Canvas image = camera.render(w); + + image.SaveAsPNG("ch7_test.png"); + + + return 0; +} \ No newline at end of file