mirror of
https://github.com/MorizzG/ray-tracer.git
synced 2025-12-06 04:22:42 +00:00
finished 6.7
This commit is contained in:
parent
a3e1542250
commit
40951c3bbd
13 changed files with 188970 additions and 188753 deletions
51
src/camera.h
Normal file
51
src/camera.h
Normal 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_;
|
||||
};
|
||||
34
src/colour.h
34
src/colour.h
|
|
@ -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;
|
||||
}
|
||||
29
src/image.h
29
src/image.h
|
|
@ -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;
|
||||
}
|
||||
76
src/main.cc
76
src/main.cc
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
|
||||
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_;
|
||||
|
|
|
|||
16
src/raytracer.h
Normal file
16
src/raytracer.h
Normal 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
31
src/renderobject.h
Normal 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
46
src/renderobjectlist.h
Normal 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
79
src/sphere.h
Normal 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;
|
||||
};
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
constexpr uint32_t FToU8(double x) {
|
||||
return static_cast<uint16_t>(255.999 * x);
|
||||
}
|
||||
13
src/vec3.h
13
src/vec3.h
|
|
@ -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(); }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue