summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt5
-rw-r--r--bitmap.h41
-rw-r--r--main_simple_scanner.cpp60
-rw-r--r--ray.h36
-rw-r--r--timer.h27
5 files changed, 165 insertions, 4 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b822baa..b5a00d4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,12 +12,13 @@ set(CMAKE_VERBOSE_MAKEFILE on)
# main executable
-add_executable(rt main.cpp vec.h bitmap.h)
+add_executable(rt main.cpp vec.h bitmap.h ray.h)
add_executable(image_output main_image_output.cpp vec.h bitmap.h)
+add_executable(simple_scanner main_simple_scanner.cpp vec.h bitmap.h ray.h timer.h)
# googletest
-add_executable(all_tests test.cpp vec.h bitmap.h)
+add_executable(all_tests test.cpp vec.h bitmap.h ray.h)
target_link_libraries(all_tests gtest_main)
include(FetchContent)
diff --git a/bitmap.h b/bitmap.h
index b94bb25..31b9053 100644
--- a/bitmap.h
+++ b/bitmap.h
@@ -10,13 +10,48 @@
#include <ostream>
#include <cassert>
+#define COLORMIX_OVERFLOW_CHECK
+
+//// T is some unsigned integer
+//template<typename T>
+//T saturate_add(T a, T b) {
+// T c = a + b;
+// if (a > c || b > c) {
+// c = (1ULL << (sizeof(T) * 8U)) - 1ULL;
+// }
+// return c;
+//}
// T is some unsigned integer, do not use float point types!
template<typename T>
struct pixel {
T r, g, b;
+
+ /**
+ * Create a pixel with given depth, from normalized color values.
+ * For example: for 8bit pixel, with (1, 0.5, 0.25), we get: (255, 127, 63).
+ */
+ static pixel<T> from_normalized(double r, double g, double b) {
+ const auto mod = (1ULL << (sizeof(T) * 8U)) - 1ULL;
+ return pixel<T>{.r = (T) (mod * r), .g = (T) (mod * g), .b = (T) (mod * b)};
+ }
};
+// Mix two colors a and b. Returns a*u + b*v
+template<typename T>
+inline pixel<T> mix(const pixel<T> &a, const pixel<T> &b, double u, double v) {
+ assert(u >= 0);
+ assert(v >= 0);
+ assert(u <= 1);
+ assert(v <= 1);
+ assert(u + v <= 1);
+ pixel<T> c{0, 0, 0};
+ c.r = (T) (u * a.r + v * b.r);
+ c.g = (T) (u * a.g + v * b.g);
+ c.b = (T) (u * a.b + v * b.b);
+ return c;
+}
+
// 8 bit pixel
using pixel8b = pixel<uint8_t>;
@@ -28,16 +63,18 @@ class bitmap {
pixel<T> &image(unsigned x, unsigned y) {
assert(x < width);
assert(y < height);
- return content[x * width + y];
+ return content[x + y * width];
}
pixel<T> &image(unsigned x, unsigned y) const {
assert(x < width);
assert(y < height);
- return content[x * width + y];
+ return content[x + y * width];
}
public:
+ bitmap() = delete;
+
bitmap(unsigned int width, unsigned int height) : width(width), height(height) {
content.resize(width * height, pixel<T>{});
}
diff --git a/main_simple_scanner.cpp b/main_simple_scanner.cpp
new file mode 100644
index 0000000..a1d75f7
--- /dev/null
+++ b/main_simple_scanner.cpp
@@ -0,0 +1,60 @@
+//
+// Created by Keuin on 2022/4/11.
+//
+
+#include <cstdint>
+#include <iostream>
+
+#include "vec.h"
+#include "ray.h"
+#include "bitmap.h"
+#include "timer.h"
+
+
+class viewport {
+ uint16_t width, height;
+ vec3d center;
+ bitmap8b bitmap_{width, height};
+
+ static pixel8b color(const ray3d &r) {
+ 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
+ );
+ }
+
+public:
+ viewport() = delete;
+
+ viewport(uint16_t width, uint16_t height, vec3d viewport_center) :
+ width(width), height(height), center(viewport_center) {}
+
+ void render(vec3d viewpoint) {
+ const auto r = center - viewpoint;
+ const int half_width = width / 2, half_height = height / 2;
+ for (int j = -half_height; j < half_height; ++j) {
+ for (int i = -half_width; i < half_width; ++i) {
+ const auto dir = r + vec3d{static_cast<double>(i), static_cast<double>(j), 0};
+ const ray3d ray{viewpoint, dir}; // from camera to pixel (on the viewport)
+ const auto pixel = color(ray);
+ bitmap_.set(i + half_width, j + half_height, pixel);
+ }
+ }
+ }
+
+ const bitmap8b &bitmap() const {
+ return bitmap_;
+ }
+};
+
+int main() {
+ viewport vp{1920, 1080, vec3d{0, 0, -100}};
+ timer tm;
+ tm.start_measure();
+ vp.render(vec3d{0, 0, 0}); // camera position as the coordinate origin
+ tm.stop_measure();
+ vp.bitmap().write_plain_ppm(std::cout);
+} \ No newline at end of file
diff --git a/ray.h b/ray.h
new file mode 100644
index 0000000..3281c6e
--- /dev/null
+++ b/ray.h
@@ -0,0 +1,36 @@
+//
+// Created by Keuin on 2022/4/11.
+//
+
+#ifndef RT_RAY_H
+#define RT_RAY_H
+
+#include "vec.h"
+
+// A ray is a half-line, starts from a 3d point, along the direction of a unit vector, to the infinity
+template<typename T>
+class ray3 {
+ const vec3<T> source_;
+ const vec3<T> direction_; // unit vector
+
+public:
+ ray3() = delete;
+ ray3(const vec3<T> &source, const vec3<T> &direction) : source_(source), direction_(direction.unit_vec()) {}
+
+ vec3<T> source() const {
+ return source_;
+ }
+
+ vec3<T> direction() const {
+ return direction_;
+ }
+
+ template<typename U>
+ vec3<T> at(U t) const {
+ return source_ + direction_ * t;
+ }
+};
+
+using ray3d = ray3<double>;
+
+#endif //RT_RAY_H
diff --git a/timer.h b/timer.h
new file mode 100644
index 0000000..1f100cd
--- /dev/null
+++ b/timer.h
@@ -0,0 +1,27 @@
+//
+// Created by Keuin on 2022/4/11.
+//
+
+#ifndef RT_TIMER_H
+#define RT_TIMER_H
+
+#include <iostream>
+#include <chrono>
+
+class timer {
+private:
+ typeof(std::chrono::system_clock::now()) start_time;
+public:
+ void start_measure() {
+ fprintf(stderr, "Start timing...\n");
+ start_time = std::chrono::system_clock::now();
+ }
+ void stop_measure() {
+ const auto end = std::chrono::system_clock::now();
+ const auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start_time);
+ const auto secs = 1e-3 * duration.count() * std::chrono::microseconds::period::num / std::chrono::microseconds::period::den;
+ fprintf(stderr, "Stop timing. Duration: %fs\n", secs);
+ }
+};
+
+#endif //RT_TIMER_H