In this post, I try to implement a simple Ray-Tracing algorithm which support to render some basic shape (sphere, box, plane, disk). And the corresponding codes can be downloaded in hudongyue1/myGL (github.com) The reference is Scratchapixel.

1. The Whole Pipeline

1-pipeline for simple ay tracer

The picture above shows the whole procedure in my simple ray-tracer.

2. Tool functions and Classes

Degree to radians

1
2
3
4
inline
float deg2rad(const float &deg) {
return deg * M_PI / 180;
}

Clamp

1
2
3
4
inline
float clamp(const float &lo, const float &hi, const float &v) {
return std::max(lo, std::min(hi, v));
}

MixColor

1
2
3
4
inline
Vec3f mix(const Vec3f &a, const Vec3f& b, const float &mixValue){
return a * (1 - mixValue) + b * mixValue;
}

The parent class for all shape

Different kind of shape should implement these virtual function separately, and the detailed method could be found in the previous post: Scratchapixel: Intersection.

1
2
3
4
5
6
7
8
9
10
11
12
class Object {
public:
Object() : color(dis(gen), dis(gen), dis(gen)) {}
virtual ~Object() {}

// weather intersect and the distance of hit point
virtual bool intersect(const Vec3f &, const Vec3f &, float &) const = 0;

// get the surface date for texture in the hit point
virtual void getSurfaceData(const Vec3f &, Vec3f &, Vec2f &) const = 0;
Vec3f color;
};

3. Important Functions

render

The render could be splited as two steps:

  • Iterating over each pixel of the image, and calling the function to cast a ray for each pixel.

  • Using the result of as the color for each pixel, then storing the result to build the image.

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
void render(const Options &options, const std::vector<std::unique_ptr<Object>> &objects) {
Vec3f *framebuffer = new Vec3f[options.width * options.height];
Vec3f *pix = framebuffer;

float scale = tan(deg2rad(options.fov * 0.5));
float imageAspectRatio = options.width / (float) options.height;

Vec3f orig;
options.cameraToWorld.multVecMatrix(Vec3f(0), orig);

for(uint32_t j=0; j<options.height; ++j) {
for(uint32_t i=0; i<options.width; ++i) {
float x = (2 * (i + 0.5) / (float) options.width - 1) * scale * imageAspectRatio;
float y = (1 - 2 * (j + 0.5) / (float) options.height) * scale;

Vec3f dir;
options.cameraToWorld.multDirMatrix(Vec3f(x, y, -1), dir);
dir.normalize();
*(pix++) = castRay(orig, dir, objects);
}
}

std::ofstream ofs("./out/out.ppm", std::ios::out | std::ios::binary);
ofs << "P6\n" << options.width << " " << options.height << "\n225\n";
for(uint32_t i=0; i<options.height * options.width; ++i) {
char r = (char)(255*clamp(0, 1, framebuffer[i].x));
char g = (char)(255*clamp(0, 1, framebuffer[i].y));
char b = (char)(255*clamp(0, 1, framebuffer[i].z));
ofs << r << g << b;
}

ofs.close();

delete [] framebuffer;
}

castRay

Using the result of to compute the color for the specific ray.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vec3f castRay(const Vec3f &orig, const Vec3f &dir, const std::vector<std::unique_ptr<Object>> &objects) {
Vec3f hitColor = 0;
const Object *hitObject = nullptr;
float t;
if(trace(orig, dir, objects, t, hitObject)) {
Vec3f Phit = orig + dir * t;
Vec3f Nhit;
Vec2f tex;
hitObject->getSurfaceData(Phit, Nhit, tex);

float scale = 4;
float pattern = (fmodf(tex.x * scale, 1) > 0.5) ^ (fmodf(tex.y * scale, 1) > 0.5);
hitColor = std::max(0.f, Nhit.dotProduct(-dir)) * mix(hitObject->color, hitObject->color * 0.8, pattern);
}

return hitColor;
}

trace

Iterating all the objects in world space, finding the first intersection with these objects for the specific ray (if do not parallel).

1
2
3
4
5
6
7
8
9
10
11
12
13
bool trace(const Vec3f &orig, const Vec3f &dir, const std::vector<std::unique_ptr<Object>> &objects, float &tNear, const Object *&hitObject) {
tNear = kInfinity;
std::vector<std::unique_ptr<Object>>::const_iterator iter = objects.begin();
for(; iter != objects.end(); ++iter) {
float t = kInfinity;
if((*iter)->intersect(orig, dir, t) && t < tNear) {
hitObject = iter->get();
tNear = t;
}
}

return (hitObject != nullptr);
}