CS 184/284A: Computer Graphics and Imaging, Spring 2024

Homework 1: Rasterizer

Author: Jian Yu🐟, Xiaoyu Zhu🐷

website

Overview

In this homework, we implemented a series of graphic rendering techniques, including rasterizing single-color triangles, antialiasing triangles, geometric transformations (translation, scaling, rotation), and different sampling techniques for color interpolation and texture mapping. These techniques together build a basic yet comprehensive 2D graphic rendering pipeline, covering not only the fundamental drawing of graphics but also methods for improving image quality and implementing advanced effects.

From completing this homework, we learned many interesting points. First, by implementing the rasterization algorithm, we gained a deep understanding of how to convert geometric shapes into pixel data, which is the foundation of all graphic rendering tasks. Second, through the application of antialiasing techniques, we learned how to improve the visual effects of rendered images, especially in terms of edge smoothing. Moreover, the implementation of geometric transformations allowed us to manipulate the position, size, and orientation of shapes without altering their intrinsic properties. Finally, exploring sampling techniques taught us how to effectively apply texture mapping and understand how to reduce aliasing issues with mipmaps at different viewpoints and distances, improving rendering effects while maintaining good performance.

Section I: Rasterization

Part 1: Rasterizing single-color triangles

Implement the rasterize_triangle function in rasterizer.cpp to rasterize single-color triangles.

Our inputs are the coordinates of three points and the color of this triangle(x0, y0, x1, y1, x2, y2, color), with the goal of performing the rasterize_point operation on points that are inside the triangle.

To simplify the computation, instead of traversing the entire image, we first derive the enclosing frame of this triangle from the information of the three points:

Only points with x values between x_min, x_max and y values between y_min, y_max can be in the triangle.

And then we iterate over the integer points in this range, adding 0.5 to x and y and using the barycentric coordinates to determine if they are inside the triangle:

For rasterization acceleration, we consider using the intersecting lines algorithm. But as soon as we started using this algorithm, we realized that the results showed some horizontal lines that should not be there. So we realized that we did not handle the horizontal lines very well. Finally, we used the following algorithm.

  1. Determine the relative positions of the three vertices to determine whether it is a flat top/flat bottom or a general triangle

  2. Split the general triangle horizontally into a flat-bottomed and a flat-topped triangle with a vertex in the center

  3. Generate new boundary points on the long side by interpolation

  4. Run the rasterization algorithm for the top/bottom triangles separately for the flat/flat tops.

  5. Each time a line is drawn, two boundary points are generated by interpolation, and the portion between the boundary points is drawn.

We first determine the order of y-coordinates of the three points

Discussion of different scenarios based on the algorithm

To implement rasterize_flat_top_triangle, the following actions are taken:

  1. Get coordinates of points with non-flat tops

  2. Calculate the slopes of the two hypotenuse edges, calculating the coordinates of the points from both sides upwards in turn

  3. Draw the entire calculated line directly

Result comparison

We can find both increases and decreases in running time, and generally an improvement in very complex drawings.

Part 2: Antialiasing triangles
Part 3: Transforms

Implement translate, scale, rotate

orginal robot:

robot

Our new robot who is dancing:

our_robot

Section II: Sampling

Part 4: Barycentric coordinates

* Explaination of barycentric coordinates

Barycentric coordinates offer a way to express the position of a point within a triangle (or more generally, within a simplex in higher dimensions) using the vertices of that triangle as reference points. These coordinates are a set of three numbers (in the case of a triangle) that represent the weights or influence each vertex of the triangle has on a specific point inside it. The key property of barycentric coordinates is that they sum up to 1.

magine you have a triangle with vertices A, B, and C. Any point P inside (or on the edges of) this triangle can be described using barycentric coordinates (α, β, γ), where α represents the influence of vertex A, β the influence of vertex B, and γ the influence of vertex C on the point P. The values of α, β, and γ tell you how "close" P is to each vertex, with the sum α + β + γ = 1 ensuring that P is indeed within the triangle defined by A, B, and C.

rgb_triangle

As shown above, its three vertices are red, green, and blue, and the other colors are determined by the coordinates of their centers of gravity.

Implement RasterizerImp::rasterize_interpolated_color_triangle(...) to draw a triangle with colors defined at the vertices and interpolated across the triangle area using barycentric interpolation.

Just as the code in Task 1, we first get the bounding box of the triangle. Then for pixels in the box, we calculate the barycentric coordinates of this pixel (α,β,γ)

With the coordinates of the center of gravity about the three points, we can get the color of this point by weighting.

Part 5: "Pixel sampling" for texture mapping

Implement texture mapping, including nearest sampling and billear sampling

The top left corner is nearest sampling with supersample 1, the bottom left corner is bilinear sampling with supersample 16, the top right corner is nearest sampling with supersample 16, and the bottom right corner is bilinear sampling with supersample 16. We can find that in the case of this thin bar, nearest sampling leads to undersampling and discontinuities. This situation is improved if bilinear sampling is used. If supersampling is added, this situation can also be directly improved due to the direct increase of the sampling value.

Part 6: "Level sampling" with mipmaps for texture mapping

We use the reference output images given for 01_degenerate_square1.svg file in hardcore folder to show performance for four different combinations, because this texture has details on different levels. From left to right, from top to bottom, they are respectively L_ZERO and P_NEAREST, L_ZERO and P_LINEAR, L_NEAREST and P_NEAREST, L_NEAREST and P_LINEAR.

We can see that only sampling on level 0 results in a moiré pattern appearing along the diagonal. The results obtained using P_LINEAR during sampling are smoother compared to P_NEAREST, but there is not a significant improvement. However, using the level sampling method noticeably improves the moiré pattern.