Open GL Graphics Programming
Accomplishments and Techniques:
Used C++ and FreeGLut to implement a cutting-edge multi-scale solution for runtime ambient occlusion in OpenGL.
Implemented critical graphics techniques including render textures, reflections, shadows, and skyboxes in OpenGL.
Wrote custom tessellation and geometry shaders for real-time tessellation on the GPU.
Created a game engine-like architecture for efficiently rendering custom .obj files.
Project Overview:
For this project, I spent several months learning the the critical aspects of graphics programming with OpenGL and FreeGlut in C++. Each week, I tackled a new subject and created a small demo, following the below schedule:
Week 1: Set up FreeGlut, print colors to a window on the screen
Week 2: Load an .obj file and render using matrix transformations and a custom camera class
Week 3: Implement lights and shading using a blinn-phong lighting model
Week 4: Create, bind, and load textures to objects using png files
Week 5: Create render textures, loading objects in screen-space quads with render buffers
Week 6: Implement skyboxes and environment-mapped lighting
Week 7: Implement shadows using shadow mapping and render buffers with percentage-closer filtering
Week 8: Use compute shaders to achieve real-time tessellation on the GPU
Weeks 9 & 10: Implement multi-scale screen-space ambient ambient occlusion with a deferred shading pipeline
Weeks 1-3:
I spent the first month of the project setting up freeGLUT, establishing a working render pipeline, and getting familiar with basic rendering techniques, such as multiplying object positions by world, projection, and camera matrices for proper in-window placement. The biggest challenge of this phase was learning to properly load vertex buffers with interleaved data, including vertex positions, texture coordinates, and per-vertex normals. I accomplished this with the help of a Cem Yuksul library called trimesh, which allows for mesh .obj files to be cleanly ready loaded into custom data structures. The code snippet below showcases how each vertex buffer was loaded, while the video showcases a quick progression of these first few weeks.
Weeks 4-6
With the basics out of the way, I began working on intermediate techniques including render-to-texture operations, skybox environment mapping, and planar reflections. My biggest challenge during this phase was implementing a combination of planar and skybox reflections on both a teapot model and the ground it stands on. After struggling for hours, I finally reached out to a peer and asked for advice on combining both reflections on a single plane. They were able to see the problem from a unique perspective and quickly corrected my approach, helping me realize the value of collaboration even in supposedly ‘solo’ projects. The snippet below displays the frag shader code for rendering reflections on the teapot, while the video showcases a progression of weeks 4-6.
Language: C++, FreeGLUT
IDE: Visual Studio 2022
Production Time: 3 Months
Link To Github: OpenGL Study on Github
Weeks 7-8:
I spent the last strech of the project learning advanced rendering techniques, specifially shadow-mapping and real-time tessellation on the GPU. Shadow mapping is an especially tricky subject, as it requires a unique series of matrix transformations that occur from the perspective of the scene’s directional light. This solution utilizes a depth buffer to store information on each fragment based on whether they are occluded from the view of the light source.
With shadows working, I went ahead and implemented them along with a custom real-time tessellation and tessellation control shader, allowing the user to dynamically tessellate a flat plane into an increasingly realistic teapot mold. This required the use of a geometry shader as well, a unique process in which the GPU is tasked with creating new vertices based on provided barycentric coordinates. The right image displays a snippet of the shadow fragment shader, while the video shows a demo of each technique.
Weeks 9-10:
I spent the final two weeks of the project implementing a solution for multi-scale screen-space ambient occlusion, based off the research of Thai Duong and Kok-Lim Low. Being a freshly discovered solution, there were little resources available to help me in my implementation. This provided a fun challenge as I used my understanding of linear algebra, SSAO, and the rendering pipeline to reverse-engineer a partial implementation of the researcher’s excellent work. I used a deferred rendering algorithm and g-buffer approach to store lighting and AO information in packed textures before rendering a series of custom models to the screen with both naive and multi-scale SSAO. There is still optimization work to do, as my implementation is not nearly as peformant as Duong’s and Low’s - primarily due to the custom downscaling algorithm they use, which I replaced with a simple mip-map shortcut. Check out the video below for a detailed breakdown of my implementation.
View out the research paper from Duong and Low here.
Wrapping Up:
As a technical designer, I feel that it’s critical to understand each aspect of the game engineering process so that I can best contribute to a project’s success. These last few months have awarded me with not only a deeper understanding of graphics programming, but the ability to think like an engineer while dealing with complex problems. I look foward to applying my renewed familiarity with C++, 3D math, and logical debugging to my scripting and design work on future projects.