You can use RenderTextures to produce Fog of War to great effect. Here’s a few simple steps you can follow to quickly get a simple Fog of War set up.
-
Create a Rendertexture in your project, name it whatever you like. Set the dimensions to 2D, size to 1024x1024 (or lower if needed), and color to ARGB32. You can tweak the other settings for quality but they don’t matter too much. this texture will be used to map what areas of the map are revealed and which areas are shrouded in fog
-
In your scene create a “Fog Camera” specifically for writing to the Fog texture. in most cases you’ll want the camera to be center high above your map looking straight down and set to orthographic. Adjust the size so that it sees the same area that the fog would visibly cover. other important settings include setting the target texture to the new RenderTexture you created, and setting the culling mask to a specific layer that will have objects that clear the fog (I call this layer the Fog Stencil). Now from here on the other settings on the camera will dictate the type of Fog behavior you want:
- Dynamic Fog: (To have the fog clear when you get near and recover when you leave, for showing current vision) Clear flags = Color, Background color =transparent black,Culling Mask = Fog Stencil, Target Texture = fog renderTexture
- Static Fog: (To have the fog remain uncovered once explored, for showing explored areas) Clear Flags = Depth,Culling Mask = Fog Stencil, Target Texture = fog renderTexture
- Disable Fog Removal: (previously explored areas remain cleared but walking into the darkness doesn’t clear fog)Clear Flags = Depth, Culling Mask = nothing, Target Texture = null
- Reveal All: (remove all fog from the map, like for cheats) Clear Flags = Color, Background = white,Culling Mask = Fog Stencil, Target Texture = fog renderTexture
- Shroud All: (cover entire map in darkness, vision does not clear fog) Clear Flags = Color, Background = black, Culling Mask = Nothing
- Next lets add the actual Fog the players will see to the map. simply Create a Plane gameobject and have it scaled up so that it fits as close as possible to match the camera’s visible bounds that you set earlier. As long as you are close enough the fog should appear to line up correctly when finished. Raise this plane so that its above your all the elements in your terrain but below your main camera so that it can properly obscure the map. Also just a note that you might need to spin the plane 180 degrees on the y-axis depending on your setup. To make the Fog render correctly we’ll need to add a custom shader,
Now for the Shader, you mentioned that you have no experience with shaders, no worries you can use the one provided below. Create a new Material and have it use this shader:
Painter Render Texture surface shader
Shader "Painted Render Texture Shader"
{
Properties
{
_Color ("Color", Color) = (0,0,0,0.7)
_MainTex ("Canvas", 2D) = "white" {}
_Smoothness ("Feather", Range(0,0.1)) = 0.005
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" "LightMode"="ForwardBase" }
Blend SrcAlpha OneMinusSrcAlpha
Lighting off
LOD 200
CGPROGRAM
#pragma surface surf NoLighting noambient alpha:blend
fixed4 _Color;
sampler2D _MainTex;
float _Smoothness;
struct Input
{
float2 uv_MainTex;
};
fixed4 LightingNoLighting(SurfaceOutput s, fixed3 lightDir, float aten)
{
fixed4 color;
color.rgb = s.Albedo;
color.a = s.Alpha;
return color;
}
void surf (Input IN, inout SurfaceOutput o)
{
half4 gaussianH = tex2D (_MainTex, IN.uv_MainTex + float2(-_Smoothness,0))*0.25;
gaussianH += tex2D (_MainTex, IN.uv_MainTex )*0.5 ;
gaussianH += tex2D (_MainTex, IN.uv_MainTex + float2( _Smoothness,0))*0.25;
half4 gaussianV = tex2D (_MainTex, IN.uv_MainTex + float2(0,-_Smoothness))*0.25;
gaussianV += tex2D (_MainTex, IN.uv_MainTex ) *0.5 ;
gaussianV += tex2D (_MainTex, IN.uv_MainTex + float2(0, _Smoothness))*0.25;
half4 blurred = (gaussianH+ gaussianV)*0.5;
o.Albedo = _Color.rgb * blurred.g;
o.Alpha = _Color.a - blurred.g;
}
ENDCG
}
FallBack "Diffuse"
}
Then drag your Fog RenderTexture onto the Canvas field for the shader, the other fields should be fine for most circumstances.
Note: the Smoothness|Feather is not a requirement for a functional fog, and you’ll likely want to keep the value small. It just adds a little quality to the fog, and can be safely removed if needed.
Also note that the Shader only reads the green channel.
-
Now its time to add the vision stencils to the entities in your map which will provide you vision and clear the fog. Select a gameobject that will be providing vision and add a child. This child will use the same Fog Stencil layer that the camera. Here you can use a Sprite Renderer, a Textured Plane, or even a procedural mesh (for line of sight), using a textured plane is the simplest option. The texture you likely want for is a feathered, semi-transparent circle. The feather will help the fog look better, and Unity’s Default Particle Texture is a great starting example, but you’ll likely want a tighter feather. For simplicity, the shader my stencil used also was simply Unity’s Particles/Additive shader, I didn’t need to write a new shader. You can also instead use a cone texture if you want a flashlight or field of vision effect.
-
Simply having the rest of the map be semi-dark yet still have all the enemy units visible in the darkness kind of defeats the purpose of the Fog so you need to be able to turn off the entities you want to be obscured by the fog. You can attach this script to those entities:
TextureStencilScript
using UnityEngine;
//turns off the referenced renderer if the fog camera sees the transform's position on a "fog" pixel
public class TextureStencilScript : MonoBehaviour
{
public Camera cam; //The Camera using the masked render texture
public Renderer myRenderer; // reference to the render you want toggled based on the position of this transform
[Range(0f,1f)]public float threshold = 0.1f; //the threshold for when this script considers myRenderer should render
// made so all instances share the same texture, reducing texture reads
private static Texture2D myT2D;
private static Rect r_rect;
private static bool isDirty = true;// used so that only one instance will update the RenderTexture per frame
private Color GetColorAtPosition()
{
if(!cam)
{
// if no camera is referenced script assumes there no fog and will return white (which should show the entity)
return Color.white;
}
RenderTexture renderTexture = cam.targetTexture;
if(!renderTexture)
{
//fallback to Camera's Color
return cam.backgroundColor;
}
if(myT2D == null|| renderTexture.width != r_rect.width || renderTexture.height != r_rect.height)
{
r_rect = new Rect(0,0,renderTexture.width,renderTexture.height);
myT2D = new Texture2D((int)r_rect.width,(int)r_rect.height,TextureFormat.RGB24,false);
}
if(isDirty)
{
RenderTexture.active = renderTexture;
myT2D.ReadPixels(r_rect,0,0);
RenderTexture.active = null;
isDirty = false;
}
var pixel = cam.WorldToScreenPoint(transform.position);
return myT2D.GetPixel((int)pixel.x,(int)pixel.y);
}
private void Update()
{
isDirty = true;
}
void LateUpdate()
{
if(!myRenderer)
{
this.enabled = false;
return;
}
myRenderer.enabled = GetColorAtPosition().grayscale >= threshold;
}
}
Hook up the fields on the script and then hit play, and you should have a working fog of war system. You can even increase the threshold for some entities, effectively giving a “stealth mechanic” so that they would only be visible to entities with “brighter” vision stencils. You can even create a 2nd RenderTexture and Fog Camera so that you can layer the fog effects if you want(like having both Dynamic and static fog to mix current vision and explored areas).