The triangle class

A lot of 3d models used in computer graphics consist of triangles. Almost all 3d modeling software can export to triangulated meshes and so it’s important to support triangles in our raytracer too.

Our triangle class will be extend from the virtual hittable class like this:

class Triangle : public Hittable { public: Triangle(int A, int B, int C, std::vector<Vertex>* vertices, Material* mat); virtual bool hit(const Ray& r, float t_min, float t_max, hitRecord& rec)const; private: std::vector<Vertex>* vertices; int A, B, C; Material* matPtr; };

A triangle consists of tree index values A, B and C for the list of Vertices. This list contains all the vertices of the mesh which a triangle is part off, but we will discuss that later.

A vertex is a point in space defined by a coördinate. additionally a vertex can also contain information about the color, normal vector, texture, and everything else needed by the software. I defined my vertex as a struct like this:

struct Vertex { Vertex() {} Vertex(vec3 pos, vec3 norm) : Position(pos), Normal(norm) {} vec3 Position; vec3 Normal; };

Other values can be added later if needed.

To finish the triangle class we need to implement the hit function. I will be using the Ray triangle intersection methode by Thomas Möller and Ben Trumbore.

bool Triangle::hit(const Ray& r, float t_min, float t_max, hitRecord& rec)const { const float EPSILON = 0.0000001; vec3 edge1, edge2, h, s, q, ca, cb, cc; float a, f, u, v; ca = vertices->at(A).Position; cb = vertices->at(B).Position; cc = vertices->at(C).Position; edge1 = cb - ca; edge2 = cc - ca; h = cross(r.direction(), edge2); a = dot(edge1, h); if (a > -EPSILON && a < EPSILON) return false; // This ray is parallel to this triangle. f = 1.0 / a; s = r.origin() - ca; u = f * dot(s, h); if (u < 0.0 || u > 1.0) return false; q = cross(s, edge1); v = f * dot(r.direction(), q); if (v < 0.0 || u + v > 1.0) return false; // At this stage we can compute t to find out where the intersection point is on the line. float t = f * dot(edge2, q); if (t > EPSILON && t < 1 / EPSILON) // ray intersection { if (t < t_max && t > t_min) //object closer than previous { rec.t = t; rec.p = r.origin() + r.direction() * t; rec.normal = this->getNormal(); rec.matPtr = matPtr; return true; } else return false; //this means there is a ray intersection, but it is behind a differend object } else // This means that there is a line intersection but not a ray intersection. return false; }

I only adjusted it slightly to work with the architecture of this raytracer.

The triangle mesh

A triangle mesh is a way to store an object made out of triangles efficiently. It consists of one big list of vertices that is shared by all the triangles. As you can see in the triangle class above, a triangle only knows the position of it’s vertices in the list.

For example looking at the triangles in the image, we only need a list of vertices A, B, C and D. Then we make two triangles with a reference to this list and the triangles indexes. Vertexlist {A, B, C, D}

UpperTriangle(0, 1, 2)

LowerTriangle(1, 2, 3)

The mesh class looks like this:

class Mesh : public Hittable { public: Mesh(std::vector<Vertex> vertices, std::vector<unsigned int> indices, Material* matPtr); virtual bool hit(const Ray& r, float t_min, float t_max, hitRecord& rec)const; private: std::vector<Triangle> triangles; std::vector<Vertex> vertices; };

The constructor creates the triangles by looping through the indices. The hit function loops over all the triangles for now, we will optimise this later, but I focus on features for now. An easy improvement is adding a hitbox using AABB collision detection in the hit function. More info about that can be found here.

I am using Assimp for loading the models into my own datastructure. It is realy straightforeward so I won’t cover that here. There are plenty of examples or read the documentation here.

Using the new mesh and triangle classes we are able to render images like this!

I hope to see you in the next post!

Ruurd