Software rasterizer

Software rasterizer

In this project, I wrote a software rasterizer for polygonal meshes. Essentially, this involved:

  • Loading a .obj polygonal mesh.
  • Triangulate each polygonal face.
  • For each triangle:
    • Transform its vertices to camera space, project them to screen space, divide them by their z-coordinate to perform perspective diminishing, and then transform them from NDC coordinates to pixel coordinates.
    • Cull it if it lies completely behind the near plane or beyond the far plane of the camera.
    • Compute its axis-aligned bounding box in pixel space, clamped to the extents of the image.
    • For each pixel row contained by the bounding box:
      • Determine the interval of pixels in the row that the triangle overlaps.
      • For each pixel in this interval (a triangle fragment):
        • Compute its barycentric coordinates in relation to the triangle’s vertices in pixel space.
        • Use these barycentric coordinates to compute the fragment’s z-coordinate in a perspective-correct way.
        • Perform the depth test and update the z-buffer.
        • Use the barycentric coordinates to interpolate vertex attributes in a perspective-correct way in pixel space. Vertex attributes include normal and texture UV coordinate.
        • Perform back-face culling of the triangle based on the orientation of the interpolated normal with repect to the camera.
        • Evaluate a reflection model (Lambertian diffuse reflection) to obtain the fragment’s color. The camera acts as a flashlight; Lambert’s cosine law can be evaluated with the interpolated normal in pixel space and the light vector in camera space.

Because of the distortion that results from a perspective projection, we can’t simply use the barycentric coordinates of the fragment in pixel space to interpolate vertex normals and UVs. The method to perform perspective-correct interpolation that I implemented here is explained in Rasterization: a Practical Implementation.

An additional feature that I decided to implement was normal mapping. This involved computing a tangent-bitangent-normal frame of reference for tangent space with respect to world space out of world-space vertex positions and UVs. The inverse of the corresponding homogeneous matrix can then be used to transform the world-space light vector to tangent space to compute Lambert’s cosine law term with a tangent-space normal read from a normal map.

Without normal mappingWith normal mapping
Without normal mappingWith normal mapping
Without normal mappingWith normal mapping
Without normal mappingWith normal mapping