finished 6.7

This commit is contained in:
Moritz Gmeiner 2023-12-27 17:23:35 +01:00
commit 40951c3bbd
13 changed files with 188970 additions and 188753 deletions

377328
image.ppm

File diff suppressed because it is too large Load diff

View file

@ -17,4 +17,4 @@ subdir('src')
inc = include_directories('src')
executable('ray-tracer', sources, include_directories : inc)
executable('ray-tracer', sources, include_directories : inc)

51
src/camera.h Normal file
View file

@ -0,0 +1,51 @@
#pragma once
#include "ray.h"
#include "raytracer.h"
#include "vec3.h"
class Camera {
public:
constexpr Camera(Vec3 centre, u32 image_width, u32 image_height, double focal_length)
: centre_{centre} {
constexpr double viewport_height = 2.0;
double viewport_width = viewport_height * static_cast<double>(image_width) / image_height;
Vec3 u_viewport = viewport_width * Vec3::e_x;
Vec3 v_viewport = -viewport_height * Vec3::e_y;
d_u_pixel_ = u_viewport / image_width;
d_v_pixel_ = v_viewport / image_height;
// upper left of viewport: move from camera centre `focal_length` in z direction, then half
// of the viewport length in x and y direction respectively
auto viewport_upper_left =
centre_ - focal_length * Vec3::e_z - u_viewport / 2 - v_viewport / 2;
pixel_00_ = viewport_upper_left + 0.5 * d_u_pixel_ + 0.5 * d_v_pixel_;
}
constexpr Vec3 centre() const { return centre_; }
constexpr Vec3 Image2World(u32 i, u32 j) const {
return pixel_00_ + i * d_u_pixel_ + j * d_v_pixel_;
}
constexpr Ray CastRay(u32 i, u32 j) const {
auto pixel_centre = Image2World(i, j);
auto ray_direction = pixel_centre - centre();
return Ray{centre(), ray_direction};
}
private:
Vec3 centre_;
// position of pixel 0, 0
Vec3 pixel_00_;
// Vec3 pointing from pixel to right/lower neighbour resp.
Vec3 d_u_pixel_;
Vec3 d_v_pixel_;
};

View file

@ -3,9 +3,13 @@
#include <cassert>
#include <iomanip>
#include <ios>
#include <iostream>
#include <ostream>
#include <sstream>
#include <string>
#include "util.h"
#include "raytracer.h"
#include "vec3.h"
class Colour {
public:
@ -19,6 +23,8 @@ class Colour {
assert(0.0 <= b && b <= 1.0);
}
constexpr explicit Colour(Vec3 v) : Colour{v.x(), v.y(), v.z()} {}
constexpr double& r() { return r_; }
constexpr double& g() { return g_; }
constexpr double& b() { return b_; }
@ -27,6 +33,18 @@ class Colour {
constexpr double g() const { return g_; }
constexpr double b() const { return b_; }
std::string to_string() const {
std::stringstream ss;
ss << '#';
ss << std::hex << std::setfill('0') << std::setw(2) << FToU8(r());
ss << std::hex << std::setfill('0') << std::setw(2) << FToU8(g());
ss << std::hex << std::setfill('0') << std::setw(2) << FToU8(b());
return ss.str();
}
private:
double r_;
double g_;
@ -35,20 +53,14 @@ class Colour {
constexpr Colour Colour::kBlack{0.0, 0.0, 0.0};
constexpr Colour operator*(double t, Colour col) {
return {t * col.r(), t * col.g(), t * col.b()};
}
constexpr Colour operator*(double t, Colour col) { return {t * col.r(), t * col.g(), t * col.b()}; }
constexpr Colour operator+(Colour c1, Colour c2) {
return {c1.r() + c2.r(), c1.g() + c2.g(), c1.b() + c2.b()};
}
inline std::ostream& operator<<(std::ostream& out, Colour colour) {
out << '#';
inline std::ostream& operator<<(std::ostream& os, Colour colour) {
os << FToU8(colour.r()) << ' ' << FToU8(colour.g()) << ' ' << FToU8(colour.b());
out << std::hex << std::setfill('0') << std::setw(2) << FToU8(colour.r());
out << std::hex << std::setfill('0') << std::setw(2) << FToU8(colour.g());
out << std::hex << std::setfill('0') << std::setw(2) << FToU8(colour.b());
return out;
}
return os;
}

View file

@ -2,26 +2,25 @@
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <memory>
#include <ostream>
#include "colour.h"
#include "util.h"
#include "raytracer.h"
class Image {
public:
constexpr Image(uint32_t width, uint32_t height)
constexpr Image(u32 width, u32 height)
// NOLINTNEXTLINE(modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
: buf_(std::make_unique_for_overwrite<Colour[]>(static_cast<std::size_t>(width) * height)),
width_{width},
height_{height} {}
constexpr uint32_t width() const { return width_; }
constexpr uint32_t height() const { return height_; }
constexpr u32 width() const { return width_; }
constexpr u32 height() const { return height_; }
Colour& operator[](uint32_t x, uint32_t y) {
Colour& operator[](u32 x, u32 y) {
assert(x < width_);
assert(y < height_);
@ -30,7 +29,7 @@ class Image {
return buf_[y * width_ + x];
}
Colour operator[](uint32_t x, uint32_t y) const {
Colour operator[](u32 x, u32 y) const {
assert(x < width_);
assert(y < height_);
@ -43,24 +42,26 @@ class Image {
// NOLINTNEXTLINE(modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
std::unique_ptr<Colour[]> buf_;
uint32_t width_;
uint32_t height_;
u32 width_;
u32 height_;
};
constexpr std::ostream& operator<<(std::ostream& os, const Image& img) {
os << "P3\n" << img.width() << ' ' << img.height() << "\n255\n";
os << "P3" << newline;
os << img.width() << ' ' << img.height() << newline;
os << "255" << newline;
for (uint32_t j = 0; j < img.height(); j++) {
for (u32 j = 0; j < img.height(); j++) {
std::clog << "\rWriting line " << j << " of " << img.height() << std::flush;
for (uint32_t i = 0; i < img.width(); i++) {
for (u32 i = 0; i < img.width(); i++) {
const Colour col = img[i, j];
os << FToU8(col.r()) << ' ' << FToU8(col.g()) << ' ' << FToU8(col.b()) << '\n';
os << col << newline;
}
}
std::clog << "\rDone " << std::endl;
std::clog << "\rDone " << newline;
return os;
}
}

View file

@ -1,26 +1,30 @@
#include <cstdint>
#include <iostream>
#include <memory>
#include "camera.h"
#include "colour.h"
#include "image.h"
#include "ray.h"
#include "raytracer.h"
#include "renderobject.h"
#include "renderobjectlist.h"
#include "sphere.h"
#include "vec3.h"
bool IntersectsSphere(const Ray& r, const Point3& centre, double radius) {
Vec3 oc = r.origin() - centre;
Colour RayColour(const Ray& ray, const RenderObject& world) {
auto hit_record = world.hit(ray, 0.0, kInf);
double a = r.direction().squared();
double b = 2.0 * dot(oc, r.direction());
double c = oc.squared() - radius * radius;
if (hit_record.has_value()) {
// assert(hit_record.front_face);
auto discr = b * b - 4 * a * c;
Vec3 n = hit_record->normal;
return discr >= 0;
}
if (!hit_record->front_face) {
// return Colour{0.0, 0.0, 0.0};
return Colour{0.5 * (n + Vec3{1, 1, 1})};
}
Colour RayColour(const Ray& ray) {
if (IntersectsSphere(ray, -Vec3::e_z, 0.5)) {
return {1.0, 0.0, 0.0};
return Colour{0.5 * (n + Vec3{1, 1, 1})};
}
auto unit_dir = ray.direction().normed();
@ -31,54 +35,42 @@ Colour RayColour(const Ray& ray) {
}
int main(int /* argc */, char* /* argv */[]) {
// image properties
// image
constexpr double aspect_ratio = 16.0 / 9.0;
constexpr uint32_t image_width = 800;
constexpr u32 image_width = 800;
constexpr auto image_height = static_cast<uint32_t>(image_width / aspect_ratio);
constexpr auto image_height = static_cast<u32>(image_width / aspect_ratio);
static_assert(image_height >= 1);
Image img{image_width, image_height};
// world
RenderObjectList world;
world.Add(std::make_shared<Sphere>(Vec3{0, -100.5, -1}, 100));
world.Add(std::make_shared<Sphere>(-Vec3::e_z, 0.5));
// camera
constexpr double focal_length = 1.0;
constexpr double viewport_height = 2.0;
constexpr double viewport_width =
viewport_height * static_cast<double>(image_width) / image_height;
constexpr Point3 camera_centre{0.0, 0.0, 0.0};
Point3 camera_centre{0.0, 0.0, 0.0};
// viewport
constexpr Vec3 u_viewport = viewport_width * Vec3::e_x;
constexpr Vec3 v_viewport = -viewport_height * Vec3::e_y;
constexpr Vec3 d_u_pixel = u_viewport / image_width;
constexpr Vec3 d_v_pixel = v_viewport / image_height;
auto viewport_upper_left =
camera_centre - focal_length * Vec3::e_z - u_viewport / 2 - v_viewport / 2;
auto pixel_00 = viewport_upper_left + 0.5 * d_u_pixel + 0.5 * d_v_pixel;
Camera camera{camera_centre, img.width(), img.height(), focal_length};
// render
Image img{image_width, image_height};
for (uint32_t j = 0; j < img.height(); j++) {
for (u32 j = 0; j < img.height(); j++) {
std::clog << "\rWriting line " << j << " of " << img.height() << std::flush;
for (uint32_t i = 0; i < img.width(); i++) {
auto pixel_centre = pixel_00 + i * d_u_pixel + j * d_v_pixel;
for (u32 i = 0; i < img.width(); i++) {
auto ray = camera.CastRay(i, j);
auto ray_direction = pixel_centre - camera_centre;
Ray r{camera_centre, ray_direction};
img[i, j] = RayColour(r);
img[i, j] = RayColour(ray, world);
}
}

View file

@ -4,16 +4,16 @@
class Ray {
public:
Ray() = default;
constexpr Ray() = default;
Ray(const Point3& origin, const Vec3& direction) : orig_{origin}, dir_{direction} {}
constexpr Ray(const Point3& origin, const Vec3& direction) : orig_{origin}, dir_{direction} {}
Point3 origin() const { return orig_; }
Vec3 direction() const { return dir_; }
Point3 At(double t) { return orig_ + t * dir_; }
constexpr Point3 At(double t) const { return orig_ + t * dir_; }
private:
Point3 orig_;
Vec3 dir_;
};
};

16
src/raytracer.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
#include <limits>
#include <numbers>
using u32 = uint32_t;
constexpr double kInf = std::numeric_limits<double>::infinity();
constexpr double kPi = std::numbers::pi_v<double>;
constexpr char newline = '\n';
constexpr u32 FToU8(double x) { return static_cast<uint16_t>(255.999 * x); }
constexpr double deg2rad(double deg) { return deg * kPi / 180.0; }

31
src/renderobject.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include <memory>
#include <optional>
#include "ray.h"
#include "vec3.h"
struct HitRecord {
Point3 p;
Vec3 normal;
double t = 0.0;
bool front_face = true;
};
class RenderObject {
public:
RenderObject() = default;
RenderObject(const RenderObject& other) = default;
RenderObject& operator=(const RenderObject&) = default;
RenderObject(RenderObject&& other) = default;
RenderObject& operator=(RenderObject&&) = default;
virtual ~RenderObject() = default;
virtual std::optional<HitRecord> hit(const Ray& ray, double t_min, double t_max) const = 0;
};
using SharedRenderObject = std::shared_ptr<RenderObject>;

46
src/renderobjectlist.h Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include <initializer_list>
#include <optional>
#include <vector>
#include "ray.h"
#include "raytracer.h"
#include "renderobject.h"
class RenderObjectList : public RenderObject {
public:
constexpr RenderObjectList() = default;
// explicit RenderObjectList(const SharedRenderObject& obj) { Append(obj); }
RenderObjectList(std::initializer_list<SharedRenderObject> objs) : objs_{objs} {}
constexpr auto begin() { return objs_.begin(); }
constexpr auto end() { return objs_.end(); }
constexpr auto begin() const { return objs_.begin(); }
constexpr auto end() const { return objs_.end(); }
void Add(const SharedRenderObject& obj) { objs_.push_back(obj); }
void Clear() { objs_.clear(); }
std::optional<HitRecord> hit(const Ray& ray, double t_min, double t_max) const override {
std::optional<HitRecord> closest_hit_record = std::nullopt;
double closest_t = kInf;
for (const auto& obj : objs_) {
auto hit_record = obj->hit(ray, t_min, t_max);
if (hit_record.has_value() && hit_record->t < closest_t) {
closest_hit_record = hit_record;
closest_t = hit_record->t;
}
}
return closest_hit_record;
}
private:
std::vector<SharedRenderObject> objs_;
};

79
src/sphere.h Normal file
View file

@ -0,0 +1,79 @@
#pragma once
#include <cassert>
#include <cmath>
#include <optional>
#include "ray.h"
#include "renderobject.h"
#include "vec3.h"
class Sphere : public RenderObject {
public:
constexpr Sphere() = default;
constexpr Sphere(Point3 centre, double radius) : centre_{centre}, radius_{radius} {
assert(radius >= 0);
}
constexpr Point3 centre() const { return centre_; }
constexpr double radius() const { return radius_; }
std::optional<HitRecord> hit(const Ray& ray, double t_min, double t_max) const override {
HitRecord hit_record;
Vec3 oc = ray.origin() - centre_;
double a = ray.direction().squared();
double b_half = dot(oc, ray.direction());
double c = oc.squared() - radius_ * radius_;
auto discr = b_half * b_half - a * c;
if (discr < 0) {
// miss
return std::nullopt;
}
// hit
// smaller of the ts that hit the sphere
double t = (-b_half - std::sqrt(discr)) / a;
if (t > t_max) {
// if the smaller t is already too large, we don't need to check the second one since it
// will be even larger
return std::nullopt;
}
if (t < t_min) {
// if the smaller t is too small, check the larger one
t = (-b_half + std::sqrt(discr)) / a;
if (t < t_min || t > t_max) {
// larger t also out of range: return false
return std::nullopt;
}
// smaller t hits front side, smaller t hits back side
// TODO: what if our hit t is less than 0? is that case even relevant?
hit_record.front_face = false;
}
hit_record.t = t;
hit_record.p = ray.At(t);
hit_record.normal = (hit_record.p - centre_) / radius_;
if (!hit_record.front_face) {
hit_record.normal = -hit_record.normal;
}
return hit_record;
}
private:
Point3 centre_{};
double radius_ = 0.0;
};

View file

@ -1,6 +0,0 @@
#pragma once
#include <cstdint>
constexpr uint32_t FToU8(double x) {
return static_cast<uint16_t>(255.999 * x);
}

View file

@ -3,6 +3,7 @@
#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <ostream>
class Vec3 {
@ -86,9 +87,7 @@ constexpr Vec3 operator+(const Vec3& u, const Vec3& v) {
return out;
}
constexpr Vec3 operator-(const Vec3& u, const Vec3& v) {
return u + (-v);
}
constexpr Vec3 operator-(const Vec3& u, const Vec3& v) { return u + (-v); }
constexpr Vec3 operator*(const Vec3& u, const Vec3& v) {
return {u.x() * v.x(), u.y() * v.y(), u.z() * v.z()};
@ -100,9 +99,7 @@ constexpr Vec3 operator*(double t, const Vec3& v) {
return out;
}
constexpr Vec3 operator/(const Vec3& v, double t) {
return (1 / t) * v;
}
constexpr Vec3 operator/(const Vec3& v, double t) { return (1 / t) * v; }
constexpr double dot(const Vec3& u, const Vec3& v) {
Vec3 tmp = u * v;
@ -114,6 +111,4 @@ constexpr Vec3 cross(const Vec3& u, const Vec3& v) {
u.x() * v.y() - u.y() - v.x()};
}
constexpr Vec3 normed(const Vec3& v) {
return v.normed();
}
constexpr Vec3 normed(const Vec3& v) { return v.normed(); }