summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt6
-rw-r--r--hitlist.h73
-rw-r--r--main_simple_scanner.cpp173
-rw-r--r--object.h41
-rw-r--r--sphere.h81
-rw-r--r--viewport.h87
6 files changed, 291 insertions, 170 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3bd2edf..78eeed9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,9 +12,9 @@ set(CMAKE_VERBOSE_MAKEFILE on)
# main executable
-add_executable(rt main.cpp vec.h bitmap.h ray.h bitfont.h)
-add_executable(image_output main_image_output.cpp vec.h bitmap.h bitfont.h)
-add_executable(simple_scanner main_simple_scanner.cpp vec.h bitmap.h ray.h timer.h bitfont.h)
+add_executable(rt main.cpp vec.h bitmap.h ray.h bitfont.h hitlist.h object.h sphere.h viewport.h)
+add_executable(image_output main_image_output.cpp vec.h bitmap.h bitfont.h hitlist.h object.h sphere.h viewport.h)
+add_executable(simple_scanner main_simple_scanner.cpp vec.h bitmap.h ray.h timer.h bitfont.h hitlist.h object.h sphere.h viewport.h)
# googletest
diff --git a/hitlist.h b/hitlist.h
new file mode 100644
index 0000000..18a8226
--- /dev/null
+++ b/hitlist.h
@@ -0,0 +1,73 @@
+//
+// Created by Keuin on 2022/4/12.
+//
+
+#ifndef RT_HITLIST_H
+#define RT_HITLIST_H
+
+#include "viewport.h"
+#include "timer.h"
+#include "bitmap.h"
+#include "ray.h"
+#include "vec.h"
+#include "object.h"
+#include <cstdlib>
+#include <memory>
+#include <limits>
+#include <vector>
+#include <iostream>
+#include <cstdint>
+
+
+// A world
+class hitlist {
+ std::vector<std::shared_ptr<object>> objects;
+
+public:
+ hitlist() = default;
+
+ hitlist(hitlist &other) = delete; // do not copy the world
+
+ // Add an object to the world.
+ void add_object(std::shared_ptr<object> &&obj) {
+ objects.push_back(std::move(obj));
+ }
+
+ // Given a ray, compute the color.
+ pixel8b color(const ray3d &r) const {
+ // Detect hits
+ bool hit = false;
+ double hit_t = std::numeric_limits<double>::infinity();
+ std::shared_ptr<object> hit_obj;
+ // Check the nearest object we hit
+ for (const auto &obj: objects) {
+ double t_;
+ if (obj->hit(r, t_, 0.0) && t_ < hit_t) {
+ hit = true;
+ hit_t = t_;
+ hit_obj = obj;
+ }
+ }
+ if (hit) {
+ // normal vector on hit point
+ const auto nv = hit_obj->normal_vector(r.at(hit_t));
+// return obj->color();
+ // visualize normal vector at hit point
+ return pixel8b::from_normalized(nv);
+ }
+
+
+ // Does not hit anything. Get background color (infinity)
+ const auto u = (r.direction().y + 1.0) * 0.5;
+ return mix(
+ pixel8b::from_normalized(1.0, 1.0, 1.0),
+ pixel8b::from_normalized(0.5, 0.7, 1.0),
+ 1.0 - u,
+ u
+ );
+ }
+
+
+};
+
+#endif //RT_HITLIST_H
diff --git a/main_simple_scanner.cpp b/main_simple_scanner.cpp
index 7769c0f..6dadf30 100644
--- a/main_simple_scanner.cpp
+++ b/main_simple_scanner.cpp
@@ -5,192 +5,31 @@
#include <cstdint>
#include <iostream>
#include <vector>
-#include <limits>
#include <memory>
#include <cstdlib>
#include "vec.h"
-#include "ray.h"
-#include "bitmap.h"
#include "timer.h"
+#include "viewport.h"
+#include "hitlist.h"
+#include "sphere.h"
#define DEMO_BALL
-class object {
-public:
- // Will the given ray hit. Returns time t if hits in range [t1, t2].
- virtual bool hit(const ray3d &r, double &t, double t1, double t2) const = 0;
-
- // With t2 = infinity
- inline bool hit(const ray3d &r, double &t, double t1) const {
- return hit(r, t, t1, std::numeric_limits<double>::infinity());
- }
-
- // Given a point on the surface, returns the normalized outer normal vector on that point.
- virtual vec3d normal_vector(const vec3d &where) const = 0;
-
- // object color, currently not parameterized
- virtual pixel8b color() const = 0;
-
- // subclasses must have virtual destructors
- virtual ~object() = default;
-};
-
-class sphere : public object {
- vec3d center;
- double radius;
-
-public:
- sphere() = delete;
-
- sphere(const vec3d &center, double radius) : center(center), radius(radius) {}
-
- ~sphere() override = default;
-
- vec3d normal_vector(const vec3d &where) const override {
- // We assume the point is on surface, speeding up the normalization
- return (where - center) / radius;
- }
-
- bool hit(const ray3d &r, double &t, double t1, double t2) const override {
- // Ray: {Source, Direction, time}
- // Sphere: {Center, radius}
- // sphere hit formula: |Source + Direction * time - Center| = radius
- // |(Sx + Dx * t - Cx, Sy + Dy * t - Cy, Sz + Dz * t - Cz)| = radius
-
- const auto c2s = r.source() - center; // center to source
- // A = D dot D
- const double a = r.direction().mod2();
- // H = (S - C) dot D
- const auto h = dot(c2s, r.direction());
- // B = 2H ( not used in our optimized routine )
- // C = (S - C) dot (S - C) - radius^2
- const double c = c2s.mod2() - radius * radius;
- // 4delta = H^2 - AC
- // delta_q = H^2 - AC (quarter delta)
- const double delta_q = h * h - a * c;
-
- bool hit = false;
- if (delta_q >= 0) {
- // return the root in range [t1, t2]
- // t = ( -H +- sqrt{ delta_q } ) / A
- double root;
- root = (-h - sqrt(delta_q)) / a;
- if (root >= t1 && root <= t2) {
- hit = true;
- t = root;
- } else {
- root = (-h + sqrt(delta_q)) / a;
- if (root >= t1 && root <= t2) {
- hit = true;
- t = root;
- }
- }
- }
- return hit;
- }
-
- pixel8b color() const override {
- return pixel8b::from_normalized(1.0, 0.0, 0.0);
- }
-};
-
-// A world
-class hitlist {
- std::vector<std::shared_ptr<object>> objects;
-
-public:
- hitlist() = default;
-
- hitlist(hitlist &other) = delete; // do not copy the world
-
- // Add an object to the world.
- void add_object(std::shared_ptr<object> &&obj) {
- objects.push_back(std::move(obj));
- }
-
- // Given a ray, compute the color.
- pixel8b color(const ray3d &r) const {
- // Detect hits
- bool hit = false;
- double hit_t = std::numeric_limits<double>::infinity();
- std::shared_ptr<object> hit_obj;
- // Check the nearest object we hit
- for (const auto &obj: objects) {
- double t_;
- if (obj->hit(r, t_, 0.0) && t_ < hit_t) {
- hit = true;
- hit_t = t_;
- hit_obj = obj;
- }
- }
- if (hit) {
- // normal vector on hit point
- const auto nv = hit_obj->normal_vector(r.at(hit_t));
-// return obj->color();
- // visualize normal vector at hit point
- return pixel8b::from_normalized(nv);
- }
-
-
- // Does not hit anything. Get background color (infinity)
- const auto u = (r.direction().y + 1.0) * 0.5;
- return mix(
- pixel8b::from_normalized(1.0, 1.0, 1.0),
- pixel8b::from_normalized(0.5, 0.7, 1.0),
- 1.0 - u,
- u
- );
- }
-
-
-};
-
-class viewport {
- double half_width, half_height; // viewport size
- vec3d center; // coordinate of the viewport center point
-
-public:
- viewport() = delete;
-
- viewport(double width, double height, vec3d viewport_center) :
- half_width(width / 2.0), half_height(height / 2.0), center(viewport_center) {}
-
- // Generate the image seen on given viewpoint.
- bitmap8b render(const hitlist &world, vec3d viewpoint, uint16_t image_width, uint16_t image_height) const {
- bitmap8b image{image_width, image_height};
- const auto r = center - viewpoint;
- const int img_hw = image_width / 2, img_hh = image_height / 2;
- // iterate over every pixel on the image
- for (int j = -img_hh + 1; j <= img_hh; ++j) { // axis y, transformation is needed
- for (int i = -img_hw; i < img_hw; ++i) { // axis x
- const vec3d off{
- .x=1.0 * i / img_hw * half_width,
- .y=1.0 * j / img_hh * half_height,
- .z=0.0
- }; // offset on screen plane
- const auto dir = r + off; // direction vector from camera to current pixel on screen
- const ray3d ray{viewpoint, dir}; // from camera to pixel (on the viewport)
- const auto pixel = world.color(ray);
- image.set(i + img_hw, -j + img_hh, pixel);
- }
- }
- return image;
- }
-};
-
void generate_image(uint16_t image_width, uint16_t image_height, double viewport_width, double focal_length,
double sphere_z, double sphere_r, const std::string &caption = "", unsigned caption_scale = 1) {
double r = 1.0 * image_width / image_height;
viewport vp{viewport_width, viewport_width / r, vec3d{0, 0, -focal_length}};
hitlist world;
+ bias_ctx bias{false, 0};
world.add_object(std::make_shared<sphere>(
vec3d{0, -100.5, -1},
100)); // the earth
world.add_object(std::make_shared<sphere>(vec3d{0, 0, sphere_z}, sphere_r));
timer tm;
tm.start_measure();
- auto image = vp.render(world, vec3d::zero(), image_width, image_height); // camera position as the coordinate origin
+ auto image = vp.render(world, vec3d::zero(),
+ image_width, image_height, bias); // camera position as the coordinate origin
tm.stop_measure();
if (!caption.empty()) {
image.print(caption,
diff --git a/object.h b/object.h
new file mode 100644
index 0000000..b6e2bbd
--- /dev/null
+++ b/object.h
@@ -0,0 +1,41 @@
+//
+// Created by Keuin on 2022/4/12.
+//
+
+#ifndef RT_OBJECT_H
+#define RT_OBJECT_H
+
+#include "hitlist.h"
+#include "viewport.h"
+#include "timer.h"
+#include "bitmap.h"
+#include "ray.h"
+#include "vec.h"
+#include <cstdlib>
+#include <memory>
+#include <limits>
+#include <vector>
+#include <iostream>
+#include <cstdint>
+
+class object {
+public:
+ // Will the given ray hit. Returns time t if hits in range [t1, t2].
+ virtual bool hit(const ray3d &r, double &t, double t1, double t2) const = 0;
+
+ // With t2 = infinity
+ inline bool hit(const ray3d &r, double &t, double t1) const {
+ return hit(r, t, t1, std::numeric_limits<double>::infinity());
+ }
+
+ // Given a point on the surface, returns the normalized outer normal vector on that point.
+ virtual vec3d normal_vector(const vec3d &where) const = 0;
+
+ // object color, currently not parameterized
+ virtual pixel8b color() const = 0;
+
+ // subclasses must have virtual destructors
+ virtual ~object() = default;
+};
+
+#endif //RT_OBJECT_H
diff --git a/sphere.h b/sphere.h
new file mode 100644
index 0000000..f604b74
--- /dev/null
+++ b/sphere.h
@@ -0,0 +1,81 @@
+//
+// Created by Keuin on 2022/4/12.
+//
+
+#ifndef RT_SPHERE_H
+#define RT_SPHERE_H
+
+#include "object.h"
+#include "hitlist.h"
+#include "viewport.h"
+#include "timer.h"
+#include "bitmap.h"
+#include "ray.h"
+#include "vec.h"
+#include <cstdlib>
+#include <memory>
+#include <limits>
+#include <vector>
+#include <iostream>
+#include <cstdint>
+
+class sphere : public object {
+ vec3d center;
+ double radius;
+
+public:
+ sphere() = delete;
+
+ sphere(const vec3d &center, double radius) : center(center), radius(radius) {}
+
+ ~sphere() override = default;
+
+ vec3d normal_vector(const vec3d &where) const override {
+ // We assume the point is on surface, speeding up the normalization
+ return (where - center) / radius;
+ }
+
+ bool hit(const ray3d &r, double &t, double t1, double t2) const override {
+ // Ray: {Source, Direction, time}
+ // Sphere: {Center, radius}
+ // sphere hit formula: |Source + Direction * time - Center| = radius
+ // |(Sx + Dx * t - Cx, Sy + Dy * t - Cy, Sz + Dz * t - Cz)| = radius
+
+ const auto c2s = r.source() - center; // center to source
+ // A = D dot D
+ const double a = r.direction().mod2();
+ // H = (S - C) dot D
+ const auto h = dot(c2s, r.direction());
+ // B = 2H ( not used in our optimized routine )
+ // C = (S - C) dot (S - C) - radius^2
+ const double c = c2s.mod2() - radius * radius;
+ // 4delta = H^2 - AC
+ // delta_q = H^2 - AC (quarter delta)
+ const double delta_q = h * h - a * c;
+
+ bool hit = false;
+ if (delta_q >= 0) {
+ // return the root in range [t1, t2]
+ // t = ( -H +- sqrt{ delta_q } ) / A
+ double root;
+ root = (-h - sqrt(delta_q)) / a;
+ if (root >= t1 && root <= t2) {
+ hit = true;
+ t = root;
+ } else {
+ root = (-h + sqrt(delta_q)) / a;
+ if (root >= t1 && root <= t2) {
+ hit = true;
+ t = root;
+ }
+ }
+ }
+ return hit;
+ }
+
+ pixel8b color() const override {
+ return pixel8b::from_normalized(1.0, 0.0, 0.0);
+ }
+};
+
+#endif //RT_SPHERE_H
diff --git a/viewport.h b/viewport.h
new file mode 100644
index 0000000..41c86a2
--- /dev/null
+++ b/viewport.h
@@ -0,0 +1,87 @@
+//
+// Created by Keuin on 2022/4/12.
+//
+
+#ifndef RT_VIEWPORT_H
+#define RT_VIEWPORT_H
+
+#include "timer.h"
+#include "bitmap.h"
+#include "ray.h"
+#include "vec.h"
+#include "hitlist.h"
+#include <cstdlib>
+#include <memory>
+#include <limits>
+#include <vector>
+#include <iostream>
+#include <cstdint>
+#include <random>
+
+// bias context, used for placing sub-pixels
+class bias_ctx {
+ bool enabled; // put all together, eliminating a virtual function call
+ std::mt19937_64 mt;
+ std::uniform_real_distribution<double> uni{0.0, 1.0};
+
+public:
+ bias_ctx(bool enabled, uint64_t seed = 0UL) : enabled(enabled), mt(std::mt19937_64{seed}) {}
+
+ void operator()(double &bx, double &by) {
+ if (enabled) {
+ bx = uni(mt);
+ by = uni(mt);
+ } else {
+ bx = 0.0;
+ by = 0.0;
+ }
+ }
+};
+
+class viewport {
+ const double half_width, half_height; // viewport size
+ const vec3d center; // coordinate of the viewport center point
+
+public:
+ viewport() = delete;
+
+ viewport(double width, double height, vec3d viewport_center) :
+ half_width(width / 2.0), half_height(height / 2.0), center(viewport_center) {}
+
+ /**
+ * Generate the image seen on given viewpoint.
+ * @param bx bias on x axis (0.0 <= bx < 1.0)
+ * @param by bias on y axis (0.0 <= by < 1.0)
+ * @return
+ */
+ bitmap8b render(const hitlist &world, vec3d viewpoint,
+ uint16_t image_width, uint16_t image_height,
+ bias_ctx &bias) const {
+ bitmap8b image{image_width, image_height};
+ double bx, by;
+ const auto r = center - viewpoint;
+ const int img_hw = image_width / 2, img_hh = image_height / 2;
+ // iterate over every pixel on the image
+ for (int j = -img_hh + 1; j <= img_hh; ++j) { // axis y, transformation is needed
+ for (int i = -img_hw; i < img_hw; ++i) { // axis x
+ bias(bx, by); // get a random bias (bx, by) for sub-pixel sampling
+ assert(0 <= bx);
+ assert(0 <= by);
+ assert(bx < 1.0);
+ assert(by < 1.0);
+ const vec3d off{
+ .x=1.0 * i / img_hw * half_width + bx,
+ .y=1.0 * j / img_hh * half_height + by,
+ .z=0.0
+ }; // offset on screen plane
+ const auto dir = r + off; // direction vector from camera to current pixel on screen
+ const ray3d ray{viewpoint, dir}; // from camera to pixel (on the viewport)
+ const auto pixel = world.color(ray);
+ image.set(i + img_hw, -j + img_hh, pixel);
+ }
+ }
+ return image;
+ }
+};
+
+#endif //RT_VIEWPORT_H