diff options
-rw-r--r-- | CMakeLists.txt | 5 | ||||
-rw-r--r-- | bitmap.h | 41 | ||||
-rw-r--r-- | main_simple_scanner.cpp | 60 | ||||
-rw-r--r-- | ray.h | 36 | ||||
-rw-r--r-- | timer.h | 27 |
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) @@ -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 @@ -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 @@ -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 |