 Example 8.1 demonstrates deferred rendering, which is an alternative way to render from what we've been doing so far, which is called forward rendering. Don't ask me why it's called forward rendering, there's no backwards rendering, but deferred rendering is the primary alternative. What we do differently in deferred rendering is that for each pixel all the information needed to compute its lighting, we first render that information into off-screen buffers, and only once our geometry is rendered into these G buffers, these geometry buffers as they're called. And then do we combine them all in a rendering pass that does the actual lighting calculations. Like a post-processing effect, the lighting phase just renders onto a quad encompassing the whole screen, and it samples all the information it needs to do the lighting per pixel from the G buffer. Now, it'd be clear that we can visualize the G buffers because they do store RGB values, but aside from the albedo buffer, which stores the diffuse texture information, the data in these buffers is not really color data. The positions in normals are XYZ values, usually in terms of world space, and the specular value is usually a monochromatic value specifying the specular lighting strength to apply to each pixel. Deferred rendering does incur a cost in terms of memory space and bandwidth for the buffers, but it can drastically improve the performance of our lighting calculations for scenes where we have more than a few lights. Firstly, in the case of overdraw, where multiple polygons overlap for a given pixel, so in forward rendering, we end up doing the lighting calculation for all of those overlapped pixels even though only the one on top is going to actually show up on screen. In deferred rendering, because we wait to do the lighting calculation until we know which pixel is going to be on top, then we're not wasting that work. But there are also techniques with deferred lighting that avoids doing the lighting calculation on pixels that are far enough away from the light that the amount of light contributed is insignificant. For a given pixel, ideally we'd only perform the lighting calculation for those lights that are in range of the geometric position of that pixel. So we'll discuss those techniques at the end. First though, looking at the code, we need to put together our G-buffer. We'll need one color attachment for the positions, another color attachment for the normals, and we'll need a third color attachment for both the so-called albedo and the specular. Because a specular information is just a single value, we can give the texture an alpha channel and store the specular in the alpha channel. But also that the normal and position buffers are RGB16F, 16-bit floating point, rather than the stock RGB, firstly because we don't want the values clamped into the range 0 to 1, and also we want 16 bits per channel rather than 8 so that we don't lose too much precision. So those are our color attachments, and we make sure to call draw buffers, specifying that we have three color attachments rather than just the default one. We set up a render buffer for the depth, as usual, and that is all we need for the G-buffer. So now in a render loop, first thing we do is render into the G-buffer, and on the C++ side that looks just like normal rendering. Our vertex shader is also doing nothing new, but then in the fragment shader, instead of frag colors output, we have G-position, G-normal, and G-albedo spec. G-position simply stores the fragment position we get from the vertex shader, G-normal stores the normal we get from the vertex shader, the note we normalize because the interpolation process may give us a non-unit vector, and then for the RGB of the albedo spec, that is the diffuse texture value, red from the diffuse map, and the alpha channel stores the specular value, red from the specular map. Now that we have all the information we need in the G-buffer, we can do our lighting pass. We're going to be sampling from the three color attachments of the G-buffer. We set up all the information we need for the lights and the camera, and then we render onto a quad that encompasses the whole screen, just like a post-processing effect. The vertex shader for this does nothing new, and then in the fragment shader, it's the same lighting calculation we've seen many times before, but now the frag position, normal, diffuse, and specular are sampled from textures. Note also that in this example, for every pixel, we're doing the full lighting calculation for every single light. We're sparing ourselves some extra lighting work in cases of overdraw, but for many pixels, we're still doing a lot of unnecessary calculations, because for lights that are far away, the diffuse and specular values are going to be basically zero. So again, this is something we'll address, but for now we're doing the naive thing, simply doing the lighting for every single light. So this is how we get deferred rendering, but in our example here, it's only the nanosuits that are drawn with deferred rendering. These colored boxes denoting the position of lights, those are being rendered afterwards with forward rendering. We couldn't just render these boxes with our nanosuits because they're using a different shader. This is a key disadvantage of our deferred rendering. In the lighting pass, we're using just one shader to draw everything, and so we couldn't in this example here draw these solid colored boxes in the same shader. Another problem is we can't draw transparent surfaces in deferred rendering because that requires drawing back to front and blending with each layer. In deferred rendering, we can't do any blending. So the solution to both of these problems is we can do a forward rendering pass on top of our deferred rendering, and that's what we're doing here. After the lighting pass, we're drawing these solid colored boxes, and to do so, we're going to need to copy the G-buffer's depth buffer to the default frame buffer, which we're going to do our forward rendering into. So here we bind the G-buffer for reading, we bind the default frame buffer for drawing, and then we call GL Blit Frame Buffer, which does the actual copy, specifying the rectangular dimensions of what we're copying, specifying that it's the depth buffer we want to copy, and specifying that we want nearest neighbor filtering rather than bilinear. So now that we have the depth information copied into our default frame buffer, we can do our forward rendering to render the boxes using a different shader. Now if we want to cut down on the lighting calculation work, the obvious thought is that for every light we compute a max radius beyond which it has no effect. Generally we would base this radius on the color of the light and calculate the distance at which that light attenuates to some minimum threshold color, and consider that the cutoff distance. But anyway, for every light, say we have some radius, well then, when we do our lighting calculation for every pixel, we can simply check if the world position of that fragment is greater than the max radius distance of the light, and if so, then we can just skip the lighting calculation. Unfortunately though, this obvious solution doesn't give us all that many benefits, as in a GPU you have many cores, typically 64, all executing the same path through code, such that for an if statement in code, only when the condition is false for every single core is the branch skipped over. If the condition is true for even just one of the cores, then all the other cores for which the condition was false basically have to sit and wait while the other cores do their business in that branch. So with the solution, for many pixels, in many cases they're going to end up effectively doing all the computation work for lights, even for lights of which they are not within the distance of the radius. There will be cases when we get lucky, and all the cores are processing pixels that happen to be outside the radius, and so the computation for those pixels for that light is skipped, so the solution may give us some benefits, but it's far from optimal. So one idea to cut down on wasted lighting work is to compute a bounding sphere for each of our point lights. Instead of rendering to a full-screen quad, we can then render onto these spheres, but when a sphere is rendered, you only do the calculations for the light associated with that sphere, not for any of the other lights, and we make sure that the blending mode is additive so that when a pixel is rendered onto multiple spheres, the diffuse and specular lighting calculations accumulate, and then as a last pass, we apply the ambient factor onto a full-screen quad. With this technique, we should make sure to call backfaces, otherwise for each light, we would be rendering each pixel twice. The problem then arises, though, for cases where the camera is positioned inside a sphere, which can happen depending upon the position and radius of a light. In that case, then, when backfaces are cold, the light wouldn't be rendered at all. There is a fix for this problem involving the stencil buffer, but actually, there's a better alternative anyway called tiling. We won't go into the details, but the general idea is very simple. We split screen space up into tiles, and we also compute the bounding boxes of all our lights in terms of screen space. For each tile, we then see which light bounding boxes overlap that tile, and that is a set of lights we use for the pixels of that tile. In this visualization, the scene in the top left is colored to show how many lights are hitting each point. In the bottom right, the scene is split up into small tiles. The lighter the tile, the more light bounding boxes intersecting that tile. So with this tile-based light culling, which I believe is the predominant way to do deferred rendering these days, it actually becomes practical on modern hardware to render not just a handful of lights, but dozens, hundreds, even into the low thousands. Keep in mind, though, that deferred rendering does have significant costs in terms of memory usage and bandwidth, and so it's generally still not appropriate for lower-tier hardware, particularly mobile. Even on high-end hardware, if you're not going for a realistic rendering look, you still might prefer forward rendering. To be clear, it mainly comes down to your use of lights rather than the aesthetic. Overwatch, for example, has a cartoony look, but is still used to deferred rendering.