Before you start
This assignment builds on the previous one. If yours worked, I strongly recommend using your own code as a starting point for this assignment. If not, you will find my sample solution in the "a4sample" directory of your GitHub repository. You are free to either use that code to figure out what was wrong with your assignment, or use it as a base for this one. Whichever you choose, be sure to do all of your work for this assignment in the "GLapp" directory of your repository (i.e. copy my code over to that directory if you are going to use it as a base).
This assignment will require adapting code from online tutorials to our GLapp. For this assignment, it is explicitly OK to adapt such code found online, as long as you credit the source in your assn5.txt. As usual, you are also free to discuss concepts and share pointers to resources. It is still not OK to share actual code with others in the class.
The Assignment
The goals of this assignment are to implement a multi-pass technique, including both OpenGL C++ code and shading code.
The specific technique you will implement is deferred shading. In deferred shading, rather than compute the full lighting contribution at each fragment as it is drawn, only to have it potentially hidden by other fragments, you just compute any per-vertex values and look up any texture data while drawing the geometry. These results are rendered into several textures known as G-buffers. Then, in a second rendering pass, you draw a triangle or rectangle that covers every pixel on the screen, using a shader that looks up the G-buffer values and computes the final color from them.
Your first G-buffer should contain the albedo (aka base surface color) in RGB and specular glossiness in A. Your second G-buffer should contain the world-space normal in RGB and specular intensity in A (as a single float rather than full color). Your third G-buffer should contain the world-space pixel position in RGB and ambient intensity in A (as a single float rather than full color). So you don't need to worry about clamping and precision, use the GL_RGBA32F data type for all three textures (32-bit float per color component).
To render and use the G-buffers, adapt the directions in this render-to-texture OpenGL tutorial. We will want three textures as GL_COLOR_ATTACHMENT0-2, and a GL_RENDERBUFFER for the GL_DEPTH_ATTACHMENT. All of these should be resized by calling glTexImage2D or glRenderbufferStorage again with the new size if the screen size changes.
Add key controls passed to the shaders in a uniform value (similar to the way the ambient intensity control works), to show G-buffers 0, 1 and 2 when the user presses the '0', '1', or '2' keys, and the final result when they press '-'.
Extra Credit
For up to 15 points of extra credit, use the position and normal together with data from nearby pixels to implement Screen Space Ambient Occlusion (SSAO). You will have to google for information about SSAO, select from among the algorithms you find, and adapt their techniques to our GLapp.
634 only
Instead of using a world-space position G-buffer, derive the world space position in your shader using the pixel position, from gl_FragCoord, and pixel depth. Note that in your deferred shading pass, the depth in gl_FragCoord will be the depth of the screen-sized quad. You can get the depth of the original geometry, either by using a texture instead of renderbuffer for the depth attachment, or by storing the depth into the unused channel the G-buffer with the normal. To get back to world space, you will need to convert image coordinates into clip-space, then apply the transform from clip space to world space.
Tips
- Before you start trying to render to texture, refactor object.frag into the part that generates vec4 quantities you will put into the G-buffers and the part that uses those to compute a final color. This way you'll be able to debug the split of the computation separate from any OpenGL rendering issues
- For the shape you can either use a rectangle, or single large triangle that sticks past the edges of the screen. The single triangle is slightly more efficient, though there are probably dozens of things in any graphics program that have a bigger impact on the final performance than that
- For the screen-filling quad (or triangle), just give the clip space positions directly and don't transform them in the vertex shader.
- You can either pass uv coordinates from the deferred pass vertex shader to fragment shader for texture lookup, or you can use gl_FragCoord (integer pixel coordinate) in the fragment shader together with the texture size.
- Look at the reference page for glTexImage2D for details on its arguments. The first is the texture type (e.g. GL_TEXTURE_2D). The second is the MIP level (should be 0, which is the base level). The third is the GPU format. This is the one that should be GL_RGBA32F for floating point textures. The fourth and fifth arguments are the image size. The sixth is a number of border pixels (which should be 0). The seventh and eight tell the format of the CPU side data. They probably don't matter since we are not providing CPU-side data, but it's good practice to match them to the GPU format, so use GL_RGBA and GL_FLOAT. The last is a pointer to the data, which is 0 to allocate on the GPU without providing
- Since the deferred pass runs on every pixel, you cannot just clear to sky color. You will need some way to identify sky pixels and color them sky color in the shader. Use some identifiable value as your G-buffer clear to be able to recognize those sky pixels in the deferred fragment shader. Since your render targets are floating point, you could clear to one of the special float values like NaN or Inf. Perhaps easier is to clear to 0 and look for that special value in the normal (which, being normalized, should never be all 0's for normal geometry). The clear color is used for all of the currently active glDrawBuffers (unless you switch to use glClearBuffer on each individually instead).
- You have several choices for what kind of texture to use for your G-buffers. The tutorial uses GL_TEXTURE_2D (sampler2D in shading code, and indexed by 0-1 uv coordinates), but has to turn off bilinear interpolation on those textures to make sure you don't interpolate between pixels. Another option is GL_TEXTURE_RECTANGLE (sampler2DRect in shading code, and indexed by integer pixel coordinates), which directly accesses the pixel data.
- Consider adding an extra G-buffer output with the final color the object.frag would have computed. Together with some debugging keys to show the G-buffers in the deferred shading fragment code, this will give you a way to check that the two versions are producing the same result
- If you enable depth testing for your geometery render pass, but disable it for the deferred pass, you do not need to clear color or depth in that second pass. The screen-filling quad will write new data to every pixel, so the clear is redundant. This will only work if you turn off the depth testing though, otherwise the pixels of the screen-filling quad might be considered to be hidden by things that have been drawn before.
What to turn in
Turn in this assignment electronically by pushing your source code to your class git repository by 11:59 PM on the day of the deadline and tagging the commit assn5. Do your development in the GLapp directory so we can find it. See the assn0 project description if you accidentally tag the wrong commit and need to retag.
Also include an assn5.txt file at the top level of your repository telling us about your assignment. Tell us what works, and what does not. Also tell us what (if any) help you received from books, web sites, or people other than the instructor and TA. Finally, if you did anything toward the extra credit, be sure to tell us about it in this file. For the extra credit, be sure to tell us about the algorithm you used and where you found it.
You must make multiple commits along the way with useful checkin messages. We will be looking at your development process, so a complete and perfectly working assignment submitted in a single checkin one minute before the deadline will NOT get full credit. Individual checkins of complete files one at a time will not count as incremental commits. Do be sure to check in all of your source code, but no build files, log files, generated images, zip files, libraries, or other non-code content.