This is a post duplicated from the Digitanks website.
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.
Thanks for reading!