The goal of this project was to gain a deep understanding of 3D graphics rendering pipelines and apply that knowledge by implementing both a rasteriser and a ray-tracer in GLSL. The program reads a 3D model from an OBJ file, processes its geometry and materials, and outputs a fully rendered image in BMP format.
The Rasteriser required:
Vertex transformations using a transformation matrix
Perspective Division
Viewport Transformation
Calculating barycentric coordinates
The Raytracer (Whitted Raytracer) required:
Calculating a ray direction
Ray Triangle Intersection
Light Contribution (Lambert’s Law)
The Shade function computes the colour at a point on a triangle using a simple Whitted-style shading model. It begins with a small ambient contribution taken from the triangle’s colour, then calculates the surface normal and the direction towards the light. A shadow ray is traced from the point in the direction of the light to determine whether the point is in shadow. If the point is not in shadow, a diffuse component is added according to Lambert’s law (dot(normal, lightDir)). If the triangle is reflective and the recursion depth has not been exceeded, a reflection ray is traced and a portion of the reflected colour is added. The function returns the combined ambient, diffuse, and reflection contributions as the final colour.
PointInTriangle determines whether a point lies inside a triangle in 3D space. It does this by comparing the orientation of the point relative to each edge of the triangle. For each vertex, it computes vectors along the edges and from the vertex to the point, then takes the cross product of these vectors. The dot product of the resulting cross products is used to check if the point is on the same side of the edge as the triangle’s interior. If the dot products for all three edges are positive, the point is inside the triangle; otherwise, it lies outside.
RayTriangleIntersection uses the Möller–Trumbore algorithm to efficiently test whether a ray intersects a triangle. It computes two triangle edges and uses the ray direction to find barycentric coordinates (u and v). If the coordinates satisfy 0 ≤ u ≤ 1, 0 ≤ v ≤ 1, and u + v ≤ 1, the intersection lies within the triangle. The distance along the ray (t) is computed, and the intersection point is stored if valid. If the ray misses the triangle, is parallel, or the intersection lies outside, the function returns FLT_MAX to indicate no hit.