Athena: Cheap(er) Per-Pixel Directional Lighting (cont)

In my previous blog entry, Athena: Cheap(er) Per-Pixel Directional Lighting, I wrote how I had an idea for post process lighting calculations. Well, like many other things, it has already been done before, in fact a long time ago, 1988. It is known as Deferred Shading.

The wiki article also mentions something else that I didn’t take into considering, and that is transparent objects, since there would be more than one lighting equation needed.

On the whole though, I’m glad I got a chance to implement it, one more thing I know how to do.

Athena: Cheap(er) Per-Pixel Directional Lighting

Per-Pixel Directional Lighting through Post ProcessAs mentioned in Athena: More Uses for Framebuffer Objects, I have thought up a way of doing lighting using colour buffers, at the moment it is only directional lighting however.

But the great thing is, depending on how many objects are in your scene, and how much overdraw can happen, this method is faster (I think, I don’t know the cost of writing to a second colour attachment).

Colour Shader

The colour shader is just a basic shader I’ve setup to show. First of all the vertex shader…

//
// Varying Variables
varying vec3 Normal;
 
//
// Vertex Shader entry point
void main()
{
	Normal = normalize( gl_NormalMatrix * gl_Normal );
	gl_Position = ftransform();
}

 

As you can see this is just a basic vertex shader, transforming a vertex position into screen space and passing the normal onto the fragment shader through a varying variable type.

Now the fragment shader…

//
// Varying Variables
varying vec3 Normal;
 
//
// Fragment Shader entry point
void main()
{
	vec3 N = normalize( Normal );
	float kDrawLit = 1.0;
	gl_FragData[0] = vec4( 1.0, 0.0, 0.0, 1.0 );
	gl_FragData[1] = vec4( N, kDrawLit );
}

 

This is a typical fragment shader as well, using draw buffer zero for the unlit texel colour. However, I’ve normalised the interpolated normal that was calculated for this texel and I’m outputting it to draw buffer one, along with another value ‘kDrawLit‘.

When the w component of draw buffer 1 for this texel is set to 0.0, the texel will not have any lighting calculations performed on it in the post process. I came up with this when I started thinking about rendering a HUD before the post processing.

I’ve chose just to output red as a colour, anything is fine, such as a texture that is mapped to the object in question.

Per-Pixel Lighting Shader

Again, another simple vertex shader, passing a transformed vertex and texture coordinate to the fragment shader…

//
// Varying Variables
varying vec3 v_lightDir;
varying vec3 v_halfVec;
 
//
// Vertex Shader entry point
void main()
{
	//
	v_lightDir = normalize( gl_LightSource[0].position.xyz );
	v_halfVec = normalize( gl_LightSource[0].halfVector.xyz );
 
	//
	gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;
	gl_Position = gl_Vertex;
}

 

Now for where the magic happens, the fragment shader, it uses two textures. The ‘baseImage‘ sampler is for the colour buffer, whilst the ‘nrmImage‘ is used for the normal texture we wrote…

//
// Uniform Variables
uniform sampler2D baseImage;
uniform sampler2D nrmImage;
 
//
// Varying Variables
varying vec3 v_lightDir;
varying vec3 v_halfVec;
 
//
// Fragment Shader entry point
void main()
{
	// Get texel colour and normal data
	vec4 kTexelColour = texture2D( baseImage, gl_TexCoord[0].xy );
	vec4 kNormalColour = texture2D( nrmImage, gl_TexCoord[0].xy );
 
	// Don't Perform Lighting on a non-lit texel
	if( kNormalColour.w == 0.0 )
	{
		gl_FragData[0] = kTexelColour;
		return;
	}
 
	// Calculate Ambient
	vec4 kAmbient = gl_LightSource[0].ambient * kTexelColour;
 
	// Process Light Direction and Normals
	float NdotL = max( dot( kNormalColour.xyz, v_lightDir ), 0.0 );
	vec4 kColour = kAmbient;
 
	// Add Diffuse and Specular is NdotL is greater than zero
	if( NdotL > 0.0 )
	{
		// Calculate Diffuse
		vec4 kDiffuse = ( 1.0 - kAmbient ) * ( gl_LightSource[0].diffuse * kTexelColour );
		vec4 kSpecular = gl_LightSource[0].specular;
		float kShininess = 60.0;
 
		float NdotHV = max( dot( kNormalColour.xyz, v_halfVec ), 0.0 );
		kColour += kDiffuse * NdotL;
		kColour += kSpecular * pow( NdotHV, kShininess );
	}
 
	// Write Pixel Colour
	gl_FragData[0] = kColour;
}

 

As you can see, if the w component of the normal texture is 0.0 then the colour value is outputted as it is. If not, using the normal and the light currently setup, the colour is modified to be lit before being outputted.

I’ve just used a simple Blinn shader for calculating the lighting.

The Application

Now for the application side of things, below is just some quick sample code for what I used for my example. As you can see I’ve used a unsigned byte to store the normal, a floating point texture may have better accuracy at describing it though.

//
//
namespace CommonApp
{
	enum TextureTypes
	{
		kColour,
		kNormal,
		kDepthStencil,
		kNumTextureTypes,
	};
 
	//
	const int kWidth = 800;
	const int kHeight = 600;
 
	//
	GLuint l_frameBuffer;
	GLuint l_textures[kNumTextureTypes];
 
	//
	GLuint l_colourProgram;
	GLuint l_lightingProgram;
}
 
//
// Use alternate INTERNAL_FORMAT if floating point textures are not supported
//#define INTERNAL_FORMAT GL_RGBA
#define INTERNAL_FORMAT GL_RGBA32F_ARB
 
//
//
void CommonApp::Init()
{
	// Code to load colouring shader program
	// ...
 
	// Code to load lighting shader program and setup uniform variables
	// ...
 
	// Generate Textures
	glGenTextures( kNumTextureTypes, l_textures );
 
	// Create Colour Texture
	glBindTexture( GL_TEXTURE_2D, l_textures[kColour] );
	glTexImage2D( GL_TEXTURE_2D, 0, INTERNAL_FORMAT, kWidth, kHeight, 0, GL_RGBA, GL_FLOAT, 0 );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
 
	// Create Normal Texture
	glBindTexture( GL_TEXTURE_2D, l_textures[kNormal] );
	glTexImage2D( GL_TEXTURE_2D, 0, INTERNAL_FORMAT, kWidth, kHeight, 0, GL_RGBA, GL_FLOAT, 0 );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
 
	// Create Depth Buffer
	glBindTexture( GL_TEXTURE_2D, l_textures[kDepthStencil] );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8_EXT, kWidth, kHeight, 0, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT, 0 );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
 
	// Generate Framebuffer and attach textures
	glGenFramebuffersEXT( 1, &l_frameBuffer );
	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, l_frameBuffer );
	glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, l_textures[kColour], 0 );
	glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, l_textures[kNormal], 0 );
	glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, l_textures[kDepthStencil], 0 );
	glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, l_textures[kDepthStencil], 0 );
}
 
//
//
void CommonApp::DeInit()
{
	glDeleteFramebuffersEXT( 1, &l_frameBuffer );
	glDeleteTextures( kNumTextureTypes, l_textures );
}

 

Just the usual code as you can see for setting up a frame buffer. Now for rendering the scene…

//
//
void CommonApp::Render()
{
	// Use Frame Buffer
	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, l_frameBuffer );
 
	// Set Draw Buffers
	const GLenum kBuffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
	glDrawBuffers( 2, kBuffers );
 
	// Use Shading Program
	glUseProgram( l_colourProgram );
 
	// Perform Rendering
	// ...
 
	// Reset Draw Buffer
	glDrawBuffer( GL_COLOR_ATTACHMENT0_EXT );
 
	// Reset Frame Buffer
	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
}

 

I’ve set it up so that drawing to draw buffer zero writes to the colour texture, whilst drawing to draw buffer one writes to the normal texture, to reflect what is being done in the shader.

You don’t need to use the colour shader I described up above, as long as it writes out the normal and an unlit colour, any shader can be used for this process, but for demonstration I’ve made it in this example that all geometry is drawn with this shader program.

Finally with our scene rendered, we can moved onto the final post processing stage…

//
//
void CommonApp::PostRender()
{
	glActiveTexture( GL_TEXTURE0 );
	glBindTexture( GL_TEXTURE_2D, l_textures[kColour] );
 
	glActiveTexture( GL_TEXTURE1 );
	glBindTexture( GL_TEXTURE_2D, l_textures[kNormal] );
 
	glUseProgram( l_lightingProgram );
 
	// Render Fullscreen Quad
	// ...
}

 

More Per-Pixel Directional Lighting through Post ProcessAttaching the colour and normal texture, using the per pixel lighting shader program and drawing a quad. The images within this blog are the final result (showing a comparison would be useless since they look the same).

One downside to this is that all texels have to use the same shininess value, however the alpha value of the colour buffer could be used to control this.

It is only a start, I need to performance check it at some point to see if it gives better or worse performance and under what conditions.

Per Pixel Lighting (MacOSX) (428 downloads)

Athena: More Uses for Framebuffer Objects

Depth Buffer using Frame Buffer ObjectAs I was upgrading my engine to the latest version of OpenGL, I noticed how my depth buffer was broken again (I use it for debugging my scenes when on the debug menu), and despite many changes, I was still having trouble getting it to display properly.

The glReadPixels() function with GL_DEPTH_COMPONENT was working, but I was having no luck at getting it displaying at any colour other than white without converting it first before writing it to a texture with glDrawPixels() – on reflection a floating point texture might have made this easier.

Anyways, when looking for answers, I found a way of using frame buffer objects to write out to multiple colour buffers, which allowed me to modify my Blinn shader to render a scene and the depth buffer to texture at the same time.

First of all, the shader needs to be shaders, instead of gl_FragColor, we use gl_FragData[n], where n is the index of the colour buffer we want to write to.

//
// Varying Variables
varying float v_farPlane;
varying float v_nearPlane;
 
//
// Fragment Shader entry point
void main()
{
	// Write Pixel Colour
	//gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
	gl_FragData[0] = vec4( 1.0, 0.0, 0.0, 1.0 );
 
	// Write Depth
	float kDepth = 1.0 - ( gl_FragCoord.z / gl_FragCoord.w ) / ( v_farPlane - v_nearPlane );
	gl_FragData[1] = vec4( kDepth, kDepth, kDepth, 1.0 ); // Luminace style
}

 

And finally when binding your frame buffer object.

//
//
void RenderTarget::Setup()
{
	// Create Frame Buffer
	glGenBuffers( 1, &m_frameBuffer );
 
	// Bind Frame Buffer
	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_frameBuffer );
 
	// Attach texture to receive colour
	glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_colorTexture, 0 );
 
	// Attach Texture to receive depth buffer
	glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, m_depthTexture, 0 );
 
	// Attach Depth and Stencil Buffer (same object since my gfx card doesn't support them being separate)
	glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_stencilDepthBuffer, 0 );
	glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, m_stencilDepthBuffer, 0 );
}
 
//
//
void RenderTarget::StartRender()
{
	// Bind Frame Buffer
	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_frameBuffer );
 
	// Set which index draws to which colour attachment
	const GLenum kBuffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
	glDrawBuffers( 2, kBuffers );
}

 

The call to glDrawBuffers() sets where gl_FragData[x] is being wrote to, I’ve specified there are two indexes, the first one points to colour attachment 0, and the second points to colour attachment 1.

Now when an object is rendered using that shader, it will output the normal colour buffer to colour attachment 0, and output the depth buffer to colour attachment 1.

Also, don’t forget to reset the draw buffer destination after you’re finished with it.

//
//
void RenderTarget::EndRender()
{
	// Reset draw buffer destination
	glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
 
	// Unbind frame buffer and all attachments
	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
}

 

I will probably remove the glBindFramebufferEXT() call to unbind the frame buffer at some point, so that the same frame buffer gets used for all render targets, only the attachments get changed.

I’ve read it is faster this way. For more information on this, there is this great article on GameDev.net about it called, OpenGL Frame Buffer Object 201.

I probably won’t use this for writing out the depth buffer, but it did give me some ideas for per pixel lighting.

Athena: OpenGL 3.0 Upgrade in Progress…

Building in progress...

I just upgraded to the latest version of gDEBugger, and it kept notifying me that a lot of my code is using deprecated functions as of OpenGL 3.0.

So I have begun the long task of converting code and shaders alike, hopefully in a positive step since it will mean my DirectX 10 port of the Athena engine will have little modifications to work like the OpenGL and vice versa.

These functions are the first on my list, they are used for rendering my geometry.

glTexCoordPointer()

glColorPointer()

glNormalPointer()

glVertexPointer()

These have been replaced with glVertexAttribPointer(), a more useful system so you can specify custom attributes a vertex has, and link them to attribute variables in shaders (shaders are required now for everything like DirectX 10). So for a simple array of vertices, I linked them to attribute variable in my shader which was bound to location 0.

//
// Bind VBO Object
glBindBuffer( GL_ARRAY_BUFFER, m_vertexBuffer );
 
//
// Enable vertex attribute streaming on location 0
glEnableVertexAttribArray( 0 /* location */ );
 
//
// Set attribute offset, size, and type details for location 0
glVertexAttribPointer(	0, /* location */
			4, /* components */
			GL_FLOAT, /* type */
			GL_FALSE, /* normalize */
			sizeof( float ) * 4, /* stride */
			0 ); /* buffer offset */

 

I found out shortly after however that the index I was using for my vertex attribute is a reserved index by nVidia for their built-in attributes, as are 12 others.

0 – gl_Vertex
2 – gl_Normal
3 – gl_Color
4 – gl_SecondaryColor
5 – gl_FogCoord
8 – gl_MultiTexCoord0
9 – gl_MultiTexCoord1
10 – gl_MultiTexCoord2
11 – gl_MultiTexCoord3
12 – gl_MultiTexCoord4
13 – gl_MultiTexCoord5
14 – gl_MultiTexCoord6
15 – gl_MultiTexCoord7

So in the interest of keeping things sane I changed the location index to be 16, all that was needed was updating my shaders to use attributes rather than gl_Position, etc.

//
// Vertex Attributes
attribute vec4 a_vertex;
attribute vec2 a_texCoord0;
 
//
// Vertex Shader Entry Point
void main()
{
	//gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;
	gl_TexCoord[0].xy = a_texCoord0;
 
	//gl_Position = gl_Vertex;
	gl_Position = a_vertex;
}

 

You may have noticed that I don’t multiply my vertex by any transformation matrices, this is because my vertex coordinates for 2D are always in the range of -1.0 to 1.0. Doing it this way saves me changing projection matrix states whenever I want to draw 2D, so my projection matrix is always setup for 3D. Also, the source for this vertex shader program contains another attribute for the texture coordinate as well since I just copy-pasta’d my Bloom shader.

Now just two simple calls were needed to be added to my  shader program before it was linked, so that the vertex attribute is bound to the correct location.

glBindAttribLocation( m_program, 16 /* location */, "a_vertex" );
glBindAttribLocation( m_program, 17 /* location */, "a_texCoord0" );
glLinkProgram( m_program );

 

As you can see my vertex shader has a texture coordinate as well (I’m using my Bloom vertex shader as an example), so the final code for setting up the VBO for this is.

//
// VBO layout
struct TexturedVertex
{
	float texCoord[2];
	float position[4];
};
 
//
// Bind VBO Object
glBindBuffer( GL_ARRAY_BUFFER, myVBO );
 
//
// Enable vertex attribute streaming on location 0
glEnableVertexAttribArray( 16 );
glEnableVertexAttribArray( 17 );
 
//
// Set attribute offset, size, and type details for location 0
glVertexAttribPointer( 17, 2, GL_FLOAT, GL_FALSE, sizeof( TexturedVertex ), 0 );
glVertexAttribPointer( 16, 4, GL_FLOAT, GL_FALSE, sizeof( TexturedVertex ), sizeof( float ) * 2 );

 

I’m sure there is a preprocessor command to get the offset of a variable within a structure, but “sizeof( float ) * 2” works as well. My code is a bit more complex than is shown here (I have a vertex format class that manages what needs to be set where, and something to managed state changes et cetera), but you get the idea of how it all works.

Well that was one big change down, on the next page of this blog entry will be another big change, replacing glMatrixMode() and glLoadMatrix().

Cg/HLSL Saturate

I’ve been converting my Cg shaders I wrote for my AGT work in university into GLSL, however I came by an annoying problem, Cg uses a function that doesn’t exist in GLSL, but does exist in HLSL. I eventually found documentation on what it does and so looked up the GLSL equivilant. The Cg version shown below…

float3 outValue = saturate( inValue );

Becomes the following when converted to GLSL…

vec3 outValue = clamp( inValue, 0.0, 1.0 );

 

Thought this might be useful for others who might be having the same problem.