PaintHell - Masters Group Project
During the second semester of my Masters course (MSComp Computer Science - Game Engineering 4th year) I was tasked with building a 3D game, alongside a group of eight people (myself included). The theme for this project was Splatoon, with a particular focus on the paint mechanic.
I received 89% (first class) for this submission.
My primary responsibility in this group was with Graphics, though I did delve into most areas of the project. The initial framework for our project was built on top of my submission for the previous Physics Coursework (which in turn was built on a framework provided by Richard Davidson, one of our lecturers).
The other members of my group were as follows:
- Daniel T. Abraham
- Harry G. Brettell
- Cho Hin Chiu (Felix)
- Yifei Hu
- Sashwat Kashyap (Kash)
- Shantao. Liu
- Xiaoyang Liu
Example gameplay:
Development
Graphics
Many of the techniques I discuss here are based on these tutorials. Take a look if you're interested in a more in-depth look at how specific techniques are done.
The first major thing I did for this project was to overhaul the render pipeline. Originally, my physics coursework made no changes to the rendering framework provided (as there was no need), so all of the rendering code was found in a few member functions of the GameTechRenderer class.
My solution to this was to build a render-pass system in which we could extend a base RenderPass class for each major part of the render pipeline and have the renderer build a list of passes which it will iterate through during the mainloop. Over time I extended this structure to differentiate between different types of render passes, with main passes (rendering to the GBuffer), post-processing passes (applying effects to an already drawn scene), and overlay passes (drawing on top of the backbuffer).
The following diagram roughly outlines the final full render pipeline:
Many of the systems I made for this project's render pipeline were designed to be platform agnostic. Data structures, like textures
and framebuffers, were encapsulated in base classes which hold public member functions for all the functionality one would need when
handling them. Specific implementations (in particular OpenGL and PS4) would then extend these base classes, with per-platform
code to handle the actual API calls (i.e. Shader.Bind()
would use glUseProgram()
for OpenGL, and configure a
GnmxGfxContext
for PS4). This was mainly done to allow easier porting to PS4, though unfortunately we never fully got
around to this.
Painting Pass
This pass takes in every model with a paint texture, and writes to the texture any new paint splats which have occured since the previous frame. The results are then used later in the model pass to inform the albedo of a model.
Skybox
A procedural skybox is generated using the same code from my Graphics Coursework. The skybox is configurable, allowing for a dynamic sun direction and varying amounts of cumulus and cirrus clouds. The clouds are generated using simplex noise, skewed to account for perspective. Mie scattering and Reyleigh scattering calculations are used to generate the actual sky.
The code used for the skybox was originally sourced from here.
Output texture for this pass:
Model
Models with a RenderObject are drawn to the GBuffer, filling in textures for albedo, normal (with bump-mapping, and parallax-mapping), specular-mapping, and depth.
Animations (seen with the player character and the boss) were originally done by Sean, though I did go through and overhaul it to fix some bugs and make it better fit with the structure of our project.
Output textures for this pass:
Lighting
The final scene in our game only uses a single directional light for the sun (though functionality exists for multiple lights, as well as point and spot lights). Lighting is calculated using the Blinn-Phong reflection model, with shadow mapping to cast per-light-source shadows. The spec-map texture generated during the model pass is also used here to weight the amount of specular light a given surface can have.
Output texture for the main directional light's shadow map:
Output textures for this pass:
SSAO (Screen-Space Ambient Occlusion)
Ambient occlusion is generated using 64 kernels with a radius of 0.5 (about half the player's radius). All parameters are configurable, with the kernels handled via SSBO to allow resizing.
Output texture for this pass:
Combine
This pass takes all elements of the GBuffer and combines them into a single output texture. It is here that the final lighting calculations occur, combining the skybox, albedo, diffuse, specular, and ambient occlusion. Different modes have also been added for debug purposes allowing to switch to only rendering certain textures, like the depth or normal textures.
This pass also marks the end of the main stage of the render pipeline, with the next passes being post-processing effects.
Output texture for this pass:
Bloom
Though barely visible in the final project, due to a lack of bright objects, physically based bloom is generated during this pass. This is done by downsampling then upsampling the scene to create a blurry texture, which is then added back to the original scene to create the bloom effect.
HDR (High Dynamic Range)
Though I didn't have the time to implement automatic eye adjustment, I was able to implement HDR. This pass is very simple, taking a single exposure parameter to increase or decrease the overall brightness of the image.
Presentation
This pass takes the final texture of post-processing and draws it directly to the backbuffer, with gamma correction applied. All remaining passes are overlay passes, which draw on top of this scene.
HUD
This pass was created by another group member, though I did help fix a few bugs in it. The main purpose of the HUD pass is to draw health bars, done via drawing textures to quads using the same shader as the menu pass.
Debug
In the original framework from Richard Davidson, debug text and lines were drawn by filling static lists of vertex and uv information, which would then be used at the end of the render-pipeline. I took this system and plugged it into it's own render pass, as well as making it platform agnostic to fit the rest of my redesigned framework.
Fully rendered scene:
Menu
This pass was created by Yifei, with my assistance. I also redesigned much of the menu system to use a nested structure, allowing for widgets to have child widgets which are recursively drawn. Individual components are drawn as textured quads, using a texture matrix to offset and scale the vertices to their correct positions.
Main menu:
Play Station 4
PS4 integration was mainly handled by Daniel, though I did spend the final week helping port our game over, and many of my design choices were with platform agnosticism in mind. Due to troubles with admin rights in the computer cluster we ended up with little time to implement the PS4 version of the game. As such, while we do have a working PS4 version, it is nowhere near as fully realised as the Windows version.
Daniel was able to get the full gameplay and physics system working fine, since these are platform agnostic to begin with. The main issue came with the graphics system, in which he and I attempted to convert the existing OpenGL code to the PS4 equivalents. Even though much of my render system was designed to make porting easier, there were many structural differences which made it difficult to port directly (such as how OpenGL doesn't care which shader pass a given uniform goes to, while PS4 requires uniforms to be sent only to the correct pass). Since we didn't know what PS4 would require until late into the project we ended up with many incompatibilities and decided it would take too long to directly port the existing project.