diff --git a/rtiww/CMakeLists.txt b/rtiww/CMakeLists.txt index c1c9b96..a3ccf6f 100644 --- a/rtiww/CMakeLists.txt +++ b/rtiww/CMakeLists.txt @@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD 17) add_executable(rtiww src/main.cpp src/vec3.h src/ray.h src/color.h + src/material.h src/hittable.h src/hittable_list.h src/sphere.h src/camera.h diff --git a/rtiww/src/color.h b/rtiww/src/color.h index 7b2038d..83abe01 100644 --- a/rtiww/src/color.h +++ b/rtiww/src/color.h @@ -52,4 +52,20 @@ private: } }; +void write_color(std::ostream &out, color pixel_color, + int samples_per_pixel) { + auto r = pixel_color.x(); + auto g = pixel_color.y(); + auto b = pixel_color.z(); + + auto scale = 1.0 / samples_per_pixel; + r = sqrt(scale * r); + g = sqrt(scale * g); + b = sqrt(scale * b); + + out << static_cast(255.999 * clamp(r, 0.0, 0.999)) << ' ' + << static_cast(255.999 * clamp(g, 0.0, 0.999)) << ' ' + << static_cast(255.999 * clamp(b, 0.0, 0.999)) << '\n'; +} + #endif // RTIWW_COLOR_H diff --git a/rtiww/src/hittable.h b/rtiww/src/hittable.h index 90140f3..8ef632d 100644 --- a/rtiww/src/hittable.h +++ b/rtiww/src/hittable.h @@ -2,18 +2,23 @@ #define HITTABLE_H_ #include "ray.h" +#include "rtweekend.h" #include "vec3.h" +#include + +class material; struct hit_record { point3 p; vec3 normal; + std::shared_ptr mat_ptr; double t; bool front_face; - inline void set_face_normal(const ray& r, const vec3& outward_normal) { - front_face = dot(r.direction(), outward_normal) < 0; - normal = front_face ? outward_normal: -outward_normal; - } + inline void set_face_normal(const ray &r, const vec3 &outward_normal) { + front_face = dot(r.direction(), outward_normal) < 0; + normal = front_face ? outward_normal : -outward_normal; + } }; class hittable { diff --git a/rtiww/src/main.cpp b/rtiww/src/main.cpp index 0af61bf..11e8e85 100644 --- a/rtiww/src/main.cpp +++ b/rtiww/src/main.cpp @@ -4,66 +4,79 @@ #include "color.h" #include "hittable_list.h" +#include "material.h" #include "sphere.h" #include "vec3.h" #include +#include color ray_color(const ray &r, const hittable &world, int depth) { - hit_record rec; - if (depth <= 0) - return color(0, 0, 0); + hit_record rec; - if (world.hit(r, 0.001, infinity, rec)) { - point3 target = rec.p + random_in_hemisphere(rec.normal); - return 0.5 * ray_color(ray(rec.p, target - rec.p), world, depth - 1); - } + // If we've exceeded the ray bounce limit, no more light is gathered. + if (depth <= 0) + return color(0,0,0); - vec3 unit_direction = unit_vector(r.direction()); - auto t = 0.5 * (unit_direction.y() + 1.0); - return (1.0 - t) * color(1.0, 1.0, 1.0) + t * color(0.5, 0.7, 1.0); + if (world.hit(r, 0.000001, infinity, rec)) { + ray scattered; + color attenuation; + if (rec.mat_ptr->scatter(r, rec, attenuation, scattered)) + return attenuation * ray_color(scattered, world, depth-1); + return color(0,0,0); + } + + vec3 unit_direction = unit_vector(r.direction()); + auto t = 0.5*(unit_direction.y() + 1.0); + return (1.0-t)*color(1.0, 1.0, 1.0) + t*color(0.5, 0.7, 1.0); } int main() { - // Image - const auto aspect_ratio = 16.0 / 9.0; - const int image_width = 400; - const int image_height = static_cast(image_width / aspect_ratio); - const int samples_per_pixel = 100; - const int max_depth = 50; + // Image - // World - hittable_list world; - world.add(make_shared(point3(0, 0, -1), 0.5)); - world.add(make_shared(point3(0, -100.5, -1), 100)); + const auto aspect_ratio = 16.0 / 9.0; + const int image_width = 400; + const int image_height = static_cast(image_width / aspect_ratio); + const int samples_per_pixel = 100; + const int max_depth = 50; - // Camera - camera cam; + // World - // Render - std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n"; + hittable_list world; - picture p; + auto material_ground = make_shared(color(0.8, 0.8, 0.0)); + auto material_center = make_shared(color(0.7, 0.3, 0.3)); + auto material_left = make_shared(color(0.8, 0.8, 0.8)); + auto material_right = make_shared(color(0.8, 0.6, 0.2)); - for (int j = image_height - 1; j >= 0; --j) { - for (int i = 0; i < image_width; ++i) { - color pixel_color(0, 0, 0); + world.add(make_shared(point3( 0.0, -100.5, -1.0), 100.0, material_ground)); + world.add(make_shared(point3( 0.0, 0.0, -1.0), 0.5, material_center)); + world.add(make_shared(point3(-1.0, 0.0, -1.0), 0.5, material_left)); + world.add(make_shared(point3( 1.0, 0.0, -1.0), 0.5, material_right)); - for (int s = 0; s < samples_per_pixel; ++s) { - auto u = (i + random_double()) / (image_width - 1); - auto v = (j + random_double()) / (image_height - 1); + // Camera - ray r = cam.get_ray(u, v); - pixel_color += ray_color(r, world, max_depth); - } + camera cam; - p.set_pixel(j, i, pixel_color); + // Render + + std::cout << "P3\n" << image_width << " " << image_height << "\n255\n"; + + for (int j = image_height-1; j >= 0; --j) { + std::cerr << "\rScanlines remaining: " << j << ' ' << std::flush; + for (int i = 0; i < image_width; ++i) { + color pixel_color(0, 0, 0); + for (int s = 0; s < samples_per_pixel; ++s) { + auto u = (i + random_double()) / (image_width-1); + auto v = (j + random_double()) / (image_height-1); + ray r = cam.get_ray(u, v); + pixel_color += ray_color(r, world, max_depth); + } + write_color(std::cout, pixel_color, samples_per_pixel); + } } - } - - p.write(std::cout, image_height, image_width, samples_per_pixel); - std::cerr << "\nDone.\n"; + std::cerr << "\nDone.\n"; return 0; } diff --git a/rtiww/src/material.h b/rtiww/src/material.h new file mode 100644 index 0000000..34a92c5 --- /dev/null +++ b/rtiww/src/material.h @@ -0,0 +1,57 @@ +#ifndef MATERIAL_H_ +#define MATERIAL_H_ + +#include "hittable.h" +#include "rtweekend.h" +#include "vec3.h" + +struct hit_record; + +class material { +public: + virtual bool scatter(const ray &r_in, const hit_record &rec, + color &attenuation, ray &scattered) const = 0; +}; + + + +class lambertian : public material { +public: + lambertian(const color& a) : albedo(a) {} + + virtual bool scatter( + const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered + ) const override { + auto scatter_direction = rec.normal+random_in_unit_sphere(); + + // Catch degenerate scatter direction + if (scatter_direction.near_zero()) + scatter_direction = rec.normal; + + scattered = ray(rec.p, scatter_direction); + attenuation = albedo; + return true; + } + +public: + color albedo; +}; + +class metal : public material { +public: + metal(const color& a) : albedo(a) {} + + virtual bool scatter( + const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered + ) const override { + vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal); + scattered = ray(rec.p, reflected); + attenuation = albedo; + return true; + } + +public: + color albedo; +}; + +#endif // MATERIAL_H_ diff --git a/rtiww/src/sphere.h b/rtiww/src/sphere.h index 4f2705c..d854a16 100644 --- a/rtiww/src/sphere.h +++ b/rtiww/src/sphere.h @@ -2,41 +2,50 @@ #define SPHERE_H_ #include "hittable.h" +#include "material.h" -class sphere: public hittable { +class sphere : public hittable { public: sphere() {} - sphere(point3 cen, double r): center(cen), radius(r) {}; - virtual bool hit(const ray& r, double t_min, double t_max, hit_record& rec) const override; + sphere(point3 cen, double r, shared_ptr m) + : center(cen), radius(r), mat_ptr(m) {}; + + virtual bool hit( + const ray& r, double t_min, double t_max, hit_record& rec) const override; public: point3 center; double radius; + shared_ptr mat_ptr; }; -bool sphere::hit(const ray& r, double t_min, double t_max, hit_record& rec) const { - vec3 oc = r.origin() - center; - auto a = r.direction().length_squared(); - auto half_b = dot(oc, r.direction()); - auto c = oc.length_squared() - radius*radius; +bool sphere::hit(const ray &r, double t_min, double t_max, + hit_record &rec) const { + vec3 oc = r.origin() - center; + auto a = r.direction().length_squared(); + auto half_b = dot(oc, r.direction()); + auto c = oc.length_squared() - radius * radius; - auto discriminant = half_b*half_b - a*c; - if(discriminant < 0) return false; - auto sqrtd = sqrt(discriminant); + auto discriminant = half_b * half_b - a * c; + if (discriminant < 0) + return false; + auto sqrtd = sqrt(discriminant); - // Find the nearest root that lies in the acceptable range - auto root = (-half_b - sqrtd) / a; - if(root < t_min || t_max