Assignment 1: Rasterizer

Modern GPUs implement an abstraction called the Rasterization Pipeline. This abstraction breaks the process of converting 3D triangles into 2D pixels into several highly-parallel stages, allowing for a variety of efficient hardware implementations. In this assignment, you will be implementing parts of a simplified rasterization pipeline in software. Though simplified, your pipeline will be sufficient to allow Scotty3D to create preview renders without a GPU!

Different graphics APIs may present this pipeline in different ways, but the core steps remains consistent: a GPU draws things by running code (in parallel) on a list of vertices to produce homogeneous screen positions (+ extra varying data), building triangles from that list of vertices, clipping the triangles to remove parts not visible on the screen, performing a division to compute screen positions, computing a list of "fragments" covered by those triangles, running code on each fragment, and composing the results into a framebuffer.

Read through src/rasterizer/pipeline.h to understand how our code implements this pipeline. You will be filling in functions in src/rasterizer/pipeline.cpp (as well as several other files) to complete this implementation.


Total [100pts]:

Hand-in Instructions

To turn in your work, make sure you have all the files in the proper place and then run the following command in your root directory:

$ tar cvzf handin.tgz src/ tests/a1/ writeup.txt render.s3d render.png



You will submit a short document explaining how you believe your implementation performs on each of the tasks. The project directory contains a template in writeup.txt. For each task, do either of the following: * If you believe that you have correctly implemented the required functions, simply leave the text "Fully implemented." * If your implementation does not fully function, write a short explanation of what your code can and cannot do, and (optionally) briefly explain your strategy and how you got stuck. If your explanation indicates significant understanding of the problem, we may award partial credit even though the code does not work.

Render Something You Like!

At the end of the assignment, we want you to bask in the glory of successfully implementing a software rasterizer by generating a rendered image to your liking, using what you just wrote. Name this file render.png and add it to your root directory.

The most Outrun-styled render, as selected by the course staff, will be awarded a small prize. (To be eligible for the prize you must also include the source scene for your render as render.s3d in your turn-in.)

How To Test What You've Made

Writing robust graphics code is hard. You'll need to spend some time testing your code to make sure that it works. You have three ways of doing that.

via Test Cases

Writing automated test cases can help you isolate and test individual functions in your code.

For the tasks below we provide a basic set of functionality tests in the corresponding tests/a1/test.a1.*.cpp file(s). You can run these tests with ./Scotty3D --run-tests a1..

Be Warned, however, that we provide only basic functionality tests; when we grade your assignment, we will also (a) read your code and (b) use an expanded set of test cases.

You may add test cases by creating new .cpp files in the tests/a1/ folder. You may refer to the existing tests for examples of how to create test cases, but in general you should include test.h header and throw errors if the actual output of the function is inconsistent with the expected value. We encourage you to develop and share test cases on Piazza. (Reminder: Test cases are the only source code you should ever post on our class Piazza.)

via GUI

Building and rendering scenes is a great way to catch subtle bugs in your code (and it can, sometimes, be fun!).

You can run the code interactively from the GUI:

via CLI

If you find yourself re-rendering scenes repeatedly, you can use the command line interface to Scotty3D instead of the GUI:

./Scotty3D --scene media/A1-basic.s3d --camera "Camera Instance" --rasterize --output A1-basic.png

(For more information about CLI parameters, run ./Scott3D --help.)

A1T1: Scene Functions

Your first task is to complete the scene graph implementation in Scotty3D by filling in two functions in the Transform class (used to represent object positions).

As discussed in our lecture on 3D transformations, Scotty3D represents the positions of objects in a scene with a scene graph.

Our scene graph (src/scene/scene.h) represents the transformations between object space and world space using Transform objects (src/scene/transform.h) that record the scaling, rotation, and translation (in that order!) that must be applied to move from object-local positions to positions relative to each object's parent.

Indeed, Transform::local_to_parent conveniently returns these transformations as a 4x4 matrix (Mat4).

Your job is to fill in Transform::local_to_world and Transform::world_to_local -- functions that return Mat4's representing the transformation from object space all the way to / from world space.

Note that Transform::parent is a std::weak_ptr< Transform >, so you'll need to call parent->lock() to retrieve a std::shared_ptr< Transform > from which to access the parent's member functions. A good way to do this is to use an if statement containing an init-statement. Thus, your code will probably look something like this:

Mat4 Transform::local_to_world() const {
  //A1T1: local_to_world
  if (std::shared_ptr< Transform > parent_ = parent.lock()) {
    //case where transform has a parent
  } else {
    //case where transform doesn't have a parent

A1T2: Lines

A1-cubes.s3d Before A1T2 is complete:

A1-cubes.s3d After A1T2 is complete:

Drawing a scene in wireframe (that is: drawing just the edges of triangles) is both a fast preview method and an interesting stylistic choice (e.g., the "Outrun" art style).

As a rasterization warm-up, fill in the Pipeline::rasterize_line( ... ) function. This function is used by the pipeline when drawing any mesh whose "wireframe" flag is set. More details of the function's specification are included in a block comment above the function in the source code. Read this specification carefully, as it contains some useful simplifications.

A1T3: Flat Triangles

A1-cubes.s3d After A1T3 is complete:

A step above wireframe is to draw triangles with flat shading -- that is, with all of their attributes copied from the first vertex.

Fill in the rasterize_triangle function for the case where the pipeline is using "flat" (every fragment gets the same attributes) interpolation. (You will also need to fix clip_triangle here, or triangles that go beyond the screen will do strange things.)

More details of the functions' specifications are included in a block comment above the function in the source code. Read this specification carefully, as it contains some useful simplifications.

Take care that if two triangles share an edge, and a sample point lies on that edge, exactly one of the triangles should emit a fragment for that sample. One way to handle this is the "top-left" rule used in Direct3D.

A1T4: Depth Testing and Blending

A1-cubes.s3d After A1T4 is complete:

After fragments are generated by rasterization, the way they are written to the framebuffer is controlled by the depth testing and blending mode of the pipeline. In this task, you'll fill in a few small equations to make depth testing and blending work.

A1T5: Triangles with Interpolation

A1-cubes.s3d After A1T5 is complete:

In this step you will complete the implementation of rasterize_triangle by completing screen-space and perspective-correct attribute interpolation. Please refer to the block comment above rasterize_triangle for details of these interpolation schemes.

A1T6: Mip-Mapping

Implement mipmap generation, sampling, and lod determination from derivatives. Refer to the block comments near each //A1T6 comment for more information.

A1T7: Supersampling

When scenes contain high-frequency detail, point samples don't do a good job of reconstructing them, resulting in "jaggies" or "aliasing" artifacts. One way to smooth away these artifacts is to check coverage and color at more than one point within each pixel and to make the final pixel color a weighted average of these points.

In this part of the assignment you will integrate such a "super-sampling" approach into our pipeline, by updating the code to:

Extra Credit:

You can receive extra credit on this assignment for going beyond the assignment parameters. Extra credits require you to do independent thinking and may require code restructuring. Extra credits will be graded based on both the quality of the results and the clarity of the code changes and write-up.

Do your extra credit work in a separate branch so that it does not interfere with our grading of T1-T7 -- you may turn this branch in as a separate extra/ directory.

Make it Fast

Perform optimizations to make your rasterizer faster without sacrificing correctness.

You might try:

In all of these cases you should develop a testing methodology to compare your revised rasterizer to your baseline rasterizer, and provide a clear description of why you believe your optimizations work.

Investigate Multisampling

Multisampling remains an interesting area of work. Many multisampling strategies go beyond the basic sample distributions and averaging strategies in our codebase. You can recieve extra credit on this assignment for trying some different multisampling strategies.

You might try:

In each of these cases you should provide a clear write-up describing how you implemented the ideas and provide comparisons showing how (/if) they improved image quality and/or rasterization efficiency.

Write Some Really Good Test Cases

Course staff will, at their discretion, may provide a small amount of extra credit to students who author particularly useful test cases and share them on the test case thread on Piazza.

Table of Content