Update!
I managed to add fog in. It was easier than I expected, which is nice.
Have a look:
How it worksCovered/uncovered parts are stored as a texture, which is
map_widthx8 pixels big. Parts that haven't yet been discovered are marked as fully opaque black pixel, while parts uncovered are a fully transparent black pixel. Here's how a fog texture might look like for a 32-tiles wide map:
As you can see, the 8 pixels in height is just for adding the roundeiness at the top of the fog.
Once we have this texture, we apply it to a sprite that covers our entire map, stretching it with bilinear filtering. Add in some C# code for modifying the fog texture (discovering map parts), and you've got yourself a fog.
But we also need to add a
second layer of fog, which is the fog that covers what we've already seen. This
second layer of fog works by the exact same principles as the basic fog, but uses a different shader to turn into grayscale whatever's behind it. In unity, grabpass comes in handy:
Shader "Custom/FogOfWar2"
{
Properties
{
[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
_Color("Tint", Color) = (1,1,1,1)
[MaterialToggle] PixelSnap("Pixel snap", Float) = 0
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
GrabPass { }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ PIXELSNAP_ON
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 screenPos : TEXCOORD1;
};
fixed4 _Color;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.screenPos = ComputeScreenPos(OUT.vertex);
OUT.color = IN.color * _Color;
#ifdef PIXELSNAP_ON
OUT.vertex = UnityPixelSnap(OUT.vertex);
#endif
return OUT;
}
sampler2D _MainTex;
sampler2D _AlphaTex;
float _AlphaSplitEnabled;
sampler2D _GrabTexture;
fixed4 SampleSpriteTexture(float2 uv)
{
fixed4 color = tex2D(_MainTex, uv);
#if UNITY_TEXTURE_ALPHASPLIT_ALLOWED
if (_AlphaSplitEnabled)
color.a = tex2D(_AlphaTex, uv).r;
#endif //UNITY_TEXTURE_ALPHASPLIT_ALLOWED
return color;
}
fixed4 frag(v2f IN) : SV_Target
{
fixed4 c = tex2D(_GrabTexture, IN.screenPos) * IN.color * fixed4(0.3, 0.3, 0.3, 1.0);
c.r = (c.r + c.g + c.b) / 3;
c.g = c.r;
c.b = c.r;
c.a = SampleSpriteTexture(IN.texcoord).a;
c.rgb *= c.a;
return c;
}
ENDCG
}
}
}
Sorry of the terrible formattingAnd then there's one
last problem -- we need to
hide enemy units that are behind this
second layer of fog. This we can solve with another shader, applied to all units (our units will always stay in uncovered parts, so we don't need to distinguish them from the others).
We can write a shader that uses the
second layer of fog's texture, computes unit's position on that texture, looks up the visibility, and behaves accordingly.
To compute the UV on the fog texture, we can do the following:
o.visibilityUV = (mul(_Object2World, v.vertex).xy - _VisibilitySpriteBounds.xy) / _VisibilitySpriteBounds.zw;
Where _VisibilitySpriteBounds contain: (
fog X world position,
fog Y world position,
fog X size,
fog Y size).
Then we look up the visibility, and decide what to do:
fixed lookupAlpha = 1.0 - tex2D(_VisibilityTex, IN.visibilityUV).a;
if (lookupAlpha <= 0.002)
discard;
else {
fixed d = (c.r + c.g + c.b) / 3.0;
c.rgb = lerp(fixed3(d, d, d), c.rgb, lookupAlpha);
}
And that's it, basically.
Next up I'll probably be preparing to work on adding cutting trees down, which means reworking some parts of the code (currently that would require every tree to have the
Selectable component, which is a performance killer, because
Selectable contains Update function, and there are a lot of trees on one map).
@EDITRight, I forgot there's still no minimap. I'll be working on that next, then the trees.