• entries
37
52
• views
42754

# Digitanks - The Early Bloomer

554 views

One of the first visual improvements I made to Digitanks was to add bloom. Ah, bloom, the often used and eternally abused layman's visual enhancement. It makes a simple-looking scene look more sophisticated by taking out that embedded polygonal look and imbuing the frame with gradients and natural, circular shapes. It's a relatively easy way to upgrade your game's visuals from 1998 to 2008 with not too much development effort. But, if you overdo it, it can look nasty and terrible.

One of the most notorious abuses of Bloom was in the original Fable. This game put bloom everywhere. Every bright portion of the scene got bloom on it. The problem is, that's not the way light works in real life. In reality objects will gain that halo if they have a very bright light falling on them, or if they're emitting light. In Fable, they made things glow just because. The guy's clothes, the sidewalk, the rocks and leaves, everything was glowy and it gave me a headache after a while. It was just uncomfortable to be looking at all of this glowy crap. I suppose it looks nice in that screenshot but not so much when you're playing the game.

In general, the rule of thumb you want to use is that if the object is either emitting light or a very, very bright light is falling onto the object it can get bloom. If you look at a stoplight at night, you'll see a red glaze around the light. (Hopefully you won't see a green glaze around the light, because that means you need to hit the accelerator.) That glaze looks really neat in video games. But you'll never see that glaze appear on the sidewalk. On a very sunny day in the desert, you might see this glaze on a white surface that's next to a dark area (say, the entrance to a building?) but you won't see it on the ground or on people's shirts.

I suppose you can't much blame Fable for getting it wrong, it was one of the first games to implement bloom. They were going for a semi-realistic, if not stylized environment where bloom simply doesn't fit all that well. It was a new technology, and they simply didn't know what the most effective use of it was. The game that I have to give credit to for the correct use of bloom is Tron 2.0. The rules I just explained to you about regular objects glowing? Well Tron 2.0 threw that out the window. They could afford to do that though, because they weren't going after that semi-realistic style. The primary difference is that Tron 2.0 has mostly dark backgrounds, so the glow of the bloom doesn't wash out the image and cause damage to my vision. Tron 2.0 even has more bloom than Fable did, but it ended up working better because of the environments of the game.

More recently in video games the technology of "high dynamic range rendering" has percolated into most AAA titles. This change has provided a much better use of bloom for photorealistic scenes. In HDR rendering, the scene is rendered with an infinite (well, almost infinite) range of light values, from totally dark to bright as the sun, and then a small slice of those values is removed and rendered to the screen. Parts of the scene that are too dark in that slice are rendered as completely black, and parts that are too bright are rendered as completely white. Bloom is used to highlight the parts of the scene that are overexposed and completely white. This is much closer to what happens in actual photography and with the human eye. One of the first games to use HDR rendering was Valve's Lost Coast demo, and the benefits of using bloom in this situation were clear. Lost Coast looked fantastic, and HDR is now a standard feature in all Valve games. The use of bloom this way in HDR matches what we usually see with our eyes and follows the rule I put forward before - only light sources and very, very bright surfaces receive the glowy effect.

So getting back to Digitanks. Bearing our examples in mind I set forth to add the perfect amount of bloom to Digitanks. At a technical level, bloom is just a blurring of the bright portions of the scene. The first step is to render the game scene to an off-screen frame buffer. Then, a filter is run over the scene so that the bright portions of the image are isolated. Lastly, the blurred images are superimposed over the final image. Let's take a look at the initial scene that we'll be dealing with. We have some bright elements like the shields, combined with some darker elements. Overall the scene is fairly bright. The first thing we need is a shader that passes the bright elements of the scene through while omitting the dark elements. For this I wrote what I call a "bright-pass" shader. Here's the glsl sources for it, non-technical people can just skip it:
 uniform sampler2D iSource; uniform float flBrightness; uniform float flScale; void main(void) {     vec4 vecFragmentColor = texture2D(iSource, gl_TexCoord[0].xy);     float flValue = vecFragmentColor.x;     if (vecFragmentColor.y > flValue)         flValue = vecFragmentColor.y;    if (vecFragmentColor.z > flValue)         flValue = vecFragmentColor.z;     if (flValue < flBrightness && flValue > flBrightness - 0.2)     {         float flStrength = RemapVal(flValue, flBrightness - 0.2, flBrightness, 0.0, 1.0);         vecFragmentColor = vecFragmentColor*flStrength;     }     else if (flValue < flBrightness - 0.2)         vecFragmentColor = vec4(0.0, 0.0, 0.0, 0.0);     gl_FragColor = vecFragmentColor*flScale; }

First, this shader finds the brightest value of each pixel. If the pixel is brighter than flBrightness, then it saves the value. There's a slight ramp near the cutoff point where the value is ramped in softly so that abrupt changes in pixel brightness don't cause lines. Three copies of the scene are made at three different resolutions, and the shader is applied to each copy at three different intensities. This is done to take advantage of the hardware acceleration's very fast image scaling operations. The highest resolution takes only the very brightest portions of the image, and the lowest resolution receives more less-bright portions of the image. This is done by passing progressively lower values into the flBrightness uniform in the shader.

One important thing to note about this bright-pass shader is that it doesn't take the average pixel brightness, but rather the brightest color value. That is, it doesn't take (r+g+b)/3 but rather looks at the brightest of the three to determine if it passes the filter. That's important, because it allows bright solid colors to pass. For example, the tanks in the game all have bright solid colors, in this case solid blue. Blue ends up having a bright value, but red and green have values of 0. If we had taken the average, we would have gotten (0+0+1)/3 = 0.3, which wouldn't have passed the filter. However, since we use only the brightest pixel to test, this bright blue value passes.

Once we have our bright scene portions isolated, then we can blur them. Each frame is blurred using a simple gaussian filter. I won't post the shader for that since it's mostly covered rather well in other places, but you can see the results. Just like before, the blur is applied to each of the three different resolutions. Since the lower resolution frame gets the same blur as the higher resolution frame, its blur is actually much more pronounced once it gets resampled up to the final image size.

The next step is to combine these three blurs together into a single frame. This is done using additive blending, which is fantastic for creating effects that seem to be glowing. The values for brightness were chosen carefully so that the parts of the scene that I wanted to stand out can be seen clearly. For example, the tank that has his shields up the highest (it has more energy for its shields since hasn't moved as much as the others) has a very bright bloom on its shields, while the tank with less shields has barely any bloom on his shields. So, the bloom actually helps to highlight the shield strength of the tank. The movement and range indicators also get bloom highlighting on them, to help them stand out from the terrain shader.

Lastly, the bloom gets combined with the original scene, again with additive blending.

That's it! The scene looks much more lively now. Notice the slight haze around the targeting trajectories and the brighter targeting rings on the ground, and the bright shields bleeding their energy into the surrounding terrain. The terrain also has gained a warmer feel to it. It complements the scene, and it doesn't get in the way, I'm pretty satisfied with this bloom.

If you want more technical details on how to implement bloom with glsl, there's a great tutorial on Phillip Rideout's website. Thanks for reading!

That is a nice little write up you have posted - good job. I've been quietly reading your journal entries, and am impressed at the rate at which you have been progressing. Are you working on this full time, or as a side project?

One thing that I think would be interesting to see is a with and without bloom screenshot similar to your final image. That would really emphasize the difference between the two, and I'm interested to see the difference in contrast between the two images.

Keep up the good work!

a much better method for bloom than what you have now (though it is more expensive)
Is to have a bloom buffer, i.e. instead of taking the existing screen + doing a bloom with that.
start with a black texture, render the depths, and then render the meshes that you want bloom to occur with. This way you gain far better control over the actual "glowingness"

Thanks guys.

Don't be too impressed at my rate of progress, I've been working on this since April and posting to my blog all the while, but only posting to this journal once a day since I purchased an account a week or two ago :)

I did include a shot without bloom, it's up above! You want to see them side by side though? Here you go:

zedz, I was actually thinking of a way of doing bloom that would afford a large degree of control, which is to have each material for every model have a "bloom map" which defines the amount of bloom that material emits. Essentially it'd be like the "bloom buffer" idea that you mentioned, except that when the models are rendered to the bloom map, they are rendered using the bloom map instead of the normal diffuse map. That way you can control exactly what gets bloomed and what doesn't by authoring these bloom maps. That's a lot of work though, I decided against it :)