Using D3 and SVG Filters to Create a Fog of War Effect

Data Visualization
Posted on Sep 14th, 2014

The Fog of War is a common type of game data visualization, frequently used in Real Time Strategy (RTS) games. At the core, the idea is simple: areas on the map where you control units (or have explored) are visible, and unexplored areas are dark.

This post describes a simple way to achieve this effect for a browser based game using SVG filters and masks, along with a little bit of D3.js. Filters can be not only a powerful prototyping tool, but also an easy way to achieve impressive visual effects for browser based games.

Note: Filters are not supported in all browsers. If using filters in production, be sure to be aware of the performance tradeoffs and the browsers your audience uses. View demo.

Masks

There are many ways to create a fog of war effect, but one potential simple way to think of it as a mask effect. For example:

Here, we have two rectangles which both have a width of 200 and a height of 175. One rectangle is black, the other is blue. However, the reason why a pulsating circle appears as opposed to a blue rectangle is because we have added a mask (and used D3 to change its radius). Let's look at the full source code quickly:


    <svg id='mask-example' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
        <defs>
            <mask id='mask'>
                <circle id='circle-mask' cx='100' cy='75' r='25' fill='#ffffff'  />
           </mask>
        </defs>

        <rect x='0' y='0' width='200' height='175' fill='#000000' />
        <rect x='0' y='0' width='200' height='175' fill='#ffffff' mask='url(#mask)' />
    </svg>

To create a mask element, we just need a defs tag with a mask tag inside of it. The mask tag has an ID which allows other elements to reference it. Inside of the mask tag, elements can be placed inside which create the actual mask:

<circle id='circle-mask' cx='100' cy='75' r='25' fill='#ffffff'  />

A fill of white ('#ffffff') will be masked completely, while black (#000000) will not appear. To give an element a mask, the mask attribute is added to the element - for instance, we give the blue rectangle a 'mask' property attribute:
<rect x='0' y='0' width='200' height='175' fill='#ffffff' mask='url(#mask)' />

Now, the blue rectangle will be masked by whatever elements are contained with the <mask id='mask'> tag. In this example, we have a single circle in the mask tag. The mask is a composition of whatever elements (rects, circles, paths, etc.) are placed in the mask tag. Multiple elements can be placed in the mask as well, and each element in the mask can have a different fill color:

Because any SVG element can be placed in a mask, we can dynamically add and remove elements from a mask. We can transition property values, allowing the mask to fade in. We can also place elements with gradients or patterns in an element in the mask tag. And, if we want to make a fog of war effect, we can use filters.

Filters

A previous post discussed filters, so we won't go too in depth here. There are an infinite amount of possible filter effects that can be created, but chances are you're familiar with the common primitives - such as blur, turbulence, lighting effects. Let's take a look at a rectangle and circle before a filter is applied to it:

We can make this far more interesting by adding a filter effect. One filter effect I've been playing around with is a combination of a blur, turbulence, and displacementMap (more information on these filter primitives). Here are the same elements with that filter applied to it:

To me, this filter reminds me of a torn map. Filters can be applied as properties to elements (like rect, circle, and path), which means we can apply filters to elements that are in a mask.

Conclusion

The fog of war is a common form of data visualization in games. Using d3.js and SVG filters to create a fog of war effect for browser based games is not only easy, it provides great flexibility and power.

In the example below, a mask is created and circles are added to it for explored areas. When the circles are added, their fill color is transitioned from #000000 to #ffffff, causing a fade-in effect. Each circle added to the mask also has a filter effect, causing a sort of jagged visual effect. A dark, gray-scale image of the map is masked over the 'normal' map image, which allows us to provide a subtle hint of what's hidden behind darkness. To see the effect in action (a circle being added to the mask), click anywhere on the map:

However, the tradeoff is often at the cost of performance. In the example, you likely noticed that performance hits are noticable even after a relatively small amount of elements to the mask. While this is just an unoptimized demo, using complex SVG filters can be a large bottleneck. One other area that could be optimized is the filter itself. A simplier filter is more efficient, as less processing has to be done to fewer pixels.

If you were developing an RTS game where the fog of war was constantly changing, this method is likely a bad fit. For games that need only a small number of elements masked, like a rogue-like map exploration HTML5 game I'm building, the tradeoff is acceptable.

With tradeoffs in mind, and depending on your application, using SVG filters in browser based games can be great. Even if the performance isn't acceptable for your use case though, filters can serve as a powerful prototyping tool.

Happy filtering, now go filter yourself!

NEXT | Designing a Browser Based Game with D3.js
PREVIOUS | Data Visualization in Games
All Posts

Engage

Comments