Our artists continue working on the "Riverfront" level and we have just finished with the blood sprites, which took one of our artists around a month (!) to do for just 9 sprites. Moreover, we have recently added soft shadows to the engine and they look badass :). But today I am going to tell you something I am very proud of: the Dynamic Hemispheric Ambient lighting system, which made our models blend superbly well with the environment.
I adore how modern games make their models blend into the scene seamlessly. This is mainly done via advanced lighting systems and is, in my opinion, one of the hallmarks of the next-gen graphics, just like shaders, soft shadows and (subsequently) bloom was some time ago.
When you have a background with plenty of colorful objects, any model added to the scene will look like it has been inserted there with Photoshop, that is out of place. If you are making a pixel-art game, it will not be so noticeable. But in most of the other cases it will. You can fix that with lighting. When you light up the entire scene with a lightsource of some color, the whole picture becomes sleek, right? Wrong. I used to think so before I actually tried this. The lighting helps but the bitter taste remains, because in order for the models to really blend with the scene, the light has to BOUNCE off the surrounding surfaces and assume their color, like it does in real life.
In modern games, this effect is simulated with global illumination, ray tracing, radiosity, spherical harmonics and others. I recently tried reading an article on spherical harmonics and I must say it is _HARD_ even to understand, not to say to implement it (try it if you will): Cs.columbia.edu I NEEDED that effect, I wanted my scene to look sleek and I wanted it badly, so I could not just leave it there. And then I recalled a tutorial I read on hemispheric ambient light by digitalerr0r:
It is a very simple trick that produces superb results and I still can't remember why I discarded it the first time I read it. But now, when I knew what I wanted, I perused the article thoroughly. The author tells you how to implement advanced ambient lighting by using two lights of fixed color and their directions. digitalerr0r also mentions that you can sample the colors you need from a map, making it dynamic and this is where I understood that I found what I was looking for.The game we are working on, Zombie Hunter inc, is set in a unique top-down orthographic projection making it look very close to classic 2d games, while allowing us to use all the advantages of 3d graphics. Every model, every background and basically any part of the level will always be seen by player from the very same angle. Therefore we don't need to worry much about the "right" implementation of the bouncing light, where it is coming from every direction and bounces off every surface.
The only thing that really matters is the color of the ground underneath the models. If we can simulate light bouncing off the ground, assuming its color, it will be enough. So all I needed for the effect to work was to render the ground into an "ambient" texture and then sample it to get its color when computing the hemispheric ambient light for the current pixel. The sole question was: how do I find out where the "ground" is for the pixel. I did a cheap trick there - simply projected the world position of the pixel onto the ground. Once again in our setting I always know that the ground is located at z=0, so I simply computed pixel's world position in my directional lighting pass, put position.z = 0 and recomputed the uvs for the new position. Given the uvs I get the color of the ground from my "ambient" texture and then use the digitalerr0r's formula to compute ambient light. And now my models are lit from below with dynamic ground color.
Now I rendered the ambient color into a very tiny texture, only 1/10 of the current resolution. But due to the sharp nature of the rasterizer, the light sampled from the ground changed too abruptly when my models moved around. To remove this, I started bluring the ambient texture with a 6-sized kernel which made the lighting look smooth and added a feel like it is truly radiating from the ground.
I then decided to add another lighting bounce, sampled from even smaller texture (1/30 of the screen). To do so I copy the contents of my "ambient" texture into the smaller one and blur the latter again. This second bounce only counts when the object's height exceeds the specified one, adding even more reality to the effect.
The final improvement was the addition of the entire deferred lighting pipeline into this system. Instead of sampling the original colors of the ground I now light the texture up, accounting for every light source on the scene. In this way, whenever the color of the floor gets changed by the lighting, it is instantly reflected in the bouncing light. The recently-added soft shadows were accounted-for automatically as I have them as a separate texture, easy to read from. So now I have 4 standard deferred rendering textures each one 1/10 of the original screen and the fifth smaller texture 1/30 of the screen for the second bounce. The system could be optimized further, but given that it virtually costs nothing in terms of performance, I doubt I will ever spend time on it.
The DHAL technique is very close to reflections but is significantly cheaper to implement because you render the scene into tiny textures. Any game with top-down camera would benefit from this simple and incredibly good-looking technique. In our setting the floor level is always constant, but nothing stops you from using the model’s current z-position for very good approximate texture coordinates of a leveled floor. You can even adopt more sophisticated ray-casting technique to sample ambient texture around the model.
In FPS games this technique is not so easy to implement. With plenty of non-flat surfaces and concave object scattered around the level it is quite difficult to approximate bouncing light color with a flat surface. Cubemaps are not a solution as you would need to create one for every object you need to be lit. However, what you can do is take several shots of the scene with top-down camera, where on every other shot all models will be facing the camera from a different angle. You can then sample from those textures when computing DHAL lighting coming from different directions. That is quite troublesome, but the result would pay I assure you.One of the creators of XNA library, Shawn Hargreaves once posted a great article about Hemispheric Ambient Light in Gamasutra, he explains even more advanced techniques and shows how this lighting system can be used to convey a sensation of speed to your fast-moving objects:
In the next update I will show you our new level, guns and monsters, so stay tuned!