Task 2: Antialiasing by Supersampling
Use supersampling to antialias your triangles. The sample_rate
parameter in DrawRend
(adjusted using the -
and =
keys) tells you how many samples to use per pixel.
The image below shows how sampling four times per pixel produces a better result than just sampling once. The fraction of the supersamples within the triangle yields a smoother edge.
To implement supersampling, please sample at sqrt(sample_rate) * sqrt(sample_rate)
grid locations distributed over the pixel area. (sample_rate is a member variable of the RasterizerImp class)
One reasonable way to think about supersampling is simply rasterizing an image that is higher resolution, then downsampling the higher resolution image to the output resolution of the framebuffer.
The original fill_pixel function used in Task 1 directly draws onto the framebuffer, but for supersampling, you should draw into the sample_buffer first, filling all the subsamples corresponding to the output pixel.
To reiterate the overall pipeline of the rasterizer:
- SVGParser parses the svg file into SVG class representation.
- When rasterization starts, the renderer (
DrawRend::redraw
) calls SVG::draw. SVG::draw
calls the specific line / triangle / point rasterization functions to generate the image primitive by primitive.DrawRend::redraw
calls line rasterization to draw the square boundary.DrawRend::redraw
callsRasterizerImp::resolve_to_framebuffer()
to translate the internal buffer of the rasterizer to the screenbuffer so the image can be displayed and written into a file.
Suggestions for this task:
- You will need to manage appropriate memory to store your supersample data. We recommend that you use the
RasterizerImp::sample_buffer
vector (see filerasterizer.h
) for this purpose. It depends on your algorithm, but it is likely that the size of the sample buffer you need will depend on the framebuffer dimensions (which changes when the window is resized) and the supersampling rate (which changes with keystrokes as described above). You will need to update the size of the buffer dynamically. There are hints below and in the code for where you may want to manage the size of your buffer. - Clear the values in your sample buffer memory and/or framebuffer appropriately at the beginning of redrawing the frame. This is erasing the frame before you start drawing.
- Update your
rasterize_triangle
function to perform supersampling into your supersample buffer memory. There are multiple ways to organize the data in your supersample buffer, and the choice is up to you.- Based on the above way to think about supersampling, your sample buffer is just a temporary, higher-resolution framebuffer. For example, 4x4 supersampling with a 1000x1000 pixel framebuffer means rasterizing a 4000x4000 (high-res) image of the scene into your sample buffer. After you rasterize the high-res image, you need to downsample to 1000x1000 final pixels by averaging down the 4x4 grid of sample values that are related to each output pixel. In this way of thinking, you need to store more memory in order to perform the high-res supersampled rasterization. (Test your understanding: can you achieve the same results without needing more memory, and if so, what are the engineering tradeoffs?)
- At the end of rasterizing all the scene elements, you will need to populate the framebuffer from your supersamples. This is sometimes called resolving the samples into the framebuffer. Notice that the
RasterizerImp::resolve_to_framebuffer
function is called as the last step in rendering the frame indrawrend.cpp
, so you may wish to implement this part of your algorithm here. - Note that you will need to convert between different color datatypes.
RasterizerImp::rgb_framebuffer_target
stores a pointer to the framebuffer pixel data that is finally drawn to the display.rgb_framebuffer_target
is an array of 8-bit values for each of the R, G and B components of each pixel’s color – this is the compact data format expected by most real graphics systems for drawing to the display. In contrast, theRasterizerImp::sample_buffer
variable that we suggest you use for your supersample memory is an array ofColor
objects that store R, G and B internally as floating point values. You may wish to familiarize yourself with theColor
class. You may need to convert between these datatypes. Watch out for floating point to integer conversion errors, such as rounding and overflow. - In most of graphics field, color is just the same as any other vector data: They are 3 dimensional (4 if it has the alpha channel). This means any XYZ vector representing a point or a direction could also represent a color. We will cover color space and color theory later in this course.
- You will likely find that points and lines stop rendering correctly after your supersampling modifications. Lines and points are not supersampled, but they still need to be drawn into the supersample buffer. Modify
RasterizerImp::fill_pixel
if needed to restore functionality. One way to think about this is to fill all the supersamples corresponding to the point or line with the same color, so it comes out as a single sampled pixel in the framebuffer. You do NOT need to antialias points and lines.
Once your implementation is complete, your triangle edges should look noticeably smoother when using more than one sample per pixel! You can examine the differences closely using the pixel inspector (see controls listed above). Also note that, it may take several seconds to switch to a higher sampling rate.
For convenience, here is a list of functions you will likely want to use or modify.
- For managing supersample buffer memory:
RasterizerImp::sample_buffer
,RasterizerImp::set_sample_rate()
,RasterizerImp::set_framebuffer_target()
,RasterizerImp::clear_buffers()
inrasterizer.h/cpp
. - To implement triangle supersampling:
RasterizerImp::rasterize_triangle()
,RasterizerImp::fill_pixel()
, inrasterizer.cpp
. - For resolving supersamples to framebuffer:
RasterizerImp::resolve_to_framebuffer()
.
Extra Credit (1 pt): Implement an alternative sampling pattern, such as jittered or low-discrepancy sampling. Create comparison images showing the differences between grid supersampling and your new pattern. Try making a scene that contains aliasing artifacts when rendered using grid supersampling but not when using your pattern.
For Your Write-Up: Task 2
- Walk through your supersampling algorithm and data structures. Why is supersampling useful? What modifications did you make to the rasterization pipeline in the process? Explain how you used supersampling to antialias your triangles.
- Show png screenshots of basic/test4.svg with the default viewing parameters and sample rates 1, 4, and 16 to compare them side-by-side. Position the pixel inspector over an area that showcases the effect dramatically; for example, a very skinny triangle corner. Explain why these results are observed.
- As a reminder, your screenshots need to be generated by using the
'S'
hotkey, not using your operating systems native screenshotting mechanism. You can refer back to our guide on Using the GUI on how to generate these images, and where to find them once they’re generated.
- As a reminder, your screenshots need to be generated by using the
- Extra credit: If you implemented alternative antialiasing methods, describe them and include comparison pictures demonstrating the difference between your method and grid-based supersampling.