├── .gitignore ├── .DS_Store ├── CMakeLists.txt ├── README.md ├── ray.h ├── hittable.h ├── colour.h ├── interval.h ├── rtweekend.h ├── hittable_list.h ├── sphere.h ├── main.cpp ├── material.h ├── vec3.h └── camera.h /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | out/* -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syntax/raytracer/main/.DS_Store -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(ImageRenderer) 4 | 5 | set(CMAKE_CXX_STANDARD 23) 6 | 7 | add_executable(image_renderer main.cpp) 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # raytracer 2 | 3 | following https://raytracing.github.io/books/RayTracingInOneWeekend.html as I have never done a computer graphics course, and also want to get better at cpp. 4 | 5 | This is an initial projcet to get me back up to speed with cpp and then hopefully I will aim to do something a bit more off the beaten track. 6 | 7 | 8 | to build (following `mkdir build` if not already): 9 | 10 | ```bash 11 | cd build 12 | cmake .. 13 | cmake --build . 14 | ``` -------------------------------------------------------------------------------- /ray.h: -------------------------------------------------------------------------------- 1 | #ifndef RAY_H 2 | #define RAY_H 3 | 4 | #include "vec3.h" 5 | 6 | class ray { 7 | public: 8 | ray() {} 9 | 10 | ray (const point3& origin, const vec3& direction) 11 | : orig(origin), dir(direction) 12 | {} 13 | 14 | 15 | const point3& origin() const { return orig; } 16 | const vec3& direction() const { return dir; } 17 | 18 | point3 at(double t) const { 19 | return orig + t*dir; 20 | } 21 | 22 | private: 23 | point3 orig; 24 | vec3 dir; 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /hittable.h: -------------------------------------------------------------------------------- 1 | #ifndef HITTABLE 2 | #define HITTABLE 3 | 4 | class material; 5 | 6 | struct hit_record { 7 | public: 8 | point3 p; 9 | vec3 normal; 10 | const material* material_ptr; 11 | double t; 12 | bool front_face; 13 | 14 | inline void set_face_normal(const ray& r, const vec3& outward_normal) { 15 | front_face = dot(r.direction(), outward_normal) < 0; 16 | normal = front_face ? outward_normal : -outward_normal; 17 | } 18 | }; 19 | 20 | class hittable { 21 | public: 22 | virtual ~hittable() = default; 23 | 24 | virtual bool hit(const ray& r, interval ray_t, hit_record& rec) const = 0; 25 | }; 26 | 27 | 28 | #endif -------------------------------------------------------------------------------- /colour.h: -------------------------------------------------------------------------------- 1 | #ifndef COLOUR_H 2 | #define COLOUR_H 3 | 4 | #include "interval.h" 5 | #include "vec3.h" 6 | 7 | using colour = vec3; 8 | 9 | inline double linear_to_gamma(double x) { 10 | if (x > 0) 11 | return std::sqrt(x); 12 | 13 | return 0; 14 | } 15 | 16 | void write_colour(std::ostream& out, const colour& pixel_colour) { 17 | auto r = pixel_colour.x(); 18 | auto g = pixel_colour.y(); 19 | auto b = pixel_colour.z(); 20 | 21 | 22 | // apply gamma transform 23 | r = linear_to_gamma(r); 24 | g = linear_to_gamma(g); 25 | b = linear_to_gamma(b); 26 | 27 | 28 | // Translate the [0,1] component values to the byte range [0,255] 29 | static const interval intensity(0.000, 0.999); 30 | int rbyte = int(256 * intensity.clamp(r)); 31 | int gbyte = int(256 * intensity.clamp(g)); 32 | int bbyte = int(256 * intensity.clamp(b)); 33 | 34 | out << rbyte << ' ' << gbyte << ' ' << bbyte << '\n'; 35 | } 36 | 37 | #endif -------------------------------------------------------------------------------- /interval.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERVAL_H 2 | #define INTERVAL_H 3 | 4 | class interval { 5 | public: 6 | double min; 7 | double max; 8 | 9 | interval() : min(+infinity), max(-infinity) {} 10 | 11 | interval(double min, double max) : min(min), max(max) {} 12 | 13 | double size() const { 14 | return max - min; 15 | } 16 | 17 | bool contains(double x) const { 18 | return min <= x && x <= max; 19 | } 20 | 21 | bool surrounds(double x) const { 22 | return min < x && x < max; 23 | } 24 | 25 | double clamp(double x) const { 26 | if (x < min) return min; 27 | if (x > max) return max; 28 | return x; 29 | } 30 | 31 | static const interval empty, universe; 32 | }; 33 | 34 | const interval interval::empty = interval(+infinity, -infinity); 35 | const interval interval::universe = interval(-infinity, +infinity); 36 | 37 | #endif -------------------------------------------------------------------------------- /rtweekend.h: -------------------------------------------------------------------------------- 1 | #ifndef RTWEEKEND_H 2 | #define RTWEEKEND_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | // c++ Std Usings 12 | 13 | using std::make_shared; 14 | using std::shared_ptr; 15 | 16 | // consts 17 | 18 | const double infinity = std::numeric_limits::infinity(); 19 | const double pi = 3.1415926535897932385; 20 | 21 | // util Functions 22 | 23 | inline double degrees_to_radians(double degrees) { 24 | return degrees * pi / 180.0; 25 | } 26 | 27 | inline double random_double() { 28 | // Returns a random real in [0,1) 29 | static std::uniform_real_distribution distribution(0.0, 1.0); 30 | static std::mt19937 generator; 31 | return distribution(generator); 32 | } 33 | 34 | inline double random_double(double min, double max) { 35 | // Returns a random real within [min,max) 36 | return min + (max - min) * random_double(); 37 | } 38 | 39 | // common Headers 40 | 41 | #include "colour.h" 42 | #include "ray.h" 43 | #include "interval.h" 44 | #include "vec3.h" 45 | 46 | #endif -------------------------------------------------------------------------------- /hittable_list.h: -------------------------------------------------------------------------------- 1 | #ifndef HITTABLE_LIST_H 2 | #define HITTABLE_LIST_H 3 | 4 | #include "hittable.h" 5 | 6 | #include 7 | 8 | class hittable_list : public hittable { 9 | public: 10 | std::vector> objects; 11 | 12 | hittable_list() = default; 13 | explicit hittable_list(std::unique_ptr object) { 14 | add(std::move(object)); 15 | } 16 | 17 | void clear() { objects.clear(); } 18 | 19 | void add(std::unique_ptr object) { 20 | objects.push_back(std::move(object)); 21 | } 22 | 23 | 24 | bool hit(const ray& r, interval ray_t, hit_record& rec) const override { 25 | hit_record temp_rec; 26 | bool hit_anything = false; 27 | auto closest_so_far = ray_t.max; 28 | 29 | for (const auto& object : objects) { 30 | if (object->hit(r, {ray_t.min, closest_so_far}, temp_rec)) { 31 | hit_anything = true; 32 | closest_so_far = temp_rec.t; 33 | rec = temp_rec; 34 | } 35 | } 36 | 37 | return hit_anything; 38 | } 39 | }; 40 | 41 | #endif -------------------------------------------------------------------------------- /sphere.h: -------------------------------------------------------------------------------- 1 | #ifndef SPHERE 2 | #define SPHERE 3 | 4 | #include "hittable.h" 5 | #include 6 | 7 | class sphere : public hittable { 8 | public: 9 | sphere(const point3& center, double radius, std::unique_ptr material_ptr) : center(center), radius(std::fmax(0,radius)), material_ptr(std::move(material_ptr)) { 10 | } 11 | 12 | bool hit(const ray& r, interval ray_t, hit_record& rec) const override { 13 | vec3 oc = center - r.origin(); 14 | auto a = r.direction().length_squared(); 15 | auto h = dot(r.direction(), oc); 16 | auto c = oc.length_squared() - radius*radius; 17 | 18 | auto discriminant = h*h - a*c; 19 | if (discriminant < 0) 20 | return false; 21 | 22 | // TODO: I want to use the quake3 fast inverse square root here 23 | auto sqrtd = std::sqrt(discriminant); 24 | 25 | // find the nearest root that lies in the acceptable range. 26 | auto root = (h - sqrtd) / a; 27 | if (!ray_t.surrounds(root)) { 28 | root = (h + sqrtd) / a; 29 | if (!ray_t.surrounds(root)) 30 | return false; 31 | } 32 | 33 | rec.t = root; 34 | rec.p = r.at(rec.t); 35 | rec.normal = (rec.p - center) / radius; 36 | rec.set_face_normal(r, rec.normal); 37 | rec.material_ptr = material_ptr.get(); 38 | 39 | return true; 40 | } 41 | 42 | private: 43 | point3 center; 44 | double radius; 45 | std::unique_ptr material_ptr; 46 | }; 47 | 48 | #endif -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "rtweekend.h" 2 | 3 | #include "camera.h" 4 | #include "hittable.h" 5 | #include "material.h" 6 | #include "hittable_list.h" 7 | #include "sphere.h" 8 | 9 | #include 10 | 11 | int main() { 12 | hittable_list world; 13 | 14 | // ground 15 | world.add(std::make_unique( 16 | point3(0, -1000, 0), 1000, 17 | std::unique_ptr( new lambertian(colour(0.5, 0.5, 0.5)) ) 18 | )); 19 | 20 | // random small spheres 21 | for (int a = -3; a < 3; a++) { 22 | for (int b = -3; b < 3; b++) { 23 | auto choose_mat = random_double(); 24 | point3 center(a + 0.9 * random_double(), 0.2, b + 0.9 * random_double()); 25 | 26 | if ((center - point3(4, 0.2, 0)).length() > 0.9) { 27 | if (choose_mat < 0.8) { 28 | // diffuse 29 | auto albedo = colour::random() * colour::random(); 30 | world.add(std::make_unique( 31 | center, 0.2, 32 | std::unique_ptr( new lambertian(albedo) ) 33 | )); 34 | } else if (choose_mat < 0.95) { 35 | // metal 36 | auto albedo = colour::random(0.5, 1); 37 | auto fuzz = random_double(0, 0.5); 38 | world.add(std::make_unique( 39 | center, 0.2, 40 | std::unique_ptr( new metal(albedo, fuzz) ) 41 | )); 42 | } else { 43 | // glass 44 | world.add(std::make_unique( 45 | center, 0.2, 46 | std::unique_ptr( new dielectric(1.5) ) 47 | )); 48 | } 49 | } 50 | } 51 | } 52 | 53 | world.add(std::make_unique( 54 | point3(0, 1, 0), 1.0, 55 | std::unique_ptr( new dielectric(1.5) ) 56 | )); 57 | 58 | world.add(std::make_unique( 59 | point3(-4, 1, 0), 1.0, 60 | std::unique_ptr( new lambertian(colour(0.4, 0.2, 0.1)) ) 61 | )); 62 | 63 | world.add(std::make_unique( 64 | point3(4, 1, 0), 1.0, 65 | std::unique_ptr( new metal(colour(0.7, 0.6, 0.5), 0.0) ) 66 | )); 67 | 68 | camera cam; 69 | cam.aspect_ratio = 16.0 / 9.0; 70 | cam.image_width = 1200; 71 | cam.samples_per_pixel = 20; // when this is set at 500, it will take longer. 72 | cam.max_depth = 50; 73 | 74 | cam.vfov = 20; 75 | cam.lookfrom = point3(13, 2, 3); 76 | cam.lookat = point3(0, 0, 0); 77 | cam.vup = vec3(0, 1, 0); 78 | cam.defocus_angle = 0.6; 79 | cam.focus_distance = 10.0; 80 | 81 | cam.render(world); 82 | } -------------------------------------------------------------------------------- /material.h: -------------------------------------------------------------------------------- 1 | #ifndef MATERIAL_H 2 | #define MATERIAL_H 3 | 4 | 5 | #include "hittable.h" 6 | 7 | class material { 8 | public: 9 | virtual ~material() = default; 10 | 11 | virtual bool scatter(const ray& r_in, 12 | const hit_record& rec, 13 | colour& attenuation, 14 | ray& scattered) const {return false;} 15 | }; 16 | 17 | class lambertian : public material { 18 | public: 19 | lambertian(const colour& a) : albedo(a) {} 20 | 21 | bool scatter(const ray& r_in, 22 | const hit_record& rec, 23 | colour& attenuation, 24 | ray& scattered) 25 | const override { 26 | vec3 scatter_direction = rec.normal + random_unit_vector(); 27 | 28 | // Catch degenerate scatter direction 29 | if (scatter_direction.near_zero()) 30 | scatter_direction = rec.normal; 31 | 32 | 33 | scattered = ray(rec.p, scatter_direction); 34 | attenuation = albedo; 35 | return true; 36 | } 37 | 38 | public: 39 | colour albedo; 40 | }; 41 | 42 | class metal : public material { 43 | public: 44 | metal(const colour& a, double fuzz) : albedo(a), fuzz(fuzz < 1 ? fuzz : 1) {} 45 | 46 | bool scatter(const ray& r_in, 47 | const hit_record& rec, 48 | colour& attenuation, 49 | ray& scattered) 50 | const override { 51 | vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal); 52 | scattered = ray(rec.p, reflected); 53 | attenuation = albedo; 54 | return true; 55 | } 56 | 57 | public: 58 | colour albedo; 59 | double fuzz; 60 | }; 61 | 62 | class dielectric : public material { 63 | public: 64 | dielectric(double ri) : refraction_index(ri) {} 65 | 66 | bool scatter(const ray& r_in, 67 | const hit_record& rec, 68 | colour& attenuation, 69 | ray& scattered) 70 | const override { 71 | attenuation = colour(1.0, 1.0, 1.0); 72 | double ri = rec.front_face ? (1.0/refraction_index) : refraction_index; 73 | 74 | vec3 unit_direction = unit_vector(r_in.direction()); 75 | // handling for total intel reflection 76 | double cos_theta = std::fmin(dot(-unit_direction, rec.normal), 1.0); 77 | double sin_theta = std::sqrt(1.0 - cos_theta*cos_theta); 78 | 79 | bool cannot_refract = ri * sin_theta > 1.0; 80 | vec3 direction; 81 | 82 | if (cannot_refract || reflectance(cos_theta, ri) > random_double()) 83 | direction = reflect(unit_direction, rec.normal); 84 | else 85 | direction = refract(unit_direction, rec.normal, ri); 86 | 87 | scattered = ray(rec.p, direction); 88 | return true; 89 | } 90 | 91 | private: 92 | double refraction_index; 93 | 94 | static double reflectance(double cosine, double refraction_index) { 95 | // this uses Schlick's approximation for reflectance (simply polynomial) 96 | auto r0 = (1 - refraction_index) / (1 + refraction_index); 97 | r0 = r0*r0; 98 | return r0 + (1-r0)*std::pow((1 - cosine),5); 99 | } 100 | 101 | }; 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /vec3.h: -------------------------------------------------------------------------------- 1 | #ifndef VEC3_H 2 | #define VEC3_H 3 | 4 | 5 | class vec3 { 6 | public: 7 | double e[3]; 8 | 9 | // init with no args, or 3 args 10 | vec3() : e{0,0,0} {} 11 | vec3(double e0, double e1, double e2) : e{e0, e1, e2} {} 12 | 13 | // accessors 14 | double x() const { return e[0]; } 15 | double y() const { return e[1]; } 16 | double z() const { return e[2]; } 17 | 18 | // operators, overriding 19 | vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); } 20 | double operator[](int i) const { return e[i]; } 21 | double& operator[](int i) { return e[i]; } 22 | 23 | vec3& operator+=(const vec3 &v) { 24 | e[0] += v.e[0]; 25 | e[1] += v.e[1]; 26 | e[2] += v.e[2]; 27 | return *this; 28 | } 29 | 30 | vec3& operator*=(const double f) { 31 | e[0] *= f; 32 | e[1] *= f; 33 | e[2] *= f; 34 | return *this; 35 | } 36 | 37 | vec3& operator/=(const double f) { 38 | return *this *= 1/f; 39 | } 40 | 41 | double length() const { 42 | return sqrt(length_squared()); 43 | } 44 | 45 | double length_squared() const { 46 | return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; 47 | } 48 | 49 | bool near_zero() const { 50 | // Return true if the vector is close to zero in all dimensions. 51 | const auto s = 1e-8; 52 | return (fabs(e[0]) < s) && (fabs(e[1]) < s) && (fabs(e[2]) < s); 53 | } 54 | 55 | inline static vec3 random() { 56 | return vec3(random_double(), random_double(), random_double()); 57 | } 58 | 59 | inline static vec3 random(double min, double max) { 60 | return vec3(random_double(min, max), random_double(min, max), random_double(min, max)); 61 | } 62 | }; 63 | 64 | // aliases 65 | using point3 = vec3; 66 | 67 | // vec3 utility functions 68 | inline std::ostream& operator<<(std::ostream &out, const vec3 &v) { 69 | return out << v.e[0] << ' ' << v.e[1] << ' ' << v.e[2]; 70 | } 71 | 72 | inline vec3 operator+(const vec3 &u, const vec3 &v) { 73 | return vec3(u.e[0] + v.e[0], u.e[1] + v.e[1], u.e[2] + v.e[2]); 74 | } 75 | 76 | inline vec3 operator-(const vec3 &u, const vec3 &v) { 77 | return vec3(u.e[0] - v.e[0], u.e[1] - v.e[1], u.e[2] - v.e[2]); 78 | } 79 | 80 | inline vec3 operator*(const vec3 &u, const vec3 &v) { 81 | return vec3(u.e[0] * v.e[0], u.e[1] * v.e[1], u.e[2] * v.e[2]); 82 | } 83 | 84 | inline vec3 operator*(double t, const vec3 &v) { 85 | return vec3(t*v.e[0], t*v.e[1], t*v.e[2]); 86 | } 87 | 88 | inline vec3 operator*(const vec3 &v, double t) { 89 | return t * v; 90 | } 91 | 92 | inline vec3 operator/(vec3 v, double t) { 93 | return (1/t) * v; 94 | } 95 | 96 | inline double dot(const vec3 &u, const vec3 &v) { 97 | return u.e[0] * v.e[0] 98 | + u.e[1] * v.e[1] 99 | + u.e[2] * v.e[2]; 100 | } 101 | 102 | inline vec3 cross(const vec3 &u, const vec3 &v) { 103 | return vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1], 104 | u.e[2] * v.e[0] - u.e[0] * v.e[2], 105 | u.e[0] * v.e[1] - u.e[1] * v.e[0]); 106 | } 107 | 108 | inline vec3 unit_vector(vec3 v) { 109 | return v / v.length(); 110 | } 111 | 112 | inline vec3 random_unit_vector() { 113 | // ensure it lies within the unit sphere, rejection policy. 114 | // probably optimisable. 115 | while (true) { 116 | auto p = vec3::random(-1,1); 117 | auto lensq = p.length_squared(); 118 | if (1e-160 < lensq && lensq <= 1) // magic number is to avoid floating-point abstraction 119 | return p / sqrt(lensq); 120 | } 121 | } 122 | 123 | inline vec3 random_on_hemisphere(const vec3& normal) { 124 | auto on_unit_sphere = random_unit_vector(); 125 | if (dot(on_unit_sphere, normal) > 0.0) // in the same hemisphere as the normal 126 | return on_unit_sphere; 127 | else 128 | return -on_unit_sphere; 129 | } 130 | 131 | inline vec3 reflect(const vec3& v, const vec3& n) { 132 | return v - 2*dot(v,n)*n; 133 | } 134 | 135 | inline vec3 refract(const vec3& uv, const vec3& n, double etai_over_etat) { 136 | // uses snells law to calculate the refracted ray 137 | auto cos_theta = std::fmin(dot(-uv, n), 1.0); 138 | vec3 r_out_perp = etai_over_etat * (uv + cos_theta*n); 139 | vec3 r_out_parallel = -std::sqrt(std::fabs(1.0 - r_out_perp.length_squared())) * n; 140 | return r_out_perp + r_out_parallel; 141 | } 142 | 143 | inline vec3 random_in_unit_disk() { 144 | while (true) { 145 | auto p = vec3(random_double(-1,1), random_double(-1,1), 0); 146 | if (p.length_squared() >= 1) continue; 147 | return p; 148 | } 149 | } 150 | 151 | #endif -------------------------------------------------------------------------------- /camera.h: -------------------------------------------------------------------------------- 1 | #ifndef CAMERA_H 2 | #define CAMERA_H 3 | 4 | #include "hittable.h" 5 | #include "material.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /* 13 | This class represents a camera in the scene. 14 | It constructs and dispatches rays into the world. 15 | Uses the results of the ray to construct the rendered image. 16 | */ 17 | class camera { 18 | public: 19 | double aspect_ratio = 1.0; // ratio of image width over height 20 | int image_width = 100; // rendered image width in pixel count 21 | int samples_per_pixel = 10; // number of samples per pixel 22 | int max_depth = 10; // max recursion depth 23 | 24 | double vfov = 90; // vertical field of view in degrees 25 | point3 lookfrom = point3(0,0,0); // point that camera is looking from 26 | point3 lookat = point3(0,0,-1); // point that camera is looking at 27 | vec3 vup = vec3(0,1,0); // cam-relative "up" direction 28 | 29 | double defocus_angle = 0; // variation angle of rays thru each pixek 30 | double focus_distance = 10; //distance from camera lookfrom pt to perfect focus 31 | 32 | void render(const hittable& world) { 33 | initialize(); 34 | auto start_time = std::chrono::high_resolution_clock::now(); 35 | 36 | std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n"; 37 | 38 | // buffer for threading output 39 | std::vector image_rows(image_height); 40 | 41 | // find number of threads/cores 42 | int thread_count = std::thread::hardware_concurrency(); 43 | if (thread_count == 0) thread_count = 4; 44 | std::clog << "Using " << thread_count << " threads\n"; 45 | std::vector threads; 46 | std::mutex mtx; 47 | 48 | int rows_per_thread = image_height / thread_count; 49 | 50 | //rending lamba func given a start and end row 51 | auto scanlines_remaining = std::make_shared>(image_height); 52 | 53 | // lambda function to be 'worked on' by each thread 54 | auto render_rows = [&](int start_row, int end_row) { 55 | for (int j = start_row; j < end_row; ++j) { 56 | std::ostringstream row_output; 57 | for (int i = 0; i < image_width; ++i) { 58 | colour pixel_colour(0, 0, 0); 59 | for (int s = 0; s < samples_per_pixel; ++s) { 60 | ray r = get_ray(i, j); 61 | pixel_colour += ray_colour(r, max_depth, world); 62 | } 63 | write_colour(row_output, pixel_samples_scale * pixel_colour); 64 | } 65 | 66 | // update buffer 67 | image_rows[j] = row_output.str(); 68 | 69 | // mutex on scanlines_remaining to log 70 | std::lock_guard lock(mtx); 71 | std::clog << "\rScanlines remaining: " << --(*scanlines_remaining) << ' ' << std::flush; 72 | } 73 | }; 74 | 75 | int curr_row = 0; 76 | for (int i = 0; i < thread_count; i++) { 77 | int start_row = curr_row; 78 | int end_row = curr_row + rows_per_thread; 79 | if (i == thread_count - 1) { 80 | end_row = image_height; 81 | } 82 | threads.push_back(std::thread(render_rows, start_row, end_row)); 83 | curr_row = end_row; 84 | } 85 | 86 | // join threads togethr 87 | for (auto& t : threads) { 88 | t.join(); 89 | } 90 | 91 | // dumping buffer held in mem to cout 92 | std::clog << "\nWriting image to cout...\n"; 93 | for (int j = 0; j < image_height; ++j) { 94 | std::cout << image_rows[j]; 95 | } 96 | 97 | auto end_time = std::chrono::high_resolution_clock::now(); 98 | std::chrono::duration elapsed_time = end_time - start_time; 99 | std::clog << "\nDone."; 100 | std::clog << "\nElapsed render time: " << elapsed_time.count() << " seconds\n"; 101 | std::clog << "\n"; 102 | 103 | } 104 | 105 | private: 106 | int image_height; // rendered image height 107 | double pixel_samples_scale; // colour scale factor for a sum of pizel samples 108 | point3 center; // cam center 109 | point3 pixel00_loc; // loc of pixel 0, 0 110 | vec3 pixel_delta_u; // offset to pixel to the right 111 | vec3 pixel_delta_v; // offset to pixel below 112 | vec3 u, v, w; // camera basis vectors 113 | vec3 defocus_disk_u, defocus_disk_v; // defocus disk basis vectors (horiz and vert) 114 | 115 | void initialize() { 116 | image_height = int(image_width / aspect_ratio); 117 | image_height = (image_height < 1) ? 1 : image_height; 118 | 119 | pixel_samples_scale = 1.0 / samples_per_pixel; 120 | 121 | center = lookfrom; 122 | 123 | // determine viewport dimensions. 124 | auto theta = degrees_to_radians(vfov); 125 | auto h = std::tan(theta/2); 126 | auto viewport_height = 2.0 * h * focus_distance; 127 | auto viewport_width = viewport_height * (double(image_width)/image_height); 128 | 129 | // calculateing the unit basis vectors for the camera coordinate system 130 | w = unit_vector(lookfrom - lookat); 131 | u = unit_vector(cross(vup, w)); 132 | v = cross(w, u); 133 | 134 | // calc the vectors across the horizontal and down the vertical viewport edges. 135 | auto viewport_u = viewport_width * u; // vector across viewport horiz edge 136 | auto viewport_v = viewport_height * -v; // vector down viewport vert edge 137 | 138 | // calc the horizontal and vertical delta vectors from pixel to pixel. 139 | pixel_delta_u = viewport_u / image_width; 140 | pixel_delta_v = viewport_v / image_height; 141 | 142 | // calc the location of the upper left pixel. 143 | auto viewport_upper_left = 144 | center - (focus_distance * w) - (0.5 * viewport_u) - (0.5 * viewport_v); 145 | pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v); 146 | 147 | auto defocus_radius = focus_distance * std::tan(degrees_to_radians(defocus_angle) / 2); 148 | defocus_disk_u = defocus_radius * u; 149 | defocus_disk_v = defocus_radius * v; 150 | } 151 | 152 | ray get_ray(int i, int j) const { 153 | // construct a camera ray originating from the defocus disk and directed at a randomly 154 | // sampled point around the pixel location i,j 155 | auto offset = sample_square(); 156 | auto pixel_sample = pixel00_loc 157 | + ((i + offset.x()) * pixel_delta_u) 158 | + ((j + offset.y()) * pixel_delta_v); 159 | 160 | auto ray_origin = (defocus_angle <= 0) ? center : defocus_disk_sample(); 161 | auto ray_direction = pixel_sample - ray_origin; 162 | return ray(ray_origin, ray_direction); 163 | } 164 | 165 | vec3 sample_square() const { 166 | return vec3(random_double() - 0.5, random_double() - 0.5, 0); 167 | } 168 | 169 | point3 defocus_disk_sample() const { 170 | auto p = random_in_unit_disk(); 171 | return center + (p[0] * defocus_disk_u) + (p[1] * defocus_disk_v); 172 | } 173 | 174 | colour ray_colour(const ray& r, int depth, const hittable& world) const { 175 | if (depth <= 0) { 176 | // max recursion depth exceeded 177 | return colour(0, 0, 0); 178 | } 179 | 180 | hit_record rec; 181 | 182 | // ignoring hits that are very close to zero i.e. removes shadow acne 183 | // https://digitalrune.github.io/DigitalRune-Documentation/html/3f4d959e-9c98-4a97-8d85-7a73c26145d7.htm 184 | if (world.hit(r, interval(0.001, infinity), rec)) { 185 | ray scattered; 186 | colour attenuation; 187 | if (rec.material_ptr->scatter(r, rec, attenuation, scattered)) 188 | return attenuation * ray_colour(scattered, depth-1, world); 189 | return colour(0,0,0); 190 | } 191 | 192 | vec3 unit_direction = unit_vector(r.direction()); 193 | auto a = 0.5*(unit_direction.y() + 1.0); 194 | return (1.0-a)*colour(1.0, 1.0, 1.0) + a*colour(0.5, 0.7, 1.0); 195 | } 196 | }; 197 | 198 | #endif --------------------------------------------------------------------------------