Yes, that’s deferred rendering. I didn’t like its implications, though (I need more flexibility in material shading, for NPR) , so I moved to the light-prepass.
In deferred rendering, you write to multiple render-targets at once all data you need about a fragment: normal (2 or 3 components), albedo (3 components), specular (1 or 2 components).
Oh well, I’d better link a full example first: http://www.guerrilla-games.com/publications/dr_kz2_rsx_dev07.pdf
Note: they use 2xMSAA. That’s why a 36MB FBO. Also note: their viewspace-normal’s Z recalculation is not correct, fragments with negative z are also visible, thanks to perspective-projection. (fortunately doesn’t produce nasty artifacts) . Also note: on GL3.2-capable hardware, you can (and should, as optimization) mix MRT with different formats.
After laying-down the complete g-buffer, comes drawing the lights into a single-sampled color-only HDR FBO: (either with scissors or triangles) either in batches or one-primitive-per-light, the MSAA samples are read individually and view-space (or worldspace) fragment-position and normal are reconstructed. The position/normal pair are used to calculate distance to a light and dot-product with the light’s direction; plus specularity. The results from the different MSAA fragments are summed and written to gl_FragColor, in additive blending.
A lot of computation optimizations can be done, some are degrading the image quality slightly. Centroid-interpolation and stencil-masks showing where MSAA edges are [does not detect many edges, though], grouping of lights into tiles, using RGBA8’s alpha for tricks which ultimately kill your hopes of alpha2coverage.
In light prepass, you sacrifice the “single-geometry-pass” feature of deferred-rendering, for some bandwidth and flexibility.
http://diaryofagraphicsprogrammer.blogspot.com/2008/03/light-pre-pass-renderer.html
http://www.naughtydog.com/docs/Naughty-Dog-GDC08-UNCHARTED-Tech.pdf
Below is a simplistic shader I had coded to kick-start results from light-prepass. 8 point-lights, a fullscreen quad is drawn. I’ve added comments for clarity.
#include "system.h"
varying vec2 varPos; // range [-1;1], clipspace
varying vec2 varCoord; // range [0;1]
varying vec2 varICoord; // range [0;1280], [0;720]
uniform mat4 u_Inverse_MVP; // inverse model-view-projection of camera
uniform sampler2DMS texWSNormal; // AA texture for worldspace normal
uniform sampler2DMS texWSDepth; // AA texture for depth
#if IS_VERTEX
in vec4 inVertex;
void main(){ // this is a vertex of a huge triangle or quad, usually full-screen
gl_Position = inVertex;
vec2 ssize=textureSize(texWSNormal);
varPos = inVertex.xy;
varCoord=(inVertex.xy+1.0)*0.5;
varICoord = varCoord * ssize.xy;
}
#endif
#if IS_FRAGMENT
out vec4 glFragColor;
vec4 DepthToWorldspacePosition(in vec2 my_varPos,in float my_zOverW){
vec4 H = vec4(my_varPos.x,my_varPos.y,my_zOverW*2-1,1);
vec4 tmp1 = u_Inverse_MVP * H;
vec4 WorldPos = tmp1/tmp1.w;
return WorldPos;
}
uniform vec4 lights[8]={
vec4(21.996891,3.728675,-7.664107,1),
vec4(5.377327,3.0,-31.299137,1),
vec4(15.553305,9.069536,-24.629501,1),
vec4(1.0,3.0,1.0,1),
vec4(18.981777,6.258294,-27.141813,1),
vec4(18.981777,6.258294,-27.141813,1),
vec4(18.945974,5.920466,-47.028144,1),
vec4(18.945974,5.920466,-47.028144,1)
};
uniform mat4 biasShadow = mat4(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0);
uniform mat4 u_ShadowMatrix;
uniform vec4 u_ShadowDir; // xyz is direction, w=bias
uniform sampler2DShadow texShadowMap;
float ComputeShadow(in vec4 worldPos,in vec3 norm1){ // a simplistic PCF, one tap.
float lightIntensity = 1.0;
vec3 lightDir = vec3(0.3,0.5,-1.0); lightDir = normalize(lightDir)*lightIntensity;
float dot1 = dot(u_ShadowDir.xyz,norm1);
if(dot1<=0.0)return 0;
vec4 coord = u_ShadowMatrix * worldPos;
coord.z -= u_ShadowDir.w; // bias . Front faces were culled, there're light-leaks, not shadow-leaks
float shadow = shadow2DProj(texShadowMap,coord).x;
return shadow*dot1;
}
void main(){
glFragColor=vec4(0);
ivec2 icoord = ivec2(varICoord);
/*
for(int i=0;i< NUM_MSAA_SAMPLES ;i++){
glFragColor += texelFetch(tex,icoord,i);
}
glFragColor/= NUM_MSAA_SAMPLES;
*/
vec4 diffuse = vec4(0);
float ambient = 0.3;
for(int i=0;i< NUM_MSAA_SAMPLES ;i++){
vec4 norm1 = texelFetch(texWSNormal,icoord,i);
float zOverW = texelFetch(texWSDepth,icoord,i).x;
vec4 WorldPos = DepthToWorldspacePosition(varPos,zOverW);
diffuse+= (ambient+ComputeShadow(WorldPos,norm1.xyz)); // assuming sky-light color is white
for(int j=0;j<8;j++){
vec3 Lpos = (lights[j]).xyz - WorldPos.xyz;
float d1 = dot(Lpos,Lpos);
float dist = 10.0/(d1);
dist *= dot(Lpos,norm1.xyz)/sqrt(d1); // actually "dist" is "lightAttenuation"
diffuse+=max(dist,0)*vec4(2,1,1,1);
}
}
diffuse/= NUM_MSAA_SAMPLES;
glFragColor = diffuse; // we're writing to a 1280x720 single-sampled FBO that has no depth-buffer, and has texture-format RGBA16F or anything that's HDR, in additive-blending mode.
}
#endif
Then you draw the scene regularly, but simplistic materials, that fetch fragment-light and optionally normal (and whatever else you want, the system is very flexible) :
#if IS_FRAGMENT
uniform sampler2D tex0;
uniform sampler2D texLIGHT; // the light-buffer we calculated
//uniform sampler2DMS texNORMAL;
out vec4 glFragColor;
void main(){
vec4 ALBEDO = texture(tex0,varCoord);
vec4 LIGHT = texelFetch(texLIGHT,ivec2(gl_FragCoord.xy),0);
vec3 color = ALBEDO.xyz * LIGHT.xyz;
glFragColor.xyz = color;
glFragColor.w = ALBEDO.w;
}
#endif
Here are screenshots, in PNG lossless format, of the above code in 4x MSAA; I changed the camera’s position to show the depth-buffer better. MSAA textures’s texels are displayed as an average sum of their 4 samples.
Normals:
http://dl.dropbox.com/u/1969613/openglForum/LightPrepass1/light_prepass1_normals.PNG
Depth (another POV) :
http://dl.dropbox.com/u/1969613/openglForum/LightPrepass1/light_prepass1_depth.PNG
Now, calculating the light-buffer:
http://dl.dropbox.com/u/1969613/openglForum/LightPrepass1/light_prepass1_light_buf.PNG
Now, drawing the scene in a MSAA4x RGBA8+Depth24_S8 FBO (actually the RGBA8 is another attachment of the same FBO, where we drew normals+depth) with those simplistic forward-rendering style shaders, that fetch from texLIGHT; then resolving this color-texture:
http://dl.dropbox.com/u/1969613/openglForum/LightPrepass1/light_prepass1_result.png
During the resolve of this last texture, when you render to a HDR format, you’d best do tone-mapping and sRGB conversion (which requires your textures be sRGB or simply upgrade the simplistic forward-rendering shader to convert to linear light when multiplying with texLIGHT). The tone-mapping and linear-light seem to decrease jaggies even further. Some mild bloom is necessary at overbrightened places, which you see in the screenshots become jaggy.
Oh well, I’ve spent only a little time on this stuff, and didn’t touch it till recently (so take all my above suggestions with a grain of salt). And btw pardon my programmer-art 