diff --git a/final_Renders/final_render.png b/final_Renders/final_render.png new file mode 100644 index 0000000..2d3f83a Binary files /dev/null and b/final_Renders/final_render.png differ diff --git a/final_Renders/image3.png b/final_Renders/image3.png new file mode 100644 index 0000000..cb8fcd8 Binary files /dev/null and b/final_Renders/image3.png differ diff --git a/final_Renders/image4.png b/final_Renders/image4.png new file mode 100644 index 0000000..fb59130 Binary files /dev/null and b/final_Renders/image4.png differ diff --git a/include/camera.h b/include/camera.h new file mode 100644 index 0000000..c4b28ec --- /dev/null +++ b/include/camera.h @@ -0,0 +1,187 @@ +#ifndef CAMERA_H +#define CAMERA_H + +#include "hittable.h" +#include "material.h" +#include +#include + + +class camera { + public: + + double aspect_ratio = 1.0; + int image_width = 100; + int samples_per_pixel = 10; + int max_depth = 10; + + double vfov = 90; + point3 lookfrom = point3(0,0,0); + point3 lookat = point3(0,0,-1); + vec3 vup = vec3(0,1,0); + + + double defocus_angle = 0; + double focus_dist = 10; + +void render(const hittable& world) { + initialize(); + + std::vector framebuffer(image_width * image_height); + + int thread_count = std::thread::hardware_concurrency(); + if (thread_count == 0) thread_count = 8; // fallback + + std::vector threads; + int rows_per_thread = image_height / thread_count; + + auto render_rows = [&](int start_row, int end_row) { + + for (int j = start_row; j < end_row; j++) { + + for (int i = 0; i < image_width; i++) { + + color pixel_color(0,0,0); + + for (int sample = 0; sample < samples_per_pixel; sample++) { + ray r = get_ray(i, j); + pixel_color += ray_color(r, max_depth, world); + } + + framebuffer[j * image_width + i] = + pixel_samples_scale * pixel_color; + } + } + }; + + for (int t = 0; t < thread_count; t++) { + int start = t * rows_per_thread; + int end = (t == thread_count - 1) + ? image_height + : start + rows_per_thread; + + threads.emplace_back(render_rows, start, end); + } + + for (auto& t : threads) + t.join(); + + // Print image after rendering completes (single-threaded) + std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n"; + + for (int j = 0; j < image_height; j++) { + for (int i = 0; i < image_width; i++) { + write_color(std::cout, + framebuffer[j * image_width + i]); + } + } + + std::clog << "Done.\n"; +} + + + + + private: + /* Private Camera Variables Here */ + int image_height; // Rendered image height + point3 center; // Camera center + double pixel_samples_scale;// color scale factor for a sum of pixel samples + point3 pixel00_loc; // Location of pixel 0, 0 + vec3 pixel_delta_u; // Offset to pixel to the right + vec3 pixel_delta_v; // Offset to pixel below + vec3 u,v,w; + vec3 defocus_disk_u; // Defocus disk horizontal radius + vec3 defocus_disk_v; // Defocus disk vertical radius + + void initialize() { + image_height = int(image_width / aspect_ratio); + image_height = (image_height < 1) ? 1 : image_height; + + pixel_samples_scale = 1.0/ samples_per_pixel; + + center = lookfrom; + + + + // Determine viewport dimensions. + + auto theta = degrees_to_radians(vfov); + auto h = std::tan(theta/2); + auto viewport_height = 2 * h * focus_dist; + auto viewport_width = viewport_height * (double(image_width)/image_height); + + w = unit_vector(lookfrom - lookat); + u = unit_vector(cross(vup,w)); + v = cross(w,u); + + // Calculate the vectors across the horizontal and down the vertical viewport edges. + vec3 viewport_u = viewport_width*u; + vec3 viewport_v = viewport_height*-v; + + // Calculate the horizontal and vertical delta vectors from pixel to pixel. + pixel_delta_u = viewport_u / image_width; + pixel_delta_v = viewport_v / image_height; + + // Calculate the location of the upper left pixel. + auto viewport_upper_left = center - (focus_dist * w) - viewport_u/2 - viewport_v/2; + pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v); + + // Calculate the camera defocus disk basis vectors. + auto defocus_radius = focus_dist * std::tan(degrees_to_radians(defocus_angle / 2)); + defocus_disk_u = u * defocus_radius; + defocus_disk_v = v * defocus_radius; + + } + + ray get_ray(int i, int j) const { + // Construct a camera ray originating from the origin and directed at randomly sampled + // point around the pixel location i, j. + + auto offset = sample_square(); + auto pixel_sample = pixel00_loc + + ((i + offset.x()) * pixel_delta_u) + + ((j + offset.y()) * pixel_delta_v); + + auto ray_origin = (defocus_angle <= 0) ? center : defocus_disk_sample(); + auto ray_direction = pixel_sample - ray_origin; + + return ray(ray_origin, ray_direction); + } + + vec3 sample_square() const { + // Returns the vector to a random point in the [-.5,-.5]-[+.5,+.5] unit square. + return vec3(random_double() - 0.5, random_double() - 0.5, 0); + } + + point3 defocus_disk_sample() const { + // Returns a random point in the camera defocus disk. + auto p = random_in_unit_disk(); + return center + (p[0] * defocus_disk_u) + (p[1] * defocus_disk_v); + } + + + + color ray_color(const ray& r,int depth, const hittable& world) const { + if(depth<=0) + return color(0,0,0); + + hit_record rec; + + if (world.hit(r, interval(0.001, infinity), rec)) { + ray scattered; + color attenuation; + if (rec.mat->scatter(r, rec, attenuation, scattered)) + return attenuation * ray_color(scattered, depth-1, world); + return color(0,0,0); + } + + vec3 unit_direction = unit_vector(r.direction()); + auto a = 0.5*(unit_direction.y() + 1.0); + return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0); + } + +}; + +#endif + diff --git a/include/color.h b/include/color.h new file mode 100644 index 0000000..a3d9ab5 --- /dev/null +++ b/include/color.h @@ -0,0 +1,37 @@ +#ifndef COLOR_H +#define COLOR_H +#include "interval.h" + +#include "vec3.h" + +#include + +using color = vec3; +inline double linear_to_gamma(double linear_component) +{ + if (linear_component > 0) + return std::sqrt(linear_component); + + return 0; +} +inline void write_color(std::ostream& out, const color& pixel_color) { + auto r = pixel_color.x(); + auto g = pixel_color.y(); + auto b = pixel_color.z(); + + // Apply a linear to gamma transform for gamma 2 + r = linear_to_gamma(r); + g = linear_to_gamma(g); + b = linear_to_gamma(b); + + // Translate the [0,1] component values to the byte range [0,255]. + static const interval intensity(0.000, 0.999); + int rbyte = int(256 * intensity.clamp(r)); + int gbyte = int(256 * intensity.clamp(g)); + int bbyte = int(256 * intensity.clamp(b)); + + // Write out the pixel color components. + out << rbyte << ' ' << gbyte << ' ' << bbyte << '\n'; +} + +#endif diff --git a/include/hittable.h b/include/hittable.h new file mode 100644 index 0000000..e22ff06 --- /dev/null +++ b/include/hittable.h @@ -0,0 +1,33 @@ +#ifndef HITTABLE_H +#define HITTABLE_H + +#include "ray.h" + +class material; + +class hit_record { + public: + point3 p; + vec3 normal; + double t; + shared_ptr mat; + bool front_face; + void set_face_normal(const ray& r, const vec3& outward_normal){ + // Sets the hit record normal vector. + // NOTE: the parameter `outward_normal` is assumed to have unit length. + + front_face = dot(r.direction(), outward_normal) < 0; + normal = front_face ? outward_normal : -outward_normal; + } + +}; + +class hittable { + public: + virtual ~hittable() = default; + + + virtual bool hit(const ray& r, interval ray_t, hit_record& rec) const = 0; +}; + +#endif diff --git a/include/hittable_list.h b/include/hittable_list.h new file mode 100644 index 0000000..5345da3 --- /dev/null +++ b/include/hittable_list.h @@ -0,0 +1,42 @@ +#ifndef HITTABLE_LIST_H +#define HITTABLE_LIST_H + +#include "hittable.h" + +#include +#include + +using std::make_shared; +using std::shared_ptr; + +class hittable_list : public hittable { + public: + std::vector> objects; + + hittable_list() {} + hittable_list(shared_ptr object) { add(object); } + + void clear() { objects.clear(); } + + void add(shared_ptr object) { + objects.push_back(object); + } + + bool hit(const ray& r, interval ray_t, hit_record& rec) const override { + hit_record temp_rec; + bool hit_anything = false; + auto closest_so_far = ray_t.max; + + for (const auto& object : objects) { + if (object->hit(r, interval(ray_t.min, closest_so_far), temp_rec)){ + hit_anything = true; + closest_so_far = temp_rec.t; + rec = temp_rec; + } + } + + return hit_anything; + } +}; + +#endif diff --git a/include/interval.h b/include/interval.h new file mode 100644 index 0000000..2e6fdd4 --- /dev/null +++ b/include/interval.h @@ -0,0 +1,37 @@ +#ifndef INTERVAL_H +#define INTERVAL_H + +class interval { + public: + double min, max; + + interval() : min(+infinity), max(-infinity) {} // Default interval is empty + + interval(double min, double max) : min(min), max(max) {} + + double size() const { + return max - min; + } + + bool contains(double x) const { + return min <= x && x <= max; + } + + bool surrounds(double x) const { + return min < x && x < max; + } + + + double clamp(double x) const { + if (x < min) return min; + if (x > max) return max; + return x; + } + + static const interval empty, universe; +}; + +const interval interval::empty = interval(+infinity, -infinity); +const interval interval::universe = interval(-infinity, +infinity); + +#endif diff --git a/include/material.h b/include/material.h new file mode 100644 index 0000000..b7a63dd --- /dev/null +++ b/include/material.h @@ -0,0 +1,96 @@ +#ifndef MATERIAL_H +#define MATERIAL_H + +#include "hittable.h" +#include "vec3.h" + +class material { + public: + virtual ~material() = default; + + virtual bool scatter( + const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered + ) const { + return false; + } +}; + + +class lambertian : public material { + public: + lambertian(const color& albedo) : albedo(albedo) {} + + bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered) + const override { + auto scatter_direction = rec.normal + random_unit_vector(); + // Catch degenerate scatter direction + if (scatter_direction.near_zero()) + scatter_direction = rec.normal; + + scattered = ray(rec.p, scatter_direction); + attenuation = albedo; + return true; + } + + private: + color albedo; +}; + + +class metal : public material { + public: + metal(const color& albedo, double fuzz) : albedo(albedo), fuzz(fuzz<1? fuzz : 1) {} + + bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered) + const override { + vec3 reflected = reflect(r_in.direction(), rec.normal); + reflected = unit_vector(reflected) + (fuzz*random_unit_vector()); + scattered = ray(rec.p, reflected); + attenuation = albedo; + return (dot(scattered.direction(), rec.normal)>0); + } + + private: + color albedo; + double fuzz; +}; + +class dielectric : public material { + public: + dielectric(double refraction_index) : refraction_index(refraction_index) {} + + bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered) + const override { + attenuation = color(1.0, 1.0, 1.0); + double ri = rec.front_face ? (1.0/refraction_index) : refraction_index; + + vec3 unit_direction = unit_vector(r_in.direction()); + double cos_theta = std::fmin(dot(-unit_direction, rec.normal), 1.0); + double sin_theta = std::sqrt(1.0 - cos_theta*cos_theta); + + bool cannot_refract = ri * sin_theta > 1.0; + vec3 direction; + + if (cannot_refract || reflectance(cos_theta, ri)> random_double()) + direction = reflect(unit_direction, rec.normal); + else + direction = refract(unit_direction, rec.normal, ri); + + scattered = ray(rec.p, direction); + return true; + } + + private: + // Refractive index in vacuum or air, or the ratio of the material's refractive index over + // the refractive index of the enclosing media + double refraction_index; + static double reflectance(double cosine, double refraction_index) { + // Use Schlick's approximation for reflectance. + auto r0 = (1 - refraction_index) / (1 + refraction_index); + r0 = r0*r0; + return r0 + (1-r0)*std::pow((1 - cosine),5); + } + +}; + +#endif diff --git a/include/ray.h b/include/ray.h new file mode 100644 index 0000000..14db1d9 --- /dev/null +++ b/include/ray.h @@ -0,0 +1,24 @@ +#ifndef RAY_H +#define RAY_H + +#include "vec3.h" + +class ray { + public: + ray() {} + + ray(const point3& origin, const vec3& direction) : orig(origin), dir(direction) {} + + const point3& origin() const { return orig; } + const vec3& direction() const { return dir; } + + point3 at(double t) const { + return orig + t*dir; + } + + private: + point3 orig; + vec3 dir; +}; + +#endif diff --git a/include/rtweekend.h b/include/rtweekend.h new file mode 100644 index 0000000..16a310d --- /dev/null +++ b/include/rtweekend.h @@ -0,0 +1,45 @@ +#ifndef RTWEEKEND_H +#define RTWEEKEND_H + +#include +#include +#include +#include +#include + + +// C++ Std Usings + +using std::make_shared; +using std::shared_ptr; + +// Constants + +const double infinity = std::numeric_limits::infinity(); +const double pi = 3.1415926535897932385; + +// Utility Functions + +inline double degrees_to_radians(double degrees) { + return degrees * pi / 180.0; +} +inline double random_double() { + thread_local static std::mt19937 generator(std::random_device{}()); + thread_local static std::uniform_real_distribution distribution(0.0, 1.0); + return distribution(generator); +} + + +inline double random_double(double min, double max) { + // Returns a random real in [min,max). + return min + (max-min)*random_double(); +} + +// Common Headers + +#include "color.h" +#include "interval.h" +#include "ray.h" +#include "vec3.h" + +#endif diff --git a/include/sphere.h b/include/sphere.h new file mode 100644 index 0000000..40e4728 --- /dev/null +++ b/include/sphere.h @@ -0,0 +1,46 @@ +#ifndef SPHERE_H +#define SPHERE_H + +#include "hittable.h" +#include "vec3.h" + +class sphere : public hittable { + public: + sphere(const point3& center, double radius, shared_ptr mat) : center(center), radius(std::fmax(0,radius)), mat(mat) {} + + bool hit(const ray& r, interval ray_t, hit_record& rec) const override { + vec3 oc = center - r.origin(); + auto a = r.direction().length_squared(); + auto h = dot(r.direction(), oc); + auto c = oc.length_squared() - radius*radius; + + auto discriminant = h*h - a*c; + if (discriminant < 0) + return false; + + auto sqrtd = std::sqrt(discriminant); + + // Find the nearest root that lies in the acceptable range. + auto root = (h - sqrtd) / a; + if (!ray_t.surrounds(root)){ + root = (h + sqrtd) / a; + if (!ray_t.surrounds(root)) + return false; + } + + rec.t = root; + rec.p = r.at(rec.t); + vec3 outward_normal = (rec.p - center) / radius; + rec.set_face_normal(r, outward_normal); + rec.mat = mat; + + return true; + } + + private: + point3 center; + double radius; + shared_ptrmat; +}; + +#endif diff --git a/include/vec3.h b/include/vec3.h new file mode 100644 index 0000000..6ae41b9 --- /dev/null +++ b/include/vec3.h @@ -0,0 +1,149 @@ +#ifndef VEC3_H +#define VEC3_H + +#include +#include + +class vec3 { + public: + double e[3]; + + vec3() : e{0,0,0} {} + vec3(double e0, double e1, double e2) : e{e0, e1, e2} {} + + double x() const { return e[0]; } + double y() const { return e[1]; } + double z() const { return e[2]; } + + vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); } + double operator[](int i) const { return e[i]; } + double& operator[](int i) { return e[i]; } + + vec3& operator+=(const vec3& v) { + e[0] += v.e[0]; + e[1] += v.e[1]; + e[2] += v.e[2]; + return *this; + } + + vec3& operator*=(double t) { + e[0] *= t; + e[1] *= t; + e[2] *= t; + return *this; + } + + vec3& operator/=(double t) { + return *this *= 1/t; + } + + double length() const { + return std::sqrt(length_squared()); + } + + double length_squared() const { + return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; + } + + + bool near_zero() const { + // Return true if the vector is close to zero in all dimensions. + auto s = 1e-8; + return (std::fabs(e[0]) < s) && (std::fabs(e[1]) < s) && (std::fabs(e[2]) < s); + } + + static vec3 random() { + return vec3(random_double(), random_double(), random_double()); + } + + static vec3 random(double min, double max) { + return vec3(random_double(min,max), random_double(min,max), random_double(min,max)); + } +}; + +// point3 is just an alias for vec3, but useful for geometric clarity in the code. +using point3 = vec3; + + +// Vector Utility Functions + +inline std::ostream& operator<<(std::ostream& out, const vec3& v) { + return out << v.e[0] << ' ' << v.e[1] << ' ' << v.e[2]; +} + +inline vec3 operator+(const vec3& u, const vec3& v) { + return vec3(u.e[0] + v.e[0], u.e[1] + v.e[1], u.e[2] + v.e[2]); +} + +inline vec3 operator-(const vec3& u, const vec3& v) { + return vec3(u.e[0] - v.e[0], u.e[1] - v.e[1], u.e[2] - v.e[2]); +} + +inline vec3 operator*(const vec3& u, const vec3& v) { + return vec3(u.e[0] * v.e[0], u.e[1] * v.e[1], u.e[2] * v.e[2]); +} + +inline vec3 operator*(double t, const vec3& v) { + return vec3(t*v.e[0], t*v.e[1], t*v.e[2]); +} + +inline vec3 operator*(const vec3& v, double t) { + return t * v; +} + +inline vec3 operator/(const vec3& v, double t) { + return (1/t) * v; +} + +inline double dot(const vec3& u, const vec3& v) { + return u.e[0] * v.e[0] + + u.e[1] * v.e[1] + + u.e[2] * v.e[2]; +} + +inline vec3 cross(const vec3& u, const vec3& v) { + return vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1], + u.e[2] * v.e[0] - u.e[0] * v.e[2], + u.e[0] * v.e[1] - u.e[1] * v.e[0]); +} + +inline vec3 unit_vector(const vec3& v) { + return v / v.length(); +} + + +inline vec3 random_in_unit_disk() { + while (true) { + auto p = vec3(random_double(-1,1), random_double(-1,1), 0); + if (p.length_squared() < 1) + return p; + } +} + +inline vec3 random_unit_vector() { + while (true) { + auto p = vec3::random(-1,1); + auto lensq = p.length_squared(); + if (1e-160 < lensq && lensq <= 1) + return p / sqrt(lensq); + } +} + +inline vec3 random_on_hemisphere(const vec3& normal) { + vec3 on_unit_sphere = random_unit_vector(); + if (dot(on_unit_sphere, normal) > 0.0) // In the same hemisphere as the normal + return on_unit_sphere; + else + return -on_unit_sphere; +} + +inline vec3 reflect(const vec3& v, const vec3& n) { + return v - 2*dot(v,n)*n; +} +inline vec3 refract(const vec3& uv, const vec3& n, double etai_over_etat){ + auto cos_theta = std::fmin(dot(-uv,n),1.0); + vec3 r_out_perp = etai_over_etat*(uv+cos_theta*n); + vec3 r_out_parallel = -std::sqrt(std::fabs(1.0 - r_out_perp.length_squared())) * n; + return r_out_perp + r_out_parallel; +} +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5db9489 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,69 @@ +#include +#include +#include "hittable.h" +#include "hittable_list.h" +#include "sphere.h" + + +int main() { + + hittable_list world; + + auto ground_material = make_shared(color(0.5, 0.5, 0.5)); + world.add(make_shared(point3(0,-1000,0), 1000, ground_material)); + + for (int a = -11; a < 11; a++) { + for (int b = -11; b < 11; b++) { + auto choose_mat = random_double(); + point3 center(a + 0.9*random_double(), 0.2, b + 0.9*random_double()); + + if ((center - point3(4, 0.2, 0)).length() > 0.9) { + shared_ptr sphere_material; + + if (choose_mat < 0.8) { + // diffuse + auto albedo = color::random() * color::random(); + sphere_material = make_shared(albedo); + world.add(make_shared(center, 0.2, sphere_material)); + } else if (choose_mat < 0.95) { + // metal + auto albedo = color::random(0.5, 1); + auto fuzz = random_double(0, 0.5); + sphere_material = make_shared(albedo, fuzz); + world.add(make_shared(center, 0.2, sphere_material)); + } else { + // glass + sphere_material = make_shared(1.5); + world.add(make_shared(center, 0.2, sphere_material)); + } + } + } + } + + auto material1 = make_shared(1.5); + world.add(make_shared(point3(0, 1, 0), 1.0, material1)); + + auto material2 = make_shared(color(0.4, 0.2, 0.1)); + world.add(make_shared(point3(-4, 1, 0), 1.0, material2)); + + auto material3 = make_shared(color(0.7, 0.6, 0.5), 0.0); + world.add(make_shared(point3(4, 1, 0), 1.0, material3)); + + camera cam; + + cam.aspect_ratio = 16.0 / 9.0; + cam.image_width = 1200; + cam.samples_per_pixel = 500; + cam.max_depth = 50; + + cam.vfov = 20; + cam.lookfrom = point3(13,2,3); + cam.lookat = point3(0,0,0); + cam.vup = vec3(0,1,0); + + cam.defocus_angle = 0.6; + cam.focus_dist = 10.0; + + + cam.render(world); +}