In this project we will experiment with different antialiasing methods and add them onto the second lab in our course.
TAA was a bit harder to figure out how and where to implement. We found some research papers to help guide us right, but writing the code was the trickiest part.
TAA is a method that uses previous frames to blend and stabilise the image. So instead of SSAA where we check a sample pixel grid all the time, we instead compare the current frame to previous frames and blend pixel colors as well as adding jitter offsets, which would be the anti-aliasing part. A framebuffer is also created to help smooth the image overtime. We realized that while the anti-aliasing worked, and more effectively than SSAA, it did come with the cost of less image sharpness, most likely due to the jitter offsets. This method is mostly used in moving scenes as we focus more on frames instead of just the rays. It is also cheaper method computionally as we only check one ray per pixel instead of the four in SSAA.
Once again, getting help from ChatGPT to know what we could have done better, they mention that instead of using alpha we could clamp the colors instead, as well as using motion vector for smoother frames while moving (not really what we were after but something extra that could be added).
No anti-aliasing vs Temporal anti-aliasing
The code is written mostly by us, except the halton function, which we found online. The code is based on the concepts in this research paper: LINK
// global variables for TAA
std::vector<vec3> currentFramebuffer(SCREEN_WIDTH* SCREEN_HEIGHT);
std::vector<vec3> previousFramebuffer(SCREEN_WIDTH* SCREEN_HEIGHT, vec3(0.0f));
int frameCount = 0;
float alpha = 0.1f; // blending factor
// Function to generate Halton sequence, taken from https://www.wayline.io/blog/halton-sequence-demonstration-and-resources
float Halton(int b, int index) {
float result = 0.0f;
float f = 1.0f;
while (index > 0) {
result += f * float((index % b));
index = index / b;
f = f / float(b);
}
return result;
}
void DrawTAA() {
sdlAux->clearPixels();
// Generating jitter offsets using the Halton sequence from wayline
float jitterX = (Halton(2, frameCount) - 0.5f);
float jitterY = (Halton(3, frameCount) - 0.5f);
for (int y = 0; y < SCREEN_HEIGHT; ++y) {
for (int x = 0; x < SCREEN_WIDTH; ++x) {
// Applying jitter to current pixel
float px = x + jitterX;
float py = y + jitterY;
vec3 d(px - SCREEN_WIDTH * 0.5f, py - SCREEN_HEIGHT * 0.5f, focalLength);
d = R * d; // applying rotation
Intersection closestIntersection;
bool intersection = ClosestIntersection(cameraPos, d, triangles, closestIntersection);
vec3 color(0.0f);
if (intersection) {
vec3 directLight = DirectLight(closestIntersection);
color = triangles[closestIntersection.triangleIndex].color * (directLight + indirectLight);
}
// temporal accumulation (blending the current color with the history color for a smoother image)
int index = y * SCREEN_WIDTH + x;
vec3 historyColor = previousFramebuffer[index];
vec3 accumulatedColor = alpha * color + (1.0f - alpha) * historyColor; // Equation 2 from Yang, L., Liu, S. and Salvi, M. (2020)
currentFramebuffer[index] = accumulatedColor;
sdlAux->putPixel(x, y, accumulatedColor);
}
}
// Swap framebuffers
previousFramebuffer = currentFramebuffer;
frameCount++;
sdlAux->render();
}Firstly we had to figure out how we were going to add SSAA to the already made lab code. Based on our research we made, the method primarily checks a sample size of rays in pixels and averages out the color between them. By playing around ourselves with the code, we made a anti-aliasing method that could be classified as SSAA.
As we made it ourselves it is not completly optimized, but with some help from ChatGPT, we could find a few improvement areas. Most likely if you would expand on this function, you would apply parallelization by using multi-threading but as we don't want to dive too deep into this we left it as is!
No anti-aliasing vs Super-sample anti-aliasing
void DrawSSAA()
{
const int N_SAMPLES = 4; // 2x2 supersampling
float sample_offsets[4][2] = {
{-0.25f, -0.25f},
{ 0.25f, -0.25f},
{-0.25f, 0.25f},
{ 0.25f, 0.25f}
};
sdlAux->clearPixels();
for (int y = 0; y < SCREEN_HEIGHT; ++y)
{
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
vec3 pixelColor(0.0f);
// Loopign over each subpixel sample
for (int i = 0; i < N_SAMPLES; ++i)
{
float dx = sample_offsets[i][0];
float dy = sample_offsets[i][1];
vec3 d(x + dx - SCREEN_WIDTH * 0.5f, y + dy - SCREEN_HEIGHT * 0.5f, focalLength);
d = R * d;
Intersection closestIntersection;
bool intersection = ClosestIntersection(cameraPos, d, triangles, closestIntersection);
if (intersection)
{
vec3 directLight = DirectLight(closestIntersection);
vec3 color = triangles[closestIntersection.triangleIndex].color * (directLight + indirectLight);
pixelColor += color;
}
}
// averaging to compute final color
pixelColor /= float(N_SAMPLES);
sdlAux->putPixel(x, y, pixelColor);
}
}
sdlAux->render();
}

