Antialiasing in Deferred shading

I recently playing with deferred shading and love the ability to handle multiple light source with ease.

but the lack of traditional anti aliasing support borther me.

Aside from edge blur filter,are there any method to do anti aliasing in deferred shading ?

I am using OpenGL 3.2 core profile.

Thank in advance.

Snippet from my light-prepass shader:
(it’s MSAA’d; GTX275, WinXP)

uniform sampler2DMS texWSDepth; // AA texture for depth

ivec2 icoord= ...;
int i; // = 0..3
float zOverW = texelFetch(texWSDepth,icoord,i).x;

Works on geforces (G80+), must work on RHD3x00+, but I haven’t tried it on RHD2x00 yet. Anyone know if the 2x00 series can support this?

P.S. multisampled color-reads are supported on all SM4.0 cards, I’m just not sure if MSdepth-reads are accessible on RHD2x00. (why were they not a feature in DX10.0 ???)

So,you render g-buffer(and depth) to multi sample texture (openGL 3.2) and then do the loop/accumulation texel fetch/light calculation in fragment shader ?

I got this by google some article.

can you summarize the process for me please?

thank in advance

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:

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.

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

	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;
		varICoord = varCoord * ssize.xy;



	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]={

	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(,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(){
		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,; // assuming sky-light color is white


			for(int j=0;j<8;j++){
				vec3 Lpos = (lights[j]).xyz -;

				float d1 = dot(Lpos,Lpos);
				float dist = 10.0/(d1);
				dist *= dot(Lpos,; // actually "dist" is "lightAttenuation"
		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. 

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) :


	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 = *; = color;
		glFragColor.w = ALBEDO.w;

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.

Depth (another POV) :

Now, calculating the light-buffer:

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:

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 :slight_smile:

Thank for your very detailed answer. :slight_smile:

I will try to modified my deferred renderer to support multisample and then post the result here(I am still at my workplace).

well I have another question.

Is it possible(performance wise) to do lighting pass in MSAA texture since I need the FBO (deferred result + depth) for another pass(forward rendering of other translucent object).

It would look very weird if the result from deferred pass are multisampled but the forward pass are not.


I am not sure, I’ve not been testing AA+blending recently on anything but my gtx275 (blending and 4x AA are basically free on it). On PS3 and xbox360, most devs actually render alphablended stuff at half (quarter) resolution.

How can they combine the final result? ,If they render alpha blend/tranlucent stuff in half resolution/different FBO.

I assume they have to do some kind of z comparing when combining the result,right?

I don’t remember it clearly right now, but there are powerpoint slides and PDFs where different devs described this. What I remember was that the nasty resulting edges were considered acceptable quality by most.