Making a 3D Game with OpenGL: Deferred shading and stuff
// July 19th, 2012 // OpenGL
Lately OpenGL and C++ is pretty much the only thing I do for clients, mostly for installations and iPad apps. And that’s gowning pretty well. But as a creative code guy, I pretty much stayed in our own tech subculture. But when it comes to cutting edge graphic techniques, we can learn a lot from those other guys, the game developers.
So one sunny afternoon, I decided to do just that and I started to make my own 3D game from scratch.
I wanted to write a blog-post when it was finished. But I hugely underestimated the work (Now I understand why indie gamedevelopers take +3 years to finish a game). And I’ve already done so much it wouldn’t fit in one blog post. So I’m going to try to make multiple blog-posts, to light out different technical things I’ve used for my game, and keep you updated about the progress of the game itself.
Or more game engine, since I don’t really have any game play at the moment:
As you can see, there are still a lot of glitches and things to be done, but I’m happy where it’s going.
Some of the things I’ve done so far (not always complete, final or clean).
-The terrain generation, it’s an infinite/live generated terrain.
-Seamless day-night transitions.
-Cascade shadow maps, SSAO (screen space ambient occlusion).
-Normal maps, alpha textures, alpha shadows.
-Some simple LOD (level of detail) setup for the terrain mesh and the 3D objects.
-Bone animations in the vertex shader + interpolations between the animations. (models are loaded with Assimp)
-The main part of the rendering pipeline.
-Learned the basics of modeling and animating in Blender
-and a lot of other stuff.
The current Render pipeline
One of the things I wanted to try was deferred shading, just for the heck of it. But I’m really glad I did. Since it seems much faster, and has some additional advantages (there are also some disadvantages)
How does it work:
You just render all the main geometry once, In 3 textures. a depth texture, a color texture with just the diffuse. and a color texture with the normals in worldspace.
you can do that in OpenGL all at once, with an FBO (frame buffer object) with multiple color attachments. And since you don’t do any lightning, its pretty fast. After that, you use those textures to calculate the lighting etc.
The big advantage is that you only have to calculate the lightning on the pixels that are actually visible on the screen. So you have no overhead of overlapping objects. And if you want to do some post processing stuff, like ambient occlusion or depth of field, you already have the depth texture and normal texture in place.
To get back from the depth to the worldspace, you just multiply the screen space with the inverse of the perspective matrix:
float depth = texture2D(depthTexture, uv_var).x*2.0-1.0; vec3 viewSpace ; viewSpace .xy = uv *2.0 -1.0; viewSpace.z=depth; vec4 worldSpace = perspectiveInvMatrix* vec4(viewSpace,1.0); worldSpace.xyz/=worldSpace.w;
But what I really liked about it, is that you can have a lot of dynamic lights in your scene. When you use forward shading, your happy if you can use a couple of lights. But but with deferred shading you can have hundreds. You just render the lights as volumes in a separate pass (sphere for a point light, a cone for a spotlight etc). So you only have to calculate the light on the position where it really affects things, and since you use the normal and depth texture as input, only on the geometry you really see.
The light volumes rendered as normal geometrie:
The fragment shader calculation I use for the light volumes:
vec3 dir = worldPos.xyz-lightCenter; // worldPos from the depth float l = length( dir); float dist =1.0-pow((clamp (l ,0.0,lightSize_var)/lightSize_var),2.0); // quick light falloff float lambert =clamp(dot(normalize(dir) , -normal ),0.0,1.0); //shading gl_FragColor = vec4(lightColor *dist * lambert ,1.0);
The result of the Light pass:
I just use a blendfunct GL_ONE, GL_ONE (add) for all the lights, although technically not correct. But it looks good enough.
The lights in the final scene:
Lambert texutre lookup
An other neat trick I discovered while doing research for this, is using a Texture to lookup your shading color (found it in the Valve Team Fortress 2 Paper).
The great thing about this is that you can edit your shading in photoshop. In my case I used a 2D texture, so I can have al the subtle light differences between day and night.
This is the texture I use now, but it still needs some work:
I just use the shading color for the u and the time for the v lookup.
Here is the plain old shading:
And the shading after the look-up:
That’s it for now, hopefully some more blog-posts to follow this up
Source code and assets at Github ( at your own risk )