Interpolation effect on the Fog of War

Posted On: Apr 04, 2017

This was low priority, but I was thinking about it a lot so I decided to implement it real fast and I'm really happy with the results. Oh, I'm getting ahead of myself, the problem is that reveal happens instantly. This is generally fine unless you explore a big area at once, like when you walk up a hill:

Before interpolation

So we go from that to this:

After interpolation

Kinda hard to see with the low frame rate of the gif... so I guess you'll just have to read the rest of the blog! ~_^

The Method:

So the idea here is that we have 2 textures one that was previously created and another currently created one. We then interpolate from the old texture to the new texture over several frames (I'm using 6 frames, 12 also looks good). After the interpolation we set the old texture to the current texture then update the current texture. OK, now lets look at the shader:

The Shader:

Under the properties we are going to add a texture and a float:

_LastShadowMap("Last FoW (RGB)", 2D) = "white" {}
_interpolationValue("interp value", float) = 0

Then we are going to import those into the cg program. Just put these under the _ShadowMap sampler:

sampler2D _LastShadowMap;
float _interpolationValue;

Now for the surf method:

void surf (Input IN, inout SurfaceOutputStandard o) {
	// Albedo comes from a texture tinted by color
	fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
	fixed4 s = tex2D (_ShadowMap, IN.uv_MainTex);
	fixed4 ls = tex2D(_LastShadowMap, IN.uv_MainTex);

	s = lerp(ls, s, _interpolationValue);

	o.Albedo = c.rgb * (s.r + s.g) / 2;
	o.Alpha = c.a;
}

On line 5 of that snippet we get the color of old shadow map, then on line 7 of the snippet we linearly interpolate from the old shadow map color to the current shadow map color. We then continue like before.

The Code:

Now in the FogOfWar class we are going to add a few variables:

[SerializeField]
private int _interpolationFrames = 6;
//...
private int _interpolateStartFrame;
private Texture2D _lastShadowMap;

In the Awake method we create the lastShadowMap like we do the shadowMap, then set the pixels of the last shadow map like we do the shadow map. Finally we set the material's property:

_lastShadowMap = new Texture2D(_textureWidth, _textureHeight, TextureFormat.RGB24, false);
// ...
_lastShadowMap.SetPixels32(_pixels);
_lastShadowMap.Apply();
// ...
_fogMaterial.SetTexture("_LastShadowMap", _lastShadowMap);

Next we are going to add something to make the initial clear not take _interpolationFrames to appear:

private void Start()
{
	UpdateShadowMap();
}

Alright! The last thing we are doing is changing the Update method. We first check if we should update the textures, then we update the interpolationValue in our material:

private void Update()
{
	if (Time.frameCount % _interpolationFrames == 0)
	{
		_lastShadowMap.SetPixels32(_pixels);
		_lastShadowMap.Apply();

		for (var i = 0; i < _pixels.Length; ++i)
		{
			_pixels[i].r = 0;
		}

		UpdateShadowMap();

		_interpolateStartFrame = Time.frameCount;

		_shadowMap.SetPixels32(_pixels);
		_shadowMap.Apply();
	}

	_fogMaterial.SetFloat("_interpolationValue", (Time.frameCount - _interpolateStartFrame) / (float)_interpolationFrames);
}

Conclusion:

You should now be able to run it at this point and see the changes. You can play with the _interpolationFrames to get a slower or faster transition.

This was a fun little distraction but lately I've been thinking about how to solve the line of sight issue. For those not familiar, how I'm clearing the texture is in pixel space so I can't tell if something is blocking my sight to something else. I'm thinking about making a binary space partitioning to solve this but I'm still not sure...

Here is the source code up to this post.