Lens Flare Sample
This sample shows you how to implement a lens flare effect by using occlusion queries to detect when the sun is hidden behind the landscape.Sample Overview
Lens flare tries to simulate how bright lights behave when they shine into a film camera. Computer games often have a hard time conveying an impression of brightness because monitors and televisions have only a limited range. After all, it is impossible to have a pixel brighter than white. How can you show the difference between a character wearing a white t-shirt, which is not very bright, and the sun, which is very bright indeed? The solution is to emulate what happens when a movie director points a camera directly at a bright light source. The excess light bounces around inside the camera. This excess light overloads the lens and creates strange glows and flares in places other than where the light is supposed to be shining directly. Most movie directors try to avoid these effects. However, some directors use them for artistic effect. Games can do the same thing. Games can emulate the flares that would be produced inside a physical camera. The flares provide a visual cue that indicates the brightness of the light.
The lens flare effect in this sample is formed of two components: a large, soft glow, and a row of smaller circular flare shapes.
The sample uses hardware occlusion queries to efficiently detect when the sun is hidden behind the landscape. This enables you to fade out the lens flare effect when the sun is not visible.
Sample Controls
This sample uses the following keyboard and gamepad controls.
| Action | Keyboard control | Gamepad control |
|---|---|---|
| Rotate the camera | UP ARROW, DOWN ARROW, LEFT ARROW, and RIGHT ARROW | Right thumb stick |
| Move the camera | W, S, A, D | Left thumb stick |
| Reset the camera position | R | Right thumb stick press |
| Exit the sample | ESC or ALT+F4 | BACK |
How the Sample Works
The lens flare is built up out of two different effects. First, the sample draws a large, soft circular glow, which is centered on the sun. The glow is white in the center. It fades to transparent at the edge of the circle. These screenshots show the glow effect in isolation. The screen shot on the left shows the glow over a black background; the one on the right shows the glow over a terrain mesh.
Second, a number of flare sprites are positioned at arbitrary points along a 2D line. Three slightly different circular sprite textures are used to draw the flares. Each one is given a different scale and tint. You calculate flare positions by projecting the position of the sun on the 2D screen, then laying out each sprite along a line that runs from the sun through the center of the screen. This causes the flares to move position depending on the location of the sun. When the sun is near the middle of the screen, the flares will all be bunched up near the center. On the other hand, the flares will have a wider spacing when the sun is near the edge.
The following screenshots show the flare effect in isolation. The image on the left shows the glow over a black background; the image on the right shows the glow over a terrain mesh.
Finally, these screenshots show both the glow and flare effects combined.
If you move the camera around so the sun is hidden behind a hill, you will notice that the lens flare gradually fades out. This is an important part of making a convincing effect. It would not look very realistic if you could see the sun right through other pieces of scenery! The depth buffer on the graphics card usually handles this kind of sorting automatically. The depth buffer ensures that objects farther away will never be drawn over the top of closer objects. However, the hardware depth buffer does not work for lens flares. The flare is a 2D shape drawn over the top of the 3D scene. This means it cannot be depth sorted in the usual way. For example, you might have a flare in the lower-left corner of the screen, which is caused by the sun at the top right, and you want that flare to be visible because there is no scenery blocking the upper-right corner of the screen, even if the lower left where you have placed that flare is covered up by the terrain.
The solution is to use a hardware occlusion query to make your own visibility determination. An occlusion query enables us mark a section of our drawing code. Later, you can ask the graphics card how many pixels it rendered. This works as follows:
- Draw the terrain mesh.
- Begin an occlusion query.
- Draw a small test 2D rectangle, centered on the sun, and placed at the largest possible Z depth. You can turn off color writes while you do this—the test rectangle will never show up on the screen. The graphics card will draw pixels only if they are not covered up by the terrain.
- End the occlusion query.
- Ask the occlusion query how many test pixels were drawn. This tells you what percentage of the sun is visible.
- Draw your flare effect, using the sun visibility to control the alpha.
This works well, but it is inefficient, because the graphics card does not actually carry out a command as soon as you give the command. To speed things along, the graphics card runs in parallel with your CPU. When you call a drawing method, this writes a command into a buffer. At some later time, the graphics card will examine that buffer and carry out the command. So you cannot ask it questions like, "Draw this triangle. OK, how many pixels did you just draw?" because at the point where the CPU asks "how many pixels?," the GPU will not yet have noticed your original "Draw this triangle" request. You could wait for it to catch up enough to provide the occlusion result by going into a loop and testing the OcclusionQuery.IsComplete property until it becomes true, but that would be a waste of CPU time. You have better things to do with your CPU than just sit around waiting for the GPU!
To keep the CPU and GPU running smoothly in parallel, you must delay your occlusion queries by one frame. Each time around the game loop, your query is now inverted: "How many query pixels did you draw during the previous frame? OK, now draw this triangle and remember the result for later." This way the GPU has plenty of time to get around to processing the command buffer before you ask it for the query result. The downside is that you are now a frame late in reading your occlusion information. Thus, you are choosing your flare alpha based on how much of the sun was visible during the previous frame, rather than on the latest camera position. This sounds bad, but in practice the camera tends to move quite slowly. The flare fades smoothly in and out as the sun goes behind the scenery. This means the off-by-one-frame timing error tends to be small and is hard to notice. You can see it most clearly when the sample first starts up. No lens flare is drawn during the first frame. While the sun is supposed to be visible, the GPU has not yet had a chance to finish your first occlusion query.
To display the occlusion query test rectangle on your screen, comment out the line where LensFlareComponent.UpdateOcclusion sets GraphicsDevice.BlendState to ColorWriteDisable.
The lens flare is implemented as a DrawableGameComponent, so it can easily be reused in your own games. Just add a LensFlareComponent to your Game.Components collection, and make sure you set its View, Projection, and LightDirection.
Extending the Sample
You can easily change the size, color, and position of the flare sprites by altering the declaration of the LensFlareComponent.flares array.
Try adding rays as lines (or thin triangles) that fan out from the sun toward the edge of the screen.
Try combining this lens flare effect with the bloom post-processing technique from the Bloom Sample.
This sample draws a single lens flare centered on the sun. However, a game with more than one light source (perhaps drawing a night scene with car headlamps, or a game set in space) might want to tone down the effect, and then apply it to each of its many lights.