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

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

Ayano Kagurazaka Lv3

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

Inside or out

In the last part, we learned to calculate normal vector of a sphere. For the ray hitting the sphere at the front, the normal is pointing with the different direction with the array. For the ray hitting the sphere at the back (for example, the ray passed the front surface of the sphere and hit the back surface), the normal is pointing with the same direction with the array. We need to distinguish these two cases because this is significant for objects that has different inside and outside rendering rule, like a glass sphere.

icon-padding

In the figure I used a plane to represent a small portion of the sphere

insideOrOut

If we want the normal is always pointing out, we can do this by checking the dot product of the normal and the ray direction. If the dot is positive, the ray is outside the sphere. We can use this code:

icon-padding

assume ray is a Ray object, center is a Vec3 object

1
2
3
4
5
6
if (ray.dir().dot(center) > 0) {
// inside
}
else {
//outside
}

If we want to make the normal always pointing against the array(for example, the inner surface of a glass sphere has normal pointing against the array), we still can check the dot product, but we need to reverse the normal if the dot product is positive, meaning the normal is pointing with the same direction with the array, and store whether we are hitting the outer surface or not.

icon-padding

assume ray is a Ray object, center, normal, outward_normal is a Vec3 object

1
2
3
4
5
6
7
8
9
10
11
bool is_front;
if (ray.dir().dot(center) > 0) {
// inside
is_front = false;
outward_normal = -normal;
}
else {
//outside
outward_normal = normal;
is_front = true;
}

The difference is that if you choose to implement the second method, you are determining whether it’s the front surface at the geometric calculation time, and if you choose to implement the first method, you are determining whether it’s the front surface at the shading time. Since there are much more shaders than geometric scenarios, it’s better to choose the second method.

Thus, we will modify our HitRecord to store this information and modify our hit function of the Sphere class.

GraphicObjects.h: HitRecord

1
2
3
4
5
6
7
8
struct HitRecord {
bool hit;
MathUtil::Point3 p;
double t;
MathUtil::Vec3 normal;
bool front_face;
void setFaceNormal(const MathUtil::Ray& r, const MathUtil::Vec3& normal_out);
};

GraphicObjects.cpp: HitRecord::setFaceNormal

1
2
3
4
void HitRecord::setFaceNormal(const MathUtil::Ray &r, const MathUtil::Vec3 &normal_out) {
front_face = normal_out.dot(r.dir()) < 0;
normal = front_face ? normal_out : -normal_out;
}

A whole bunch of hittable objects

Right now we only have one hittable objects, but we will have sooo much more. We need to have a way to store all the hittable objects and check whether the ray hits any of them. For this, we will create a class as the collection of all the hittable objects. Since we will have a lot of hittable objects, and they are going to be somehow big, so we will use std::shared_ptr to store them.

GraphicObjects.h: HittableList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class HittableList: IHittable {
public:
std::vector<std::shared_ptr<IHittable>> objects;

HittableList() = default;

explicit HittableList(const std::shared_ptr<IHittable>& obj);

void add(const std::shared_ptr<IHittable>& obj);

void clear();

auto begin();

auto end();

private:
bool hit(const MathUtil::Ray &r, double ray_tmin, double ray_tmax, HitRecord &record) const override;
};

GraphicObjects.cpp: HittableList

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
auto HittableList::end() {
return objects.end();
}

bool HittableList::hit(const MathUtil::Ray &r, double ray_tmin, double ray_tmax, HitRecord &record) const {
HitRecord temp_rec;
bool if_hit = false;
auto closest_t = ray_tmax;

for (const auto& i : objects) {
if (i->hit(r, ray_tmin, closest_t, temp_rec)) {
if_hit = true;
closest_t = temp_rec.t;
record = temp_rec;
}
}
return if_hit;
}

HittableList::HittableList(const std::shared_ptr<IHittable>& obj) {
add(obj);
}

void HittableList::add(const std::shared_ptr<IHittable>& obj) {
objects.push_back(obj);
}

void HittableList::clear() {
objects.clear();
}

icon-padding

Notice that I implemented begin() and end() to make this class can be iterated with range-based for loop. I also used auto as the return type of the begin and end function. This is because I don’t want to make the definition too long, and I don’t want to type the long type name.

Also, it’s reasonable for us to make a header that includes many useful utility functions and constants, such as PI, infinity, and the conversion between degrees and radians. We will put that in a header called GlobUtil.hpp since it will be a single-header utility header.

GlobUtil.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef GLOBUTIL_HPP
#define GLOBUTIL_HPP

#include <limits>

#define PI (3.1415926)
#define INF (std::numeric_limits::double::infinity())

inline double deg2Rad(double deg) {
return deg * PI / 180.0;
}

#endif // GLOBUTIL_HPP

With our HittableList class, we can create a world with multiple objects. We can do it in our main function:

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
#include "ImageUtil.h"
#include "MathUtil.h"
#include "GraphicObjects.h"
#include <vector>
#include "GlobUtil.hpp"
#include <cmath>



ImageUtil::Color rayColor(const MathUtil::Ray& ray, const IHittable& object) {
ImageUtil::Color color;
HitRecord record;
if (object.hit(ray, 0, INF, record)) {
color = 0.5 * (record.normal + ImageUtil::Color(1, 1, 1));
}
else {
auto unit = ray.dir().unit();
color = ImageUtil::Color(std::min(0.5 * unit.x + 1, 1.0), std::min(0.5 * unit.y + 1, 1.0), 1);
}
return color;
}


int main() {
auto camera = Camera(1920, 16.0 / 9.0, 2.0, 1, MathUtil::Point3(0, 0, 0));
auto world = HittableList();
world.add(std::make_shared<Sphere>(Sphere(10, {0, -10, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {5, 0, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {-5, 0, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {0, 0, -15})));

auto img = std::vector<std::vector<ImageUtil::Color>>();

for (int i = 0; i < camera.getHeight(); ++i) {
auto v = std::vector<ImageUtil::Color>();
if (i % 10 == 0 || i > camera.getHeight() - 10) spdlog::info("line remaining: {}", (camera.getHeight() - i + 1));
for (int j = 0; j < camera.getWidth(); ++j) {
auto ray_dir = camera.getPixRayDir(j, i);
auto ray = MathUtil::Ray(camera.getPosition(), ray_dir);
auto color = rayColor(ray, world);
v.emplace_back(color);
}
img.emplace_back(v);
}
ImageUtil::makePPM(camera.getWidth(), camera.getHeight(), img, "out", "test.ppm");
return 0;
}

After rendering, we have this result:

world

Which is great.

Interval

To make our life easier, we can simplify our code a bit, by using some helper classes or functions. Let’s start with “a value within an interval”.

To do this, we will implement a Interval class that takes two values as the interval. It will check whether an input value is within the interval or surrounded by it. A code implementation is like this:

MathUtil.h: Interval

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ...unchanged
class Interval {
public:
double min, max;
Interval();
Interval(double min, double max);

bool within(double x) const;

bool surround(double x) const;

static const Interval empty, universe;
};
const static Interval empty(+INF, -INF);
const static Interval universe(-INF, +INF);
// ...unchanged

MathUtil.cpp: Interval

1
2
3
4
5
6
7
8
9
10
11
12
Interval::Interval() : min(-INF), max(INF) {

}
Interval::Interval(double min, double max): min(min), max(max) {

}
bool Interval::within(double x) const {
return (min >= x) && (x <= max);
}
bool Interval::surround(double x) const {
return (min > x) && (x < max);
}

Now we can modify the code like this:

GraphicObjects.h: IHittable

1
2
3
4
5
6
class IHittable {
public:
virtual ~IHittable() = default;

virtual bool hit(const MathUtil::Ray& r, MathUtil::Interval interval, HitRecord& record) const = 0;
};

GraphicObjects.cpp Sphere::hit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool Sphere::hit(const MathUtil::Ray &r, MathUtil::Interval interval, HitRecord& record) const {
MathUtil::Point3 oc = r.pos() - position;
auto a = r.dir().lengthSq();
auto h = oc.dot(r.dir());
auto c = oc.lengthSq() - radius * radius;

auto discriminant = h * h - a * c;
if (discriminant < 0) return false;
auto discri_sqrt = std::sqrt(discriminant);

auto root = (-h - discri_sqrt) / a;
if (!interval.surround(root)) {
root = (-h + discri_sqrt) / a;
if (!interval.surround(root)){
return false;
}
}
record.t = root;
record.p = r.at(root);
auto out_normal = (record.p - position) / radius;
record.setFaceNormal(r, out_normal);
return true;
}

GraphicObjects.cpp HittableList::hit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool HittableList::hit(const MathUtil::Ray &r, MathUtil::Interval interval, HitRecord &record) const {
HitRecord temp_rec;
bool if_hit = false;
auto closest_t = interval.max;

bool HittableList::hit(const MathUtil::Ray &r, MathUtil::Interval interval, HitRecord &record) const {
HitRecord temp_rec;
bool if_hit = false;
auto closest_t = interval.max;

for (const auto& i : objects) {
if (i->hit(r, MathUtil::Interval(interval.min, closest_t), temp_rec)) {
if_hit = true;
closest_t = temp_rec.t;
record = temp_rec;
}
}
return if_hit;
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
ImageUtil::Color rayColor(const MathUtil::Ray& ray, const IHittable& object) {
ImageUtil::Color color;
HitRecord record;
if (object.hit(ray, MathUtil::Interval(0, INF), record)) {
color = 0.5 * (record.normal + ImageUtil::Color(1, 1, 1));
}
else {
auto unit = ray.dir().unit();
color = ImageUtil::Color(std::min(0.5 * unit.x + 1, 1.0), std::min(0.5 * unit.y + 1, 1.0), 1);
}
return color;
}

More versatile canera

With world, we can now render a scene with multiple objects. However, our code is still a little bit messy, with drawing code and color mapping code scrambled in one place. We can wrap the draw code and render code to camera, since camera is the one actually doing rendering.

icon-padding

Here we assume you did the class wrap in part 0

GraphicObjects.h: Camera

1
2
3
4
5
6
7
8
9
10
class Camera {
public:
// ...unchanged

void Render(const IHittable& world);

ImageUtil::Color rayColor(const MathUtil::Ray& ray, const IHittable& object);

// ...unchanged
};

GraphicObjects.cpp: Camera::Render

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Camera::Render(const IHittable& world) {
auto img = std::vector<std::vector<ImageUtil::Color>>();

for (int i = 0; i < getHeight(); ++i) {
auto v = std::vector<ImageUtil::Color>();
if (i % 10 == 0 || i > getHeight() - 10) spdlog::info("line remaining: {}", (getHeight() - i + 1));
for (int j = 0; j < getWidth(); ++j) {
auto ray_dir = getPixRayDir(j, i);
auto ray = MathUtil::Ray(getPosition(), ray_dir);
auto color = rayColor(ray, world);
v.emplace_back(color);
}
img.emplace_back(v);
}
ImageUtil::makePPM(getWidth(), getHeight(), img, "out", "test.ppm");

}

icon-padding

In this case we have to move the definition of IHittable and HitRecord to before rayColor, since we need to use them in the rayColor function.

GraphicObjects.cpp: Camera::rayColor

1
2
3
4
5
6
7
8
9
10
11
12
ImageUtil::Color Camera::rayColor(const MathUtil::Ray &ray, const IHittable &object) {
ImageUtil::Color color;
HitRecord record;
if (object.hit(ray, MathUtil::Interval(0, INF), record)) {
color = 0.5 * (record.normal + ImageUtil::Color(1, 1, 1));
}
else {
auto unit = ray.dir().unit();
color = ImageUtil::Color(std::min(0.5 * unit.x + 1, 1.0), std::min(0.5 * unit.y + 1, 1.0), 1);
}
return color;
}

After modification, our main function become extremely clean:

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 "MathUtil.h"
#include "GraphicObjects.h"
#include <vector>
#include "GlobUtil.hpp"
#include <cmath>

int main() {
auto camera = Camera(1920, 16.0 / 9.0, 2.0, 1, MathUtil::Point3(0, 0, 0));
auto world = HittableList();
world.add(std::make_shared<Sphere>(Sphere(10, {0, -10, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {5, 0, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {-5, 0, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {0, 0, -15})));

camera.Render(world);

return 0;
}

And the result stays the same:

modified

main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "ImageUtil.h"
#include "MathUtil.h"
#include "GraphicObjects.h"
#include <vector>
#include "GlobUtil.hpp"
#include <cmath>



int main() {
auto camera = Camera(1920, 16.0 / 9.0, 2.0, 1, MathUtil::Point3(0, 0, 0));
auto world = HittableList();
world.add(std::make_shared<Sphere>(Sphere(10, {0, -10, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {5, 0, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {-5, 0, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {0, 0, -15})));

camera.Render(world);

return 0;
}
MathUtil.h
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"
#include "MathUtil.h"
#include "GraphicObjects.h"
#include <vector>
#include "GlobUtil.hpp"
#include <cmath>



int main() {
auto camera = Camera(1920, 16.0 / 9.0, 2.0, 1, MathUtil::Point3(0, 0, 0));
auto world = HittableList();
world.add(std::make_shared<Sphere>(Sphere(10, {0, -10, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {5, 0, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {-5, 0, -5})));
world.add(std::make_shared<Sphere>(Sphere(2, {0, 0, -15})));

camera.Render(world);

return 0;
}

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

#ifndef ONEWEEKEND_MATHUTIL_H
#define ONEWEEKEND_MATHUTIL_H

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

namespace MathUtil {
// ...unchanged

class Interval {
public:
double min, max;
Interval();
Interval(double min, double max);

bool within(double x) const;

bool surround(double x) const;

static const Interval empty, universe;
};
const static Interval empty(+INF, -INF);
const static Interval universe(-INF, +INF);

// ...unchanged
}

#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
#include "MathUtil.h"

namespace MathUtil{

// ...unchanged
Interval::Interval() : min(-INF), max(INF) {

}
Interval::Interval(double min, double max): min(min), max(max) {

}
bool Interval::within(double x) const {
return (min <= x) && (x <= max);
}
bool Interval::surround(double x) const {
return (min < x) && (x < max);
}

// ...unchanged
}

Associated files

GraphicObjects.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
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

#ifndef ONEWEEKEND_GRAPHICOBJECTS_H
#define ONEWEEKEND_GRAPHICOBJECTS_H

#include "MathUtil.h"
#include "ImageUtil.h"
#include <utility>
#include <vector>
#include <memory>

struct HitRecord {
bool hit;
MathUtil::Point3 p;
double t;
MathUtil::Vec3 normal;
bool front_face;
void setFaceNormal(const MathUtil::Ray& r, const MathUtil::Vec3& normal_out);
};

class IHittable {
public:
virtual ~IHittable() = default;

virtual bool hit(const MathUtil::Ray& r, MathUtil::Interval interval, HitRecord& record) const = 0;
};

class Sphere : public IHittable {
public:
Sphere(double radius, MathUtil::Vec3 position);

bool hit(const MathUtil::Ray &r, MathUtil::Interval interval, HitRecord& record) const override;

private:
double radius;
MathUtil::Vec3 position;
};

class Camera {
public:
Camera(int width, double aspect_ratio, double viewport_width, double focal_len, MathUtil::Point3 position);

int getWidth() const;

int getHeight() const;

double getAspectRatio() const;

double getViewportWidth() const;

double getViewportHeight() const;

double getFocalLen() const;

const MathUtil::Point3 &getPosition() const;

const MathUtil::Vec3 &getHoriVec() const;

const MathUtil::Vec3 &getVertVec() const;

const MathUtil::Vec3 &getPixDeltaX() const;

const MathUtil::Vec3 &getPixDeltaY() const;

const MathUtil::Point3 &getViewportUl() const;

const MathUtil::Point3 &getPixel00() const;

void setWidth(int width);

void setAspectRatio(double aspect_ratio);

void setFocalLen(double focal_len);

void setPosition(const MathUtil::Point3 &position);

[[nodiscard]] MathUtil::Vec3 getPixelVec(int x, int y) const;

[[nodiscard]] MathUtil::Vec3 getPixRayDir(int x, int y) const;

void Render(const IHittable& world);

ImageUtil::Color rayColor(const MathUtil::Ray& ray, const IHittable& object);

private:

void updateVectors();

int width;
int height;
double aspect_ratio;
double viewport_width;
double viewport_height;
double focal_len;
MathUtil::Point3 position;
MathUtil::Vec3 hori_vec;
MathUtil::Vec3 vert_vec;
MathUtil::Vec3 pix_delta_x;
MathUtil::Vec3 pix_delta_y;
MathUtil::Point3 viewport_ul;
MathUtil::Point3 pixel_00;
};

class HittableList: public IHittable {
public:
std::vector<std::shared_ptr<IHittable>> objects;

HittableList() = default;

explicit HittableList(const std::shared_ptr<IHittable>& obj);

void add(const std::shared_ptr<IHittable>& obj);

void clear();

auto begin();

auto end();

private:
bool hit(const MathUtil::Ray &r, MathUtil::Interval interval, HitRecord &record) const override;
};

#endif //ONEWEEKEND_GRAPHICOBJECTS_H

GraphicObjects.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

#include "GraphicObjects.h"



void Camera::Render(const IHittable& world) {
auto img = std::vector<std::vector<ImageUtil::Color>>();

for (int i = 0; i < getHeight(); ++i) {
auto v = std::vector<ImageUtil::Color>();
if (i % 10 == 0 || i > getHeight() - 10) spdlog::info("line remaining: {}", (getHeight() - i + 1));
for (int j = 0; j < getWidth(); ++j) {
auto ray_dir = getPixRayDir(j, i);
auto ray = MathUtil::Ray(getPosition(), ray_dir);
auto color = rayColor(ray, world);
v.emplace_back(color);
}
img.emplace_back(v);
}
ImageUtil::makePPM(getWidth(), getHeight(), img, "out", "test.ppm");

}

Camera::Camera(int width, double aspect_ratio, double viewport_width, double focal_len,
MathUtil::Point3 position) : width(width), aspect_ratio(aspect_ratio),
viewport_width(viewport_width), focal_len(focal_len),
position(std::move(position)),
height(static_cast<int>(width / aspect_ratio)),viewport_height(viewport_width / (static_cast<double>(width) / static_cast<double>(height))) {
updateVectors();

}

void Camera::updateVectors() {
hori_vec = MathUtil::Vec3(viewport_width, 0, 0);
vert_vec = MathUtil::Vec3(0, -viewport_height, 0);
pix_delta_x = hori_vec / width;
pix_delta_y = vert_vec / height;
viewport_ul = position - MathUtil::Vec3(0, 0, focal_len) - (vert_vec + hori_vec) / 2;
pixel_00 = viewport_ul + (pix_delta_y + pix_delta_x) * 0.5;
}

int Camera::getWidth() const {
return width;
}

int Camera::getHeight() const {
return height;
}

double Camera::getAspectRatio() const {
return aspect_ratio;
}

double Camera::getViewportWidth() const {
return viewport_width;
}

double Camera::getViewportHeight() const {
return viewport_height;
}

double Camera::getFocalLen() const {
return focal_len;
}

const MathUtil::Point3 &Camera::getPosition() const {
return position;
}

const MathUtil::Vec3 &Camera::getHoriVec() const {
return hori_vec;
}

const MathUtil::Vec3 &Camera::getVertVec() const {
return vert_vec;
}

const MathUtil::Vec3 &Camera::getPixDeltaX() const {
return pix_delta_x;
}

const MathUtil::Vec3 &Camera::getPixDeltaY() const {
return pix_delta_y;
}

const MathUtil::Point3 &Camera::getViewportUl() const {
return viewport_ul;
}

const MathUtil::Point3 &Camera::getPixel00() const {
return pixel_00;
}

void Camera::setWidth(int width) {
Camera::width = width;
height = static_cast<int>(width / aspect_ratio);
updateVectors();
}

void Camera::setAspectRatio(double aspect_ratio) {
Camera::aspect_ratio = aspect_ratio;
height = static_cast<int>(width / aspect_ratio);
viewport_height = viewport_width / aspect_ratio;
updateVectors();
}

void Camera::setFocalLen(double focal_len) {
Camera::focal_len = focal_len;
updateVectors();
}

void Camera::setPosition(const MathUtil::Point3 &position) {
Camera::position = position;
updateVectors();
}

MathUtil::Vec3 Camera::getPixelVec(int x, int y) const {
return pixel_00 + pix_delta_x * x + pix_delta_y * y;
}

MathUtil::Vec3 Camera::getPixRayDir(int x, int y) const {
return getPixelVec(x, y) - position;
}
ImageUtil::Color Camera::rayColor(const MathUtil::Ray &ray, const IHittable &object) {
ImageUtil::Color color;
HitRecord record;
if (object.hit(ray, MathUtil::Interval(0, INF), record)) {
color = 0.5 * (record.normal + ImageUtil::Color(1, 1, 1));
}
else {
auto unit = ray.dir().unit();
color = ImageUtil::Color(std::min(0.5 * unit.x + 1, 1.0), std::min(0.5 * unit.y + 1, 1.0), 1);
}
return color;
}


Sphere::Sphere(double radius, MathUtil::Vec3 position) : radius(radius), position(std::move(position)) {
}

bool Sphere::hit(const MathUtil::Ray &r, MathUtil::Interval interval, HitRecord& record) const {
MathUtil::Point3 oc = r.pos() - position;
auto a = r.dir().lengthSq();
auto h = oc.dot(r.dir());
auto c = oc.lengthSq() - radius * radius;

auto discriminant = h * h - a * c;
if (discriminant < 0) return false;
auto discri_sqrt = std::sqrt(discriminant);

auto root = (-h - discri_sqrt) / a;
if (!interval.surround(root)) {
root = (-h + discri_sqrt) / a;
if (!interval.surround(root)){
return false;
}
}
record.t = root;
record.p = r.at(root);
auto out_normal = (record.p - position) / radius;
record.setFaceNormal(r, out_normal);
return true;
}

void HitRecord::setFaceNormal(const MathUtil::Ray &r, const MathUtil::Vec3 &normal_out) {
front_face = normal_out.dot(r.dir()) < 0;
normal = front_face ? normal_out : -normal_out;
}

auto HittableList::end() {
return objects.end();
}

bool HittableList::hit(const MathUtil::Ray &r, MathUtil::Interval interval, HitRecord &record) const {
HitRecord temp_rec;
bool if_hit = false;
auto closest_t = interval.max;

for (const auto& i : objects) {
if (i->hit(r, interval, temp_rec)) {
if_hit = true;
closest_t = temp_rec.t;
record = temp_rec;
}
}
return if_hit;
}

HittableList::HittableList(const std::shared_ptr<IHittable>& obj) {
add(obj);
}

void HittableList::add(const std::shared_ptr<IHittable>& obj) {
objects.push_back(obj);
}

void HittableList::clear() {
objects.clear();
}

auto HittableList::begin() {
return objects.begin();
}

directory layout

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
.
|-- LICENSE tree-sitter treereg treereg5.30 treereg5.34
|-- makeTikz.py
|-- part0
| |-- img
| | |-- result.png
| | `-- result2.png
| `-- part0.md
|-- part1
| |-- img [10 entries exceeds filelimit, not opening dir]
| |-- part1.md
| `-- part1Display.md
|-- part2
| |-- img
| | |-- intersections
| | | `-- intersections.tex
| | |-- intersections.svg
| | |-- shadedSphere.png
| | |-- sphere.png
| | |-- sphereNormal
| | | |-- sphereNormal.pdf
| | | |-- sphereNormal.synctex.gz
| | | `-- sphereNormal.tex
| | |-- sphereNormal.svg
| | `-- tikz
| | |-- sphereIntersect.pdf
| | |-- sphereIntersect.synctex.gz
| | |-- sphereIntersect.tex
| | |-- surfaceNormal.pdf
| | |-- surfaceNormal.synctex.gz
| | `-- surfaceNormal.tex
| `-- part2.md
|-- part3
| |-- img
| | |-- InsideOrOut
| | | `-- InsideOrOut.tex
| | |-- InsideOrOut.svg
| | |-- modification.png
| | |-- tikz
| | | |-- inside.pdf
| | | |-- inside.synctex.gz
| | | `-- inside.tex
| | `-- world.png
| `-- part3.md
`-- pdf2svg.py

Comments