Implementing Attractive Fog of War in Unity

One of the earliest features that I implemented for Gridpulse Legions was fog of war. My intention was for the feature to work similarly to how it works in the original Starcraft; that is to say, it should satisfy the following criteria:

  • Areas in the line of sight of the player’s units are not covered by fog.
  • Areas that are not in the line of sight of the player’s units, but which have been explored previously, are covered by semi-transparent fog (you can see the terrain and any static structures/elements that were in that are last time you were there, but you cannot see any new non-allied units that are there).
  • Fog covering unexplored areas is completely opaque – you cannot see anything there except for a thick black shroud.

At a high level, the solution I came up with was to render the visibility information into a RenderTexture, which is then drawn onto the terrain using a projector through a shader that takes care of rendering and blending the shroud.

Here’s a video of the feature in action:

Let’s go over each of the components in turn.

Recording Visible Areas

The first piece of information that we’ll need is the visible areas – that is, the areas that are currently within the line of sight of the player’s units. My solution involved two steps:

  1. Generate a mesh representing the visible range of each unit.
  2. Render these visibility meshes to a RenderTexture.

The visibility mesh generation can be as simple as a static mesh with its parent set to the unit whose visibility it is representing (so that it automatically moves with that unit). If your line of sight algorithm is more complex and takes elevation and obstacles into account, you may needed to procedurally update your mesh on the fly using a 2D visibility algorithm. The specifics of these algorithms are beyond the scope of this article and will differ per game, but for simplicity’s sake, you can just assume that this mesh is simply a static flat mesh floating above the unit’s head. It makes no difference to the rest of the implementation how the mesh is generated.

Place these meshes on their own layer. We’ll call this layer FOV and we’ll use it in the next step. Make sure that the main camera isn’t rendering this layer. Here’s what it’ll look like in the scene:

vision_mesh

This mesh represents the visible range for a single unit.

Camera Setup

Once you have your visibility meshes, you’ll need to set up two cameras to render these meshes, each to their own render texture. Why two cameras? Because we’ll need a separate texture to display unexplored areas (completely opaque black) from the one used to display discovered areas that are not currently within the line of sight (semi-transparent black). Ultimately, we want to render textures that look like this

shroud

, which we will then project onto the terrain using a Projector component. The pink areas in the image above correspond to the visibility meshes.

We’ll render these textures by creating dedicated cameras for fog of war and configuring them to only render the visibility mesh layer. Our shader will look at this image and will project a black shroud onto the areas where the pink is not present and will ignore the areas where the pink is present.

First, create two new RenderTextures. You can actually get away with using fairly low-res RenderTextures if you turn anti-aliasing on. In Gridpulse Legions, the RenderTexture for the dark shroud (the shroud for unexplored areas) is 64×64. The RenderTexture for the normal shroud is 128×128.

RT_editor

Set up the camera size/position so that it covers the entire map and configure it to only render the FOV layer. Also set the Target Texture to the corresponding RenderTexture.

Fog_Cams

Both cameras will be identical in setup except for two things:

  1. The camera for the dark shroud will have its clear mode set to Don’t Clear. As you’ll see a little bit later on, the openings in the fog will correspond to the locations of the visibility meshes within the camera’s viewport. As the visibility meshes move around, the RenderTextures will change to reflect the new locations of the meshes. But for the dark shroud, we actually want areas that have been explored to remain uncovered. See this image for clarification – notice that the dark shroud shows a superset of the visible areas that the normal shroud shows
    • shrouds_sidebyside
  2. The target RenderTextures will be different. The camera for the dark shroud will render to the dark shroud RenderTexture and vice versa for the other camera.

Rendering the Fog

With the above setup, you should now have two RenderTextures accessible during runtime whose pixels directly represent the areas of your map that need to be covered in fog. The next step is to actually draw the fog. For this, we’ll need a few different things:

  1. A shader for rendering the fog.
  2. Two materials, one for each fog type. Each material will have a different opacity value that will be used when rendering the fog (100% for the dark shroud and some lower value for the normal shroud).
  3. A script, which will control the blending of the fog.
  4. Two projectors, one for each fog type. Each projector will project its fog onto the map through the material described in step #2.

Let’s take a look at that shader. I’ll show just the fragment shader below, since the rest of it is just boilerplate shader code for projectors (see the full code snippet here).

fixed4 frag (v2f i) : SV_Target
{
    float aPrev = tex2Dproj(_PrevTexture, i.uv).a;
    float aCurr = tex2Dproj(_CurrTexture, i.uv).a;

    _Color.a = lerp(aPrev, aCurr, _Blend);
    return _Color;
}

As you can see, it’s using the alpha values of the shroud textures to figure out where to render the fog. When the pixel corresponds to an area of the shroud texture where the visibility mesh was rendered, the alpha will be 1 and the _Color variable will become transparent, resulting in the fog not being visible at that pixel.

You’re probably wondering now why we have _PrevTexture and a _CurrTexture and are lerping between the two instead of just using a single shroud texture for the lookup. This is a special technique for making the actual revealing/hiding of the fog look very smooth. This, plus the anti-aliasing, lets you achieve the illusion of smooth fog despite your fog texture not having nearly enough pixels to represent every visible spot on the map. As you’ll see in the next code snippet, each projector actually creates two textures internally – one for the previous visibility state and one for the current state. The script will lerp between the two and, upon completing the lerp, will blit the texture for the current state into the texture for the previous state and will blit the texture from the corresponding fog camera’s target texture into the texture for the current state. After doing this swap, it will repeat the process.

using System.Collections;
using UnityEngine;

public class FogProjector : MonoBehaviour
{
    public Material projectorMaterial;
    public float blendSpeed;
    public int textureScale;

    public RenderTexture fogTexture;

    private RenderTexture prevTexture;
    private RenderTexture currTexture;
    private Projector projector;

    private float blendAmount;

    private void Awake ()
    {
        projector = GetComponent();
        projector.enabled = true;

        prevTexture = GenerateTexture();
        currTexture = GenerateTexture();

        // Projector materials aren't instanced, resulting in the material asset getting changed.
        // Instance it here to prevent us from having to check in or discard these changes manually.
        projector.material = new Material(projectorMaterial);

        projector.material.SetTexture("_PrevTexture", prevTexture);
        projector.material.SetTexture("_CurrTexture", currTexture);

        StartNewBlend();
    }

    RenderTexture GenerateTexture()
    {
        RenderTexture rt = new RenderTexture(
            fogTexture.width * textureScale,
            fogTexture.height * textureScale,
            0,
            fogTexture.format) { filterMode = FilterMode.Bilinear };
        rt.antiAliasing = fogTexture.antiAliasing;

        return rt;
    }

    public void StartNewBlend()
    {
        StopCoroutine(BlendFog());
        blendAmount = 0;
        // Swap the textures
        Graphics.Blit(currTexture, prevTexture);
        Graphics.Blit(fogTexture, currTexture);

        StartCoroutine(BlendFog());
    }

    IEnumerator BlendFog()
    {
        while (blendAmount < 1)
        {
            // increase the interpolation amount
            blendAmount += Time.deltaTime * blendSpeed;
            // Set the blend property so the shader knows how much to lerp
            // by when checking the alpha value
            projector.material.SetFloat("_Blend", blendAmount);
            yield return null;
        }
        // once finished blending, swap the textures and start a new blend
        StartNewBlend();
    }
}

Both projectors will have this component. The script just creates two inner textures and does a continuous blend between the two textures by increasing the interpolation amount in a coroutine and feeding this value to the shader, which uses this float to lerp between the alphas from the previous and current textures.

Here’s what the actual projector component will look like in the inspector (make sure you configure it to cover your map and to project onto the correct layers based on your game’s specific needs):

projectors

Here’s what the two shroud materials look like. Notice that they differ only in the color (notice the white bars under the color, which represent the alpha, are different lengths):

projector_mats

The texture fields are empty because those are created in script.

With these components in your project/scene, you now have everything you need to get the fog working your game. The fog cameras will render the visibility meshes to the shroud textures. The projectors will then use these textures to render the shroud onto the terrain, blending the fog for smooth animation.

And there you have it. Attractive fog of war in Unity.

25 thoughts on “Implementing Attractive Fog of War in Unity

  1. Great tutorial btw, thanks! Just wondering (although I haven’t tried your method yet), how do you stop the edges of the fog of war from having a hard edge? I have found that with my render texture that uses “don’t clear”, after a few frames the smooth blend on my visibility mesh just turns into a solid colour. Your visibility mesh doesn’t have any blending itself, so is it to do with your fog projector script that is solving this issue? Thanks very much and great work!

    Liked by 1 person

    1. Hey Alex, glad you found the tutorial helpful. The edges are smooth in my example because the filter mode is being set to “Bilinear”. Make sure you do the same on your texture (rather than having it as “Point”, which will give you a hard edge). Not sure if that’s what’s causing your specific issue but that’s the setting responsible for the smoothing of the edges. Let me know if you have any other questions/issues.

      Like

  2. Hello. Thank you for this tutorial. I’m trying to understand the shaders. Your link for the shader code snippet is broken. Can you fix it?

    Like

  3. Hey Andrew, really great tutorial! Works very well!

    There is a minus in this article that isn’t in the shader code on pastebin. Looks like a typo, but could lead to confusion.
    Talking about the -= in this line:
    _Color.a -= lerp(aPrev, aCurr, _Blend);

    It should just be =

    Like

  4. Thank you for great tutorial!
    Does your code support the 2nd criteria ? If so, could you give some information for that ?
    I can not understand how it works. 😦

    Like

    1. Hey Park. The code presented in the tutorial only partially fulfills the second criteria. Specifically, the tutorial code does make sure that the previously explored areas are covered by semi-transparent fog instead of with the opaque dark cloud. This is done via the “Don’t Clear” setting on the dark shroud’s RenderTexture. If you go to the part of the article that starts with “The camera for the dark shroud will have its clear mode set to Don’t Clear”, you’ll see a better description of this with some helpful images. Feel free to let me know if that part isn’t easy to understand and I will try to explain it more. In a nutshell though, the “Don’t Clear” setting makes it so that once the visibility mesh gets rendered onto that texture (via one of your units walking to the corresponding location), that spot won’t ever get cleared to black again. It’ll remain colored. Because the colored areas on the texture correspond to the openings in the fog, the dark shroud will forever have an opening on any place where it has been colored.

      Regarding the rest of the 2nd criteria about enemy units not being visible, I took care of that separately in my game by disabling renderers of enemy GameObjects that aren’t currently spotted by any of your units. There’s separate logic for buildings, since they do remain visible but they need to remain motionless under the shroud and should stay visible there even if they get destroyed (until you explore the region again.) But that logic is outside of the fog rendering, so I decided not to include it in the tutorial.

      Liked by 1 person

      1. Thank you for your kind reply.
        I could understand the key, ‘Don’t Clear’

        And maybe do you have any plan for the rest?
        I believe it’s more hard thing.

        Actually my exact question was that.
        At first I thought the old scene would be some saved image,
        but it looks as if I have to save the old unit and render it one the fly.
        Doing it will be my challenge.

        Thank you again for this good blog

        Like

    2. One thing you could do is check the render texture which holds the current field of view. Map the world position of the enemy to the texture coordinates and check if the pixel. If its black, hide the enemy, if not, show it.

      Like

  5. Hey! I checked everything today and I realized something weird.
    The light fog does not interact the way it should with the setup from your tutorial. I completely re-did the tutorial when noticing, so I don’t think I made an error.
    I’m gonna try to explain with some images..
    1 https://pasteboard.co/I2OdZzj.png
    2 https://pasteboard.co/I2OgFhN.png
    3 https://pasteboard.co/I2Ogwq3.png

    I have two agents with the FOV mesh in my scene. In img. 1 you can see what it looks like. The first two circles are correct. The third one is a mirror of the first one.
    The bug seems to be in the render textures. In img. 2 you can see the correct field of view render texture. In img. 3 the incorrect “explroed area” render texture.

    Like

    1. Hey Tom, I can’t say I’ve run into this issue before. I did notice that your render textures are both the same size, and if I recall correctly (it was a long time ago that I ran into this so I might be mistaken) I had some issues where multiple render texture instances that had the exact same settings actually were automatically made to point to the same texture. I think this was some poorly executed optimization that Unity was trying to do under the hood. It might be worth changing the size of one of the shroud textures, but it doesn’t seem like that’s what’s happening in your case, and I’m not even sure if that happens anymore.

      I honestly don’t know why one of the textures would be getting mirrored vertically. Please do let me know if you find out though – if it does turn out to be an issue with the code I presented here, I’d love to know so that I can update the tutorial.

      Like

    1. Hey Karl, really glad that you found the tutorial useful for your project. Just took a look at your write-up and the project looks really cool – I’m planning to download it and play it as soon as I get home.

      Liked by 1 person

      1. I’m glad to hear you found it cool, hope you enjoy it! It’s a bit rough around the edges but it’s a prototype after all!

        Thanks again for the awesome tutorial!

        Like

  6. I can’t seem to get this working – I’m not sure what’s wrong at all. My setup is identical to yours in every single way, is there anything obvious I could be missing? Maybe something to do with the mesh for visibility? Does the mesh need to have a collider, or at a certain height, or anything special other than being on the FOV layer? Thanks for the tutorial!

    Like

    1. No nevermind, I’m just an idiot! I realised I was never calling StartNewBlend()! Not my proudest moment. It works like an absolute dream! Thanks so much! Is there any special way you do unit/building/terrain reveal/hiding when they’re in the fog of war?

      Like

  7. Everything’s working fine – I figured out the hiding as well. One issue I’ve noticed though – the shroud texture (not the darkshroud, the explored but in fog one) seems to make the circle around my units dull as well, and the difference between the darkened areas and the current sight is so slight that you have to really concentrate to even notice it. Is there any reason this would be? I’d really like the visibility to be obvious at first glance and it’s quite difficult to tell what’s actually in your view and what isn’t.

    Like

    1. Again – nevermind! I figured it out. Interestingly enough, it was putting a transparent material on the mesh that represents the unit’s line of sight! After making it opaque again the issue has been resolved. Thanks anyway!

      Like

      1. Hey sorry for the delayed response. Anyway it seems like you already figured everything out :). I’m glad that you found the tutorial helpful.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s