Note on "Ray tracing in one week" - Part 1

Note on "Ray tracing in one week" - Part 1

Ayano Kagurazaka Lv3

Note on “Ray tracing in one week” - Part 1

The code is compiled with cmake, please check out CMakeLists.txt

Ok… A new topic… Yet to be finished…

Preface

Ayano why you opened a new topic while you are having other topics unfinished

Well, I’m interested in computer graphics. I’m doing OpenGL. So it’s pretty reasonable for me to be interested in ray tracing anyway.

This series of articles are based on Ray Tracing in One Week , but I’m not probably going to follow the division of part defined by the book(Maybe just write till I think that’s enough for one article).

Ok… Let’s get into topic…

Image output and math

We are talking about ray tracing, which is a field of computer graphics. To actually see graphics, we need to be able to produce images. Luckily, there is a type of image that can be easily produced: ppm .

ppm is easy to generate and edit because it’s plain text format, and we can easily manipulate with std::ofstream. Now we can create a generatePPM function to do so.

But wait, an image has three channels in ppm, how will we store that? Well, we will define a class to store that. It has 3 elements: r, g, b, and we will let it return the constructed color string. Let’s name it Vec3 since vectors are used in many places of computer graphics and using Vec3 as color is reasonable since they all have 3 elements.

This file I will call it MathUtil because we will probably use more math functions and classes.

MathUtil.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// MathUtil.h
#ifndef ONEWEEKEND_MATHUTIL_H
#define ONEWEEKEND_MATHUTIL_H

#include <iostream>
#include <cmath>
#include "spdlog/fmt/fmt.h"

class Vec3 {
public:
double x, y, z;
Vec3(double x, double y, double z);
Vec3(const Vec3& other);
Vec3(Vec3&& other) noexcept;
[[nodiscard]] std::string makeColor() const;
}

#endif //ONEWEEKEND_MATHUTIL_H

MathUtil.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "MathUtil.h"

Vec3::Vec3(double x, double y, double z): x(x), y(y), z(z) {

}

Vec3::Vec3(const Vec3 &other) {
x = other.x;
y = other.y;
z = other.z;
}

Vec3::Vec3(Vec3 &&other) noexcept {
x = other.x;
y = other.y;
z = other.z;
}

std::string Vec3::makeColor() const {
return fmt::format("{} {} {}\n", static_cast<int>(x), static_cast<int>(y), static_cast<int>(z));
}

With this class, we can use std::ofstream to construct our ppm image. Let’s define a makePPM method that will take in image width and height, an image 2d vector, save directory, and file name. We will define it in a file called ImageUtil.

icon-padding

For the sake of readability, I will define Color as an alias of Vec3.

ImageUtil.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <fstream>
#include <vector>
#include <tuple>
#include <sstream>
#include <sys/stat.h>
#include "MathUtil.h"
#include "spdlog/spdlog.h"

namespace ImageUtil {

using Color = Vec3;

void makePPM(int width, int height, std::vector<std::vector<Color>> img, const std::string &path,
const std::string &name);

}

ImageUtil.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "ImageUtil.h"

namespace ImageUtil{
void makePPM(int width, int height, std::vector<std::vector<Color>> img, const std::string &path,
const std::string &name) {
auto fout = std::ofstream();
auto dir = path.ends_with("/") ? path : path + "/";
struct stat st;
if (stat(dir.c_str(), &st) != 0 && mkdirat(AT_FDCWD, dir.c_str(), 0755) == -1) {
spdlog::critical("directory create failed at {}", path);
exit(2);
}
fout.open((path.ends_with("/") ? path : path + "/") + name);
fout << "P3\n" << width - 1 << ' ' << height - 1 << "\n255\n";
for(int i = 0; i < height; i++) {
for(int j = 0; j < width; j++) {
fout << img[i][j].makeColor();
}
}
fout.close();
}
}

Finally, let’s write our image generation code in main:

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "ImageUtil.h"
#include <vector>

int main() {
int width = 256, height = 144;
int b = 0;
auto img = std::vector<std::vector<ImageUtil::Color>>();
for (int i = 0; i < height; ++i) {
auto v = std::vector<ImageUtil::Color>();
for (int j = 0; j < width; ++j) {
v.emplace_back(i, j, b);
b++;
}
img.emplace_back(v);
}
ImageUtil::makePPM(width, height, img, "./out", "test.ppm");
return 0;
}

Run, and we get…

ppmTest

An image! Great!

More computer graphical implementation of Vec3

The directory layout should be this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|-- CMakeLists.txt
|-- README.md
|-- include
| `-- OneWeekend
| |-- ImageUtil.h
| `-- MathUtil.h
|-- src
| |-- ImageUtil.cpp
| |-- MathUtil.cpp
| `-- main.cpp
`-- thirdparties
|-- include
| `-- spdlog [spdlog include omitted]
`-- lib
`-- Darwin
`-- libspdlog.a

Now we can display a ppm image. Recall that we will use Vec3 more often in computer graphics? Now it’s time to make it more suit our needs.

For a Vector in R3\mathbb{R}^3, it should be closed under Vec3 addition and scalar multiplication, define dot product and cross product, and shortcut for increment and decrement (+= and -=). We also need to be able to quickly determine its norm and the unit vector on that direction. For the sake of debugging, we will implement std::string operator and << operator

With these enhancements in mind, let’s enhance out Vec3 class:

MathUtil.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Vec3 {
public:
double x, y, z;

Vec3(double x, double y, double z);

Vec3();

Vec3(const Vec3 &other);

Vec3(Vec3 &&other) noexcept;

[[nodiscard]] std::string makeColor() const;

Vec3 &operator=(const Vec3 &other);

double operator[](int i) const;

double operator[](int i);

Vec3 operator-() const;

Vec3 operator-(const Vec3 &other);

Vec3 operator+(const Vec3 &other) const;

Vec3 operator*(double t) const;

Vec3 operator/(double t) const;

Vec3 &operator+=(const Vec3 &other);

Vec3 &operator*=(double t);

Vec3 &operator/=(double t);

[[nodiscard]] double lengthSq() const;

[[nodiscard]] double length() const;

[[nodiscard]] double dot(const Vec3 &other) const;

[[nodiscard]] Vec3 cross(const Vec3 &other) const;

[[nodiscard]] Vec3 unit() const;

[[nodiscard]] operator std::string() const;
};

std::ostream &operator<<(std::ostream &out, const Vec3 &other);

inline Vec3 operator*(double t, const Vec3 &v) {
return {t*v.x, t*v.y, t*v.z};
}

MathUtil.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
Vec3::Vec3(double x, double y, double z): x(x), y(y), z(z) {

}

Vec3::Vec3(): Vec3(0, 0, 0) {

}

Vec3::Vec3(const Vec3 &other) {
x = other.x;
y = other.y;
z = other.z;
}

Vec3::Vec3(Vec3 &&other) noexcept {
x = other.x;
y = other.y;
z = other.z;
}

Vec3 &Vec3::operator=(const Vec3 &other) {
if(this != &other) {
x = other.x;
y = other.y;
z = other.z;
}
return *this;
}

double Vec3::operator[](int i) const {
double arr[] = {x, y, z};
return arr[i];
}

double Vec3::operator[](int i) {
double arr[] = {x, y, z};
return arr[i];
}

Vec3 Vec3::operator-(const Vec3 &other) const {
return {x - other.x, y - other.y, z - other.z};
}

Vec3 &Vec3::operator+=(const Vec3 &other) {
x += other.x;
y += other.y;
z += other.z;
return *this;
}

Vec3 &Vec3::operator*=(double t) {
x *= t;
y *= t;
z *= t;
return *this;
}

Vec3 &Vec3::operator/=(double t) {
x /= t;
y /= t;
z /= t;
return *this;
}

double Vec3::lengthSq() const {
return x * x + y * y + z * z;
}

double Vec3::length() const {
return sqrt(lengthSq());
}

Vec3 Vec3::operator-() const {
return {-x, -y, -z};
}

Vec3 Vec3::operator+(const Vec3 &other) const {
return {x + other.x, y + other.y, z + other.z};
}

double Vec3::dot(const Vec3 &other) const {
return x * other.x + y * other.y + z * other.z;
}

Vec3 Vec3::cross(const Vec3 &other) const {
return {y * other.z - z * other.y, z * other.x - x * other.z, x * other.y - y * other.x};
}

Vec3 Vec3::unit() const {
auto v = *this / this->length();
return v;
}

Vec3 Vec3::operator/(double t) const {
return {x / t, y / t, z / t};
}

Vec3 Vec3::operator*(double t) const {
return {x * t, y * t, z * t};
}

std::string Vec3::makeColor() const {
return fmt::format("{} {} {}\n", static_cast<int>(x), static_cast<int>(y), static_cast<int>(z));
}

Vec3::operator std::string() const {
return fmt::format("Vec3: {:.6f} {:.6f} {:.6f}", x, y, z);
}

std::ostream& operator<<(std::ostream &out, const Vec3 &other) {
out << "Vec3: " << other.x << " " << other.y << " " << other.z;
return out;
}

Now we can do basic arithmetic for vectors on Vec3.

Ray tracing, starting with Ray

We all know from basic linear algebra, a line in R3\mathbb{R}^3, \ell, is defined by (t)=A+Bt\ell(t) = \textbf{A} + \textbf{B}t where A,BR3\textbf{A}, \textbf{B} \in \mathbb{R}^3. Here A\textbf{A} is the origin of the line, and B\mathbf{B} is the direction of the line. With this schematic, we can also define a ray in R3\mathbb{R}^3, since a ray is just a line that can only go in one direction (t[0,)t \in [0, \infty)). Also, we need to ‘trace’ the ray, so we need to know where the ray is at.

With these in mind, now we can define a Ray class:

icon-padding

Still, to make code more readable, I defined Point3 as an alias of Vec3 and wrapped all inside a namespace.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace MathUtil {
/// Vec3 definition omitted...
using Point3 = Vec3;

class Ray {
private:
Point3 position;
Vec3 direction;
public:
Ray(Vec3 pos, Vec3 dir);

Vec3 pos() const;
Vec3 dir() const;

Point3 at(double t) const;
};
}

MathUtil.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace MathUtil {
/// Vec3 implementation omitted...
Ray::Ray(MathUtil::Vec3 pos, MathUtil::Vec3 dir) : position(std::move(pos)), direction(std::move(dir)) {

}

Point3 Ray::at(double t) const {
return position + direction * t;
}

Vec3 Ray::dir() const {
return direction;
}

Vec3 Ray::pos() const {
return position;
}
}

Ray Tracing, then Tracing

Now we have a ray, let’s build a ray tracer.

Ray tracer is basically a thing that cast rays out and compute the color seen by the ray. It involves three steps:

  1. Cast a ray from its “eye”
  2. Determine the closest object the ray intersection.
  3. Get the color from the point of intersection.

For better image presentation and makes our life easier, we will use an aspect ratio of 16:9

Aspect ratio, viewport, and camera

You probably know what aspect ratio is: it’s the ratio between screen width and screen height. In 16:9, the ratio is:

width/height=16/9=1.7778\text{width} / \text{height} = 16/9 = 1.7778

To use minimal magic number and to change the aspect ratio easily, we will calculate it from the width and height of the image.

1
2
3
4
int width = 256;
auto aspect = 16.0 / 9.0;
int height = static_cast<int>(width / aspect);
height = height < 1 ? 1 : height;

icon-padding

The height could be less than 1, so we need to check that.

Viewport, as its name suggests, is the port that we view the scene. It has the same aspect ratio with the image because we don’t want to shrink the image.

Camera is the ‘eye’ we used to view the scene, and the object that casts arrays. It has a focal length, which is the distance between the camera and the associated viewport. Dimensions of the viewport can be found this way:

1
2
3
4
5
auto focal_len = 1.0;

auto viewport_width = 2.0;
auto viewport_height = viewport_width / (static_cast<double>(width) / height);
auto camera_center = MathUtil::Vec3(0, 0, 0);

Note that we are calculating aspect ratio from the image, not the defined aspect ratio. This is because when we are finding the image dimensions, we rounded down some value, making the actual aspect ratio different from the defined one. We will use the actual aspect ratio to calculate viewport height.

Viewport

Ray casting

Now we have a camera and a viewport, we can cast rays from the camera to the viewport. The way we are casting ray, however, requires a little of explaination.

In the first step of how ray tracer works, we need to cast a ray. In order to obtain a color on each pixel of our image, we need to cast a ray through each point on our viewport corresponding to an actual pixel.

To achieve this, we will use some linear algebra.

icon-padding

All the coordinates below are in right hand coordinate system.

Assume the width of the viewport is ww and the height of the viewport is hh, we make two vectors, name them A\textbf{A} and B\textbf{B}, such that

\begin{align} \textbf{A} = \begin{bmatrix} w\\ 0\\ 0 \end{bmatrix} \text{\;and\;} \textbf{B} = \begin{bmatrix} 0\\ -h\\ 0 \end{bmatrix} \end{align}

ViewportVector

Let’s denote the camera position as O\textbf{O}, focal length to be ff, focal length vector F\textbf{F} such that

F=[00f]F = \begin{bmatrix} 0\\ 0\\ -f \end{bmatrix}

We can find the upper left corner of the viewport, P\textbf{P}, which is:

P=OA+B2F\textbf{P} = \textbf{O} - \frac{\textbf{A} + \textbf{B}}{2} - \textbf{F}

UpperLeftVector

Now we can find the position of each pixel on the viewport. To find this, we first have to find the position of the first pixel on the viewport, P00P_{00}. And we need to find the horizontal and vertical distance between pixels, Δx\Delta x and Δy\Delta y.

Since the viewport is a projection of the image, and they share same aspect ratio, they should have the same pixel density. Let’s denote the screen width and height in pixel as WW and HH we have:

\begin{align*} \Delta x & = w / W \\ \Delta y & = h / H \end{align*}

and we have:

\begin{align*} P_{00} & = \textbf{P} + \begin{bmatrix} \Delta x / 2\\ \Delta y / 2\\ 0 \end{bmatrix} \\\\ P_{ij} & = P_{00} + \begin{bmatrix} i \cdot \Delta x\\ j \cdot \Delta y\\ 0 \end{bmatrix} \end{align*}

PixelPosition

fa-circle-info

Red dots are pixel upper right position.

Now we can cast rays from the camera to the viewport. Let’s denote the ray as \ell, and the ray from camera to PijP_{ij} as ij\ell_{ij}, we have:

ij=PijO\begin{matrix} \ell_{ij} = P_{ij} - \textbf{O} \\ \end{matrix}

With this direction, and position from the camera, we can construct a ray.

Currently, we don’t have any objects to interact with, so we will just skip to step 3. In this case, we will make a gradient image. We will use the x, y magnitude of the normalized ray as color of the pixel.

code:

1
2
3
4
5
6
7
ImageUtil::Color rayColor(const MathUtil::Ray& ray) {
auto unit = ray.dir().unit();
// spdlog::info(unit.y);
auto progx = std::min(0.5 * unit.x + 1, 1.0);
auto progy = std::min(0.5 * unit.y + 1, 1.0);
return ImageUtil::Color(progx, progy, 1);
}

icon-padding

Because y and x must be within the range [1,1][-1, 1], we defined prog to record the progress as y changes value

Now, we implement all the work above as code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include "ImageUtil.h"
#include "MathUtil.h"
#include <vector>

ImageUtil::Color rayColor(const MathUtil::Ray& ray) {
auto unit = ray.dir().unit();
// spdlog::info(unit.y);
auto progx = std::min(0.5 * unit.x + 1, 1.0);
auto progy = std::min(0.5 * unit.y + 1, 1.0);
return ImageUtil::Color(progx, progy, 1);
}


int main() {

// image dimension
int width = 1920;
auto aspect = 16.0 / 9.0;
int height = static_cast<int>(width / aspect);
height = height < 1 ? 1 : height;

// camera
auto focal_len = 1.0;

auto viewport_width = 2.0;
auto viewport_height = viewport_width / (static_cast<double>(width) / height);
auto camera_center = MathUtil::Vec3(0, 0, 0);

auto viewport_vec_h = MathUtil::Vec3(viewport_width, 0, 0);
auto viewport_vec_v = MathUtil::Vec3(0, -viewport_height, 0);

auto pix_delta_h = viewport_vec_h / width;
auto pix_delta_v = viewport_vec_v / height;

auto viewport_ul = camera_center - MathUtil::Vec3(0, 0, focal_len) - viewport_vec_h / 2 - viewport_vec_v / 2;


auto pixel_00 = viewport_ul + (pix_delta_h + pix_delta_v) * 0.5;
spdlog::info(std::string(pixel_00));
auto img = std::vector<std::vector<ImageUtil::Color>>();
for (int i = 0; i < height; ++i) {
auto v = std::vector<ImageUtil::Color>();
spdlog::info("line remaining: {}", (height - i));
for (int j = 0; j < width; ++j) {
auto pix = pixel_00 + pix_delta_h * j + pix_delta_v * i;
auto ray_dir = pix - camera_center;
auto ray = MathUtil::Ray(camera_center, ray_dir);
auto color = rayColor(ray);
v.emplace_back(color);
}
img.emplace_back(v);
}
ImageUtil::makePPM(width, height, img, "./out", "test.ppm");
return 0;
}

The result is beautiful:

gradient

Associated code

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include "ImageUtil.h"
#include "MathUtil.h"
#include <vector>

ImageUtil::Color rayColor(const MathUtil::Ray& ray) {
auto unit = ray.dir().unit();
// spdlog::info(unit.y);
auto progx = std::min(0.5 * unit.x + 1, 1.0);
auto progy = std::min(0.5 * unit.y + 1, 1.0);
return ImageUtil::Color(progx, progy, 1);
}


int main() {

// image dimension
int width = 1920;
auto aspect = 16.0 / 9.0;
int height = static_cast<int>(width / aspect);
height = height < 1 ? 1 : height;

// camera
auto focal_len = 1.0;

auto viewport_width = 2.0;
auto viewport_height = viewport_width / (static_cast<double>(width) / height);
auto camera_center = MathUtil::Vec3(0, 0, 0);

auto viewport_vec_h = MathUtil::Vec3(viewport_width, 0, 0);
auto viewport_vec_v = MathUtil::Vec3(0, -viewport_height, 0);

auto pix_delta_h = viewport_vec_h / width;
auto pix_delta_v = viewport_vec_v / height;

auto viewport_ul = camera_center - MathUtil::Vec3(0, 0, focal_len) - viewport_vec_h / 2 - viewport_vec_v / 2;


auto pixel_00 = viewport_ul + (pix_delta_h + pix_delta_v) * 0.5;
spdlog::info(std::string(pixel_00));
auto img = std::vector<std::vector<ImageUtil::Color>>();
for (int i = 0; i < height; ++i) {
auto v = std::vector<ImageUtil::Color>();
spdlog::info("line remaining: {}", (height - i));
for (int j = 0; j < width; ++j) {
auto pix = pixel_00 + pix_delta_h * j + pix_delta_v * i;
auto ray_dir = pix - camera_center;
auto ray = MathUtil::Ray(camera_center, ray_dir);
auto color = rayColor(ray);
v.emplace_back(color);
}
img.emplace_back(v);
}
ImageUtil::makePPM(width, height, img, "./out", "test.ppm");
return 0;
}

ImageUtil.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef ONEWEEKEND_IMAGEUTIL_H
#define ONEWEEKEND_IMAGEUTIL_H

#include <fstream>
#include <vector>
#include <tuple>
#include <sstream>
#include <sys/stat.h>
#include "MathUtil.h"
#include "spdlog/spdlog.h"

namespace ImageUtil {

using Color = MathUtil::Vec3;

void makePPM(int width, int height, std::vector<std::vector<Color>> img, const std::string &path,
const std::string &name);

}

#endif //ONEWEEKEND_IMAGEUTIL_H

mathUtil.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#ifndef ONEWEEKEND_MATHUTIL_H
#define ONEWEEKEND_MATHUTIL_H

#include <iostream>
#include <cmath>
#include "spdlog/fmt/fmt.h"

namespace MathUtil {

class Vec3 {
public:
double x, y, z;

Vec3(double x, double y, double z);

Vec3(const Vec3 &other);

Vec3(Vec3 &&other) noexcept;

Vec3();

[[nodiscard]] std::string makeColor() const;

Vec3 &operator=(const Vec3 &other);

double operator[](int i) const;

double operator[](int i);

Vec3 operator-() const;

Vec3 operator-(const Vec3 &other) const;

Vec3 operator+(const Vec3 &other) const;

Vec3 operator*(double t) const;

Vec3 operator/(double t) const;

Vec3 &operator+=(const Vec3 &other);

Vec3 &operator*=(double t);

Vec3 &operator/=(double t);

[[nodiscard]] double lengthSq() const;

[[nodiscard]] double length() const;

[[nodiscard]] double dot(const Vec3 &other) const;

[[nodiscard]] Vec3 cross(const Vec3 &other) const;

[[nodiscard]] Vec3 unit() const;

[[nodiscard]] operator std::string() const;
};

std::ostream &operator<<(std::ostream &out, const Vec3 &other);

inline Vec3 operator*(double t, const Vec3 &v) {
return {t*v.x, t*v.y, t*v.z};
}

using Point3 = Vec3;

class Ray {
private:
Point3 position;
Vec3 direction;
public:
Ray(Vec3 pos, Vec3 dir);

Vec3 pos() const;
Vec3 dir() const;

Point3 at(double t) const;
};
}

ImageUtil.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#endif //ONEWEEKEND_MATHUTIL_H
#include "ImageUtil.h"

namespace ImageUtil{
void makePPM(int width, int height, std::vector<std::vector<Color>> img, const std::string &path,
const std::string &name) {
auto fout = std::ofstream();
auto dir = path.ends_with("/") ? path : path + "/";
struct stat st;
if (stat(dir.c_str(), &st) != 0 && mkdirat(AT_FDCWD, dir.c_str(), 0755) == -1) {
spdlog::critical("directory create failed at {}", path);
exit(2);
}
fout.open((path.ends_with("/") ? path : path + "/") + name);
fout << "P3\n" << width - 1 << ' ' << height - 1 << "\n255\n";
int progress = 0;
for(int i = 0; i < height; i++) {
for(int j = 0; j < width; j++) {
// spdlog::info("progression: {}%", (progress) * 100 / ((width) * (height)));
fout << img[i][j].makeColor();
progress++;
}
}
fout.close();
}
}

MathUtil.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include "MathUtil.h"

#include <utility>

namespace MathUtil{

Vec3::Vec3(double x, double y, double z): x(x), y(y), z(z) {

}


Vec3::Vec3(const Vec3 &other) {
x = other.x;
y = other.y;
z = other.z;
}

Vec3::Vec3(): Vec3(0, 0, 0) {

}

Vec3::Vec3(Vec3 &&other) noexcept {
x = other.x;
y = other.y;
z = other.z;
}

Vec3 &Vec3::operator=(const Vec3 &other) {
if(this != &other) {
x = other.x;
y = other.y;
z = other.z;
}
return *this;
}

double Vec3::operator[](int i) const {
double arr[] = {x, y, z};
return arr[i];
}

double Vec3::operator[](int i) {
double arr[] = {x, y, z};
return arr[i];
}

Vec3 Vec3::operator-(const Vec3 &other) const {
return {x - other.x, y - other.y, z - other.z};
}

Vec3 &Vec3::operator+=(const Vec3 &other) {
x += other.x;
y += other.y;
z += other.z;
return *this;
}

Vec3 &Vec3::operator*=(double t) {
x *= t;
y *= t;
z *= t;
return *this;
}

Vec3 &Vec3::operator/=(double t) {
x /= t;
y /= t;
z /= t;
return *this;
}

double Vec3::lengthSq() const {
return x * x + y * y + z * z;
}

double Vec3::length() const {
return std::sqrt(lengthSq());
}

Vec3 Vec3::operator-() const {
return {-x, -y, -z};
}

Vec3 Vec3::operator+(const Vec3 &other) const {
return {x + other.x, y + other.y, z + other.z};
}

double Vec3::dot(const Vec3 &other) const {
return x * other.x + y * other.y + z * other.z;
}

Vec3 Vec3::cross(const Vec3 &other) const {
return {y * other.z - z * other.y, z * other.x - x * other.z, x * other.y - y * other.x};
}

Vec3 Vec3::unit() const {
return *this / this->length();
}

Vec3 Vec3::operator/(double t) const {
return {x / t, y / t, z / t};
}

Vec3 Vec3::operator*(double t) const {
return {x * t, y * t, z * t};
}

std::string Vec3::makeColor() const {
return fmt::format("{} {} {}\n", static_cast<int>(x * 255.999), static_cast<int>(y * 255.999), static_cast<int>(z * 255.999));
}

Vec3::operator std::string() const {
return fmt::format("Vec3: {:.6f} {:.6f} {:.6f}", x, y, z);
}



std::ostream& operator<<(std::ostream &out, const Vec3 &other) {
out << "Vec3: " << other.x << " " << other.y << " " << other.z;
return out;
}

Ray::Ray(MathUtil::Vec3 pos, MathUtil::Vec3 dir) : position(std::move(pos)), direction(std::move(dir)) {

}

Point3 Ray::at(double t) const {
return position + direction * t;
}

Vec3 Ray::dir() const {
return direction;
}

Vec3 Ray::pos() const {
return position;
}

}

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmake_minimum_required(VERSION 3.27)
project(OneWeekend)

set(CMAKE_CXX_STANDARD 23)

file(GLOB SRC "./src/*.cpp")
set(INCLUDE_SELF "./include/OneWeekend/")
set(INCLUDE_THIRDPARTIES "./thirdparties/include")
set(INCLUDE ${INCLUDE_SELF} ${INCLUDE_THIRDPARTIES})

set(LIB_DARWIN_DIR "./thirdparties/lib/Darwin")

add_executable(${CMAKE_PROJECT_NAME} ${SRC})
include_directories(${INCLUDE})
link_directories(${LIB_DARWIN_DIR})
link_libraries(${LIB_DARWIN_DIR}/*.a)

directory layout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.
|-- CMakeLists.txt
|-- README.md
|-- include
| `-- OneWeekend
| |-- ImageUtil.h
| `-- MathUtil.h
|-- src
| |-- ImageUtil.cpp
| |-- MathUtil.cpp
| `-- main.cpp
`-- thirdparties
|-- include
| `-- spdlog [spdlog include omitted]
`-- lib
`-- Darwin
`-- libspdlog.a
Comments