diff --git a/src/camera.h b/src/camera.h index 79c1fad..ca1baca 100644 --- a/src/camera.h +++ b/src/camera.h @@ -1,19 +1,14 @@ #pragma once -#include - -#include "image.h" -#include "ray.h" #include "raytracer.h" -#include "renderobject.h" #include "vec3.h" class Camera { public: - constexpr Camera(Vec3 centre, u32 image_width, u32 image_height, double focal_length) - : centre_{centre} { - constexpr double viewport_height = 2.0; - double viewport_width = viewport_height * static_cast(image_width) / image_height; + constexpr Camera(Vec3 centre, u32 image_width, u32 image_height, f64 focal_length) + : centre_{centre}, image_width_{image_width}, image_height_{image_height} { + constexpr f64 viewport_height = 2.0; + f64 viewport_width = viewport_height * static_cast(image_width) / image_height; Vec3 u_viewport = viewport_width * Vec3::e_x; Vec3 v_viewport = -viewport_height * Vec3::e_y; @@ -31,31 +26,17 @@ class Camera { constexpr Vec3 centre() const { return centre_; } - constexpr void Render(Image& img, const RenderObject& world) const { - for (u32 j = 0; j < img.height(); j++) { - std::clog << "\rWriting line " << j << " of " << img.height() << std::flush; + constexpr Vec3 d_u_pixel() const { return d_u_pixel_; } + constexpr Vec3 d_v_pixel() const { return d_v_pixel_; } - for (u32 i = 0; i < img.width(); i++) { - auto ray = CastRay(i, j); + constexpr u32 image_width() const { return image_width_; } + constexpr u32 image_height() const { return image_height_; } - img[i, j] = Cast(ray, world); - } - } - } - - private: - constexpr Vec3 Image2World(u32 i, u32 j) const { + constexpr Vec3 PixelToWorld(u32 i, u32 j) const { return pixel_00_ + i * d_u_pixel_ + j * d_v_pixel_; } - constexpr Ray CastRay(u32 i, u32 j) const { - auto pixel_centre = Image2World(i, j); - - auto ray_direction = pixel_centre - centre(); - - return Ray{centre(), ray_direction}; - } - + private: Vec3 centre_; // position of pixel 0, 0 @@ -64,4 +45,7 @@ class Camera { // Vec3 pointing from pixel to right/lower neighbour resp. Vec3 d_u_pixel_; Vec3 d_v_pixel_; + + u32 image_width_; + u32 image_height_; }; diff --git a/src/colour.h b/src/colour.h index 40f034a..47d57ee 100644 --- a/src/colour.h +++ b/src/colour.h @@ -17,21 +17,31 @@ class Colour { static const Colour kWhite; constexpr Colour() = default; - constexpr Colour(double r, double g, double b) : r_{r}, g_{g}, b_{b} { - assert(0.0 <= r && r <= 1.0); - assert(0.0 <= g && g <= 1.0); - assert(0.0 <= b && b <= 1.0); + constexpr Colour(f64 r, f64 g, f64 b) : r_{r}, g_{g}, b_{b} {} + + constexpr void CheckValid() const { + assert(0.0 <= r() && r() <= 1.0); + assert(0.0 <= g() && g() <= 1.0); + assert(0.0 <= b() && b() <= 1.0); } constexpr explicit Colour(Vec3 v) : Colour{v.x(), v.y(), v.z()} {} - constexpr double& r() { return r_; } - constexpr double& g() { return g_; } - constexpr double& b() { return b_; } + constexpr f64& r() { return r_; } + constexpr f64& g() { return g_; } + constexpr f64& b() { return b_; } - constexpr double r() const { return r_; } - constexpr double g() const { return g_; } - constexpr double b() const { return b_; } + constexpr f64 r() const { return r_; } + constexpr f64 g() const { return g_; } + constexpr f64 b() const { return b_; } + + constexpr Colour& operator+=(const Colour& c) { + r_ += c.r(); + g_ += c.g(); + b_ += c.b(); + + return *this; + } std::string to_string() const { std::stringstream ss; @@ -46,17 +56,22 @@ class Colour { } private: - double r_; - double g_; - double b_; + f64 r_ = 0.0; + f64 g_ = 0.0; + f64 b_ = 0.0; }; constexpr Colour Colour::kBlack{0.0, 0.0, 0.0}; -constexpr Colour operator*(double t, Colour col) { return {t * col.r(), t * col.g(), t * col.b()}; } +constexpr Colour operator*(f64 t, Colour col) { return {t * col.r(), t * col.g(), t * col.b()}; } +constexpr Colour operator/(Colour col, f64 t) { return (1.0 / t) * col; } constexpr Colour operator+(Colour c1, Colour c2) { - return {c1.r() + c2.r(), c1.g() + c2.g(), c1.b() + c2.b()}; + Colour out{c1}; + + out += c2; + + return out; } inline std::ostream& operator<<(std::ostream& os, Colour colour) { diff --git a/src/image.h b/src/image.h index 8f881ed..39a4e92 100644 --- a/src/image.h +++ b/src/image.h @@ -57,6 +57,8 @@ constexpr std::ostream& operator<<(std::ostream& os, const Image& img) { for (u32 i = 0; i < img.width(); i++) { const Colour col = img[i, j]; + col.CheckValid(); + os << col << newline; } } diff --git a/src/interval.h b/src/interval.h index a2d0dfa..8351caa 100644 --- a/src/interval.h +++ b/src/interval.h @@ -1,22 +1,26 @@ #pragma once +#include + #include "raytracer.h" class Interval { public: static const Interval kEmpty, kFull, kPositive; constexpr Interval() = default; - constexpr Interval(double min, double max) : min_{min}, max_{max} {} + constexpr Interval(f64 min, f64 max) : min_{min}, max_{max} {} - constexpr double min() const { return min_; } - constexpr double max() const { return max_; } + constexpr f64 min() const { return min_; } + constexpr f64 max() const { return max_; } - constexpr bool contains(double x) const { return min_ <= x && x <= max_; } - constexpr bool surronds(double x) const { return min_ < x && x < max_; } + constexpr bool contains(f64 x) const { return min_ <= x && x <= max_; } + constexpr bool surronds(f64 x) const { return min_ < x && x < max_; } + + constexpr double clamp(f64 x) const { return std::clamp(x, min_, max_); } private: - double min_ = kInf; - double max_ = -kInf; + f64 min_ = kInf; + f64 max_ = -kInf; }; constexpr Interval Interval::kEmpty{kInf, -kInf}; diff --git a/src/main.cc b/src/main.cc index 26e5699..9eecf53 100644 --- a/src/main.cc +++ b/src/main.cc @@ -4,42 +4,43 @@ #include "camera.h" #include "image.h" #include "raytracer.h" +#include "renderer.h" #include "renderobject.h" #include "renderobjectlist.h" #include "sphere.h" #include "vec3.h" int main(int /* argc */, char* /* argv */[]) { - // image - - constexpr double aspect_ratio = 16.0 / 9.0; - - constexpr u32 image_width = 800; - - constexpr auto image_height = static_cast(image_width / aspect_ratio); - - static_assert(image_height >= 1); - - Image img{image_width, image_height}; - // world RenderObjectList world; - world.Add(std::make_shared(Vec3{0, -100.5, -1}, 100)); + // world.Add(std::make_shared(Vec3{0, -100.5, -1}, 100)); world.Add(std::make_shared(-Vec3::e_z, 0.5)); // camera - constexpr double focal_length = 1.0; + // constexpr f64 aspect_ratio = 16.0 / 9.0; + + // constexpr u32 image_width = 800; + // constexpr auto image_height = static_cast(image_width / aspect_ratio); + + constexpr u32 image_width = 1920; + constexpr u32 image_height = 1080; + + static_assert(image_height >= 1); + + constexpr f64 focal_length = 1.0; constexpr Point3 camera_centre{0.0, 0.0, 0.0}; - Camera camera{camera_centre, img.width(), img.height(), focal_length}; + Camera camera{camera_centre, image_width, image_height, focal_length}; // render - camera.Render(img, world); + Renderer renderer{camera}; + + auto img = renderer.Render(world); std::cout << img; diff --git a/src/rand.h b/src/rand.h new file mode 100644 index 0000000..9144fe8 --- /dev/null +++ b/src/rand.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "raytracer.h" +class Random { + public: + constexpr explicit Random(u64 j) { + assert(j != v_); + + u_ = j ^ v_; + GenU64(); + + v_ = u_; + GenU64(); + + w_ = v_; + GenU64(); + } + + constexpr u64 GenU64() { + u_ = u_ * 2862933555777941757ULL + 7046029254386353087ULL; + + v_ ^= v_ >> 17; + v_ ^= v_ << 31; + v_ ^= v_ >> 8; + + w_ = 4294957665ULL * (w_ & 0xffffffffULL) + (w_ >> 32); + + u64 x = u_ ^ (u_ << 21); + x ^= x >> 35; + x ^= x << 4; + + return (x + v_) ^ w_; + } + + constexpr u32 GenU32() { return static_cast(GenU64()); } + + constexpr f64 GenF64() { return 5.42101086242752217E-20 * static_cast(GenU64()); } + + constexpr f64 GenF64(f64 min, f64 max) { return min + (max - min) * GenF64(); } + + private: + u64 u_ = 0ULL; + u64 v_ = 4101842887655102017ULL; + u64 w_ = 1ULL; +}; diff --git a/src/ray.h b/src/ray.h index aee83c3..67f7eb7 100644 --- a/src/ray.h +++ b/src/ray.h @@ -1,5 +1,6 @@ #pragma once +#include "raytracer.h" #include "vec3.h" class Ray { @@ -11,7 +12,7 @@ class Ray { Point3 origin() const { return orig_; } Vec3 direction() const { return dir_; } - constexpr Point3 At(double t) const { return orig_ + t * dir_; } + constexpr Point3 At(f64 t) const { return orig_ + t * dir_; } private: Point3 orig_; diff --git a/src/raytracer.h b/src/raytracer.h index a48bdd2..40fccf2 100644 --- a/src/raytracer.h +++ b/src/raytracer.h @@ -5,12 +5,15 @@ #include using u32 = uint32_t; +using u64 = uint64_t; -constexpr double kInf = std::numeric_limits::infinity(); -constexpr double kPi = std::numbers::pi_v; +using f64 = double; + +constexpr f64 kInf = std::numeric_limits::infinity(); +constexpr f64 kPi = std::numbers::pi_v; constexpr char newline = '\n'; -constexpr u32 FToU8(double x) { return static_cast(255.999 * x); } +constexpr u32 FToU8(f64 x) { return static_cast(255.999 * x); } -constexpr double deg2rad(double deg) { return deg * kPi / 180.0; } +constexpr f64 deg2rad(f64 deg) { return deg * kPi / 180.0; } diff --git a/src/renderer.h b/src/renderer.h new file mode 100644 index 0000000..5e75940 --- /dev/null +++ b/src/renderer.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include "camera.h" +#include "colour.h" +#include "image.h" +#include "interval.h" +#include "rand.h" +#include "ray.h" +#include "raytracer.h" +#include "renderobject.h" +#include "vec3.h" + +class Renderer { + public: + constexpr explicit Renderer(Camera camera, u32 samples_per_pixel = 10) + : camera_{camera}, rand_{0}, samples_per_pixel_{samples_per_pixel} {} + + constexpr Image Render(const RenderObject& world) { + Image img{camera_.image_width(), camera_.image_height()}; + + for (u32 j = 0; j < img.height(); j++) { + std::clog << "\rWriting line " << j << " of " << img.height() << std::flush; + + for (u32 i = 0; i < img.width(); i++) { + Colour colour_sum{0.0, 0.0, 0.0}; + + for (u32 num_sample = 0; num_sample < samples_per_pixel_; num_sample++) { + auto ray = SampleRay(i, j); + + colour_sum += Cast(ray, world); + } + + img[i, j] = colour_sum / samples_per_pixel_; + } + } + + return img; + } + + private: + constexpr Ray SampleRay(u32 i, u32 j) { + auto pixel_centre = camera_.PixelToWorld(i, j); + + pixel_centre += rand_.GenF64(-0.5, 0.5) * camera_.d_u_pixel(); + pixel_centre += rand_.GenF64(-0.5, 0.5) * camera_.d_v_pixel(); + + auto ray_direction = pixel_centre - camera_.centre(); + + return Ray{camera_.centre(), ray_direction}; + } + + static constexpr Colour Cast(const Ray& ray, const RenderObject& world) { + auto hit_record = world.hit(ray, Interval::kPositive); + + if (hit_record.has_value()) { + Vec3 n = hit_record->normal; + + if (!hit_record->front_face) { + return Colour::kBlack; + } + + return Colour{0.5 * (n + Vec3{1, 1, 1})}; + } + + auto unit_dir = ray.direction().normed(); + + f64 a = 0.5 * (unit_dir.y() + 1.0); + + return (1.0 - a) * Colour{1.0, 1.0, 1.0} + a * Colour{0.5, 0.7, 1.0}; + } + + Camera camera_; + + Random rand_; + + u32 samples_per_pixel_; +}; diff --git a/src/renderobject.h b/src/renderobject.h index bbbec7b..70ab0c1 100644 --- a/src/renderobject.h +++ b/src/renderobject.h @@ -3,7 +3,6 @@ #include #include -#include "colour.h" #include "interval.h" #include "ray.h" #include "vec3.h" @@ -11,7 +10,7 @@ struct HitRecord { Point3 p; Vec3 normal; - double t = 0.0; + f64 t = 0.0; bool front_face = true; }; @@ -31,27 +30,3 @@ class RenderObject { }; using SharedRenderObject = std::shared_ptr; - -constexpr Colour Cast(const Ray& ray, const RenderObject& world) { - auto hit_record = world.hit(ray, Interval::kPositive); - // auto hit_record = world.hit(ray, Interval{0.505, kInf}); - - if (hit_record.has_value()) { - // assert(hit_record.front_face); - - Vec3 n = hit_record->normal; - - if (!hit_record->front_face) { - // return Colour{0.0, 0.0, 0.0}; - return Colour::kBlack; - } - - return Colour{0.5 * (n + Vec3{1, 1, 1})}; - } - - auto unit_dir = ray.direction().normed(); - - double a = 0.5 * (unit_dir.y() + 1.0); - - return (1.0 - a) * Colour{1.0, 1.0, 1.0} + a * Colour{0.5, 0.7, 1.0}; -} diff --git a/src/renderobjectlist.h b/src/renderobjectlist.h index 8ba93c9..e716086 100644 --- a/src/renderobjectlist.h +++ b/src/renderobjectlist.h @@ -27,7 +27,7 @@ class RenderObjectList : public RenderObject { std::optional hit(const Ray& ray, Interval ts) const override { std::optional closest_hit_record = std::nullopt; - double closest_t = ts.max(); + f64 closest_t = ts.max(); for (const auto& obj : objs_) { Interval _ts{ts.min(), closest_t}; diff --git a/src/sphere.h b/src/sphere.h index 34b2d81..852dd5f 100644 --- a/src/sphere.h +++ b/src/sphere.h @@ -6,29 +6,30 @@ #include "interval.h" #include "ray.h" +#include "raytracer.h" #include "renderobject.h" #include "vec3.h" class Sphere : public RenderObject { public: constexpr Sphere() = default; - constexpr Sphere(Point3 centre, double radius) : centre_{centre}, radius_{radius} { + constexpr Sphere(Point3 centre, f64 radius) : centre_{centre}, radius_{radius} { assert(radius >= 0); } constexpr Point3 centre() const { return centre_; } - constexpr double radius() const { return radius_; } + constexpr f64 radius() const { return radius_; } std::optional hit(const Ray& ray, Interval ts) const override { HitRecord hit_record; Vec3 oc = ray.origin() - centre_; - double a = ray.direction().squared(); - double b_half = dot(oc, ray.direction()); - double c = oc.squared() - radius_ * radius_; + f64 a = ray.direction().squared(); + f64 b_half = dot(oc, ray.direction()); + f64 c = oc.squared() - radius_ * radius_; - auto discr = b_half * b_half - a * c; + f64 discr = b_half * b_half - a * c; if (discr < 0) { // miss @@ -38,7 +39,7 @@ class Sphere : public RenderObject { // hit // smaller of the ts that hit the sphere - double t = (-b_half - std::sqrt(discr)) / a; + f64 t = (-b_half - std::sqrt(discr)) / a; if (t >= ts.max()) { // if the smaller t is already too large, we don't need to check the second one since it @@ -77,5 +78,5 @@ class Sphere : public RenderObject { private: Point3 centre_{}; - double radius_ = 0.0; + f64 radius_ = 0.0; }; diff --git a/src/vec3.h b/src/vec3.h index f8dd25d..5d4794e 100644 --- a/src/vec3.h +++ b/src/vec3.h @@ -6,31 +6,33 @@ #include #include +#include "raytracer.h" + class Vec3 { public: static const Vec3 e_x; static const Vec3 e_y; static const Vec3 e_z; - constexpr Vec3(double x, double y, double z) : xyz_{x, y, z} {} + constexpr Vec3(f64 x, f64 y, f64 z) : xyz_{x, y, z} {} constexpr Vec3() : Vec3{0, 0, 0} {} - constexpr double& x() { return xyz_[0]; } - constexpr double& y() { return xyz_[1]; } - constexpr double& z() { return xyz_[2]; } + constexpr f64& x() { return xyz_[0]; } + constexpr f64& y() { return xyz_[1]; } + constexpr f64& z() { return xyz_[2]; } - constexpr double x() const { return xyz_[0]; } - constexpr double y() const { return xyz_[1]; } - constexpr double z() const { return xyz_[2]; } + constexpr f64 x() const { return xyz_[0]; } + constexpr f64 y() const { return xyz_[1]; } + constexpr f64 z() const { return xyz_[2]; } constexpr Vec3 operator-() const { return Vec3{-x(), -y(), -z()}; } - constexpr double& operator[](std::size_t i) { + constexpr f64& operator[](std::size_t i) { assert(i <= 3); return xyz_[i]; } - constexpr double operator[](std::size_t i) const { + constexpr f64 operator[](std::size_t i) const { assert(i <= 3); return xyz_[i]; } @@ -43,7 +45,7 @@ class Vec3 { return *this; } - constexpr Vec3& operator*=(double t) { + constexpr Vec3& operator*=(f64 t) { x() *= t; y() *= t; z() *= t; @@ -51,15 +53,15 @@ class Vec3 { return *this; } - constexpr Vec3& operator/=(double t) { + constexpr Vec3& operator/=(f64 t) { *this *= 1 / t; return *this; } - constexpr double squared() const { return x() * x() + y() * y() + z() * z(); } + constexpr f64 squared() const { return x() * x() + y() * y() + z() * z(); } - constexpr double norm() const { return std::sqrt(squared()); } + constexpr f64 norm() const { return std::sqrt(squared()); } constexpr Vec3 normed() const { auto r = *this; @@ -68,7 +70,7 @@ class Vec3 { } private: - std::array xyz_; + std::array xyz_; }; constexpr Vec3 Vec3::e_x{1.0, 0.0, 0.0}; @@ -93,15 +95,15 @@ constexpr Vec3 operator*(const Vec3& u, const Vec3& v) { return {u.x() * v.x(), u.y() * v.y(), u.z() * v.z()}; } -constexpr Vec3 operator*(double t, const Vec3& v) { +constexpr Vec3 operator*(f64 t, const Vec3& v) { Vec3 out = v; out *= t; return out; } -constexpr Vec3 operator/(const Vec3& v, double t) { return (1 / t) * v; } +constexpr Vec3 operator/(const Vec3& v, f64 t) { return (1 / t) * v; } -constexpr double dot(const Vec3& u, const Vec3& v) { +constexpr f64 dot(const Vec3& u, const Vec3& v) { Vec3 tmp = u * v; return tmp.x() + tmp.y() + tmp.z(); }