© Oliver Keppelmüller 2017-2018
Developer’s blog
How not to be seen
25th February 2018 by Oliver Keppelmüller
WHAT ARE YOU HIDING?
The difficulty of creating a fog of war (FOW) engine from a technical perspective is mainly
performance. To understand the problematic I will try to explain the different steps of the
engine.
First we need to determine the line of sight (LOS) which is quite performance intensive:
On the one hand the calculation needs to be done for every unit, on the other hand the
calculation itself is very complicated. Here, we need to do a so called "raycast" into every
direction (this means 360°) of the unit. Raycast means: we send out a ray towards a
specific direction and check if it "hits" something, in our case mostly terrain or buildings. If
a hit occurs, the line of sight stops there and we have the distance of the line of sight for
this specific direction. If we assume a total range of 360°, this means we would have to
calculate 360 raycasts which would be a lot when doing this for 200 units, and this in real-
time!
To reduce the amount of calculations, we can reduce the field resolution of our “FOW
matrix” and thus the steps in turning around. For example: if you have only 33 fields in the
scope of your unit, then you can only see, or not see, 33 angles instead of 360. In general,
the FOW does not need to have that high resolution than the basic battlefield as it will
nevertheless get blurred afterwards.
The next problem we stumble upon is the terrain slope, especially as we don’t use standard
square- or hex-based, simplified terrain. If you imagine we always look straight ahead, we
may stop our line of sight at smaller obstacles (eg. hills) on the way, although there may be
higher ground in a greater distance which could be seen. To overcome this we need again
a lot more calculations. We now do not only "look" at the same elevation of the unit but on
the target elevation of every field in front of the unit, meaning we check every field in front
of the unit with the target terrain height as seen in these two images:
With this modification we may have situations where closer terrain is invisible, while
terrain farther away is visible again because it is on higher ground. We also use this logic
for objects in way, like buildings, forest and even corn fields. Although we get quite an
accurate FOW now, we lose performance (again). If we use the previous example, we
would now have 200 units with 33 raycast angles each and maybe 40 fields in front,
bringing it to a total of 264.000 calculations (way too much!). To reduce this, we can take
into account, that raycasting shorter ranges does not need that many angles like ranges
with greater distances, as the radius projected on the FOW field matrix is much less. The
following image describes this:
The blue distance (far) shows we need 6 angles
to "fill up" (check) every FOW field, the green
distance (near) shows we need only 2 angles
because the radius is much smaller.
So we can reduce the calculations to approx. the
half of the 264k calculations with this step. As
this is still too much for a real-time calculation,
we need to spread all these calculations over
multiple cycles, eg. you do not calculate 200
units per frame but only 5. The pro is a huge
performance pickup, the con a lag in FOW
update. This is also mainly the reason why the
FOW in the most complex games is changing
slowly.
As we now have a tremendous performance win, we can spend a bit of it for visuals. All the
calculations we have made result in a simple "visible" or "not visible" marking for each
field of the FOW matrix. We now paint all the not visible fields as black pixels on a texture
(with maybe some alpha). This texture is blended over the terrain which results in darker
areas (not visible) and lighter ones (visible). But if we take a look on it, it shows up quite
blocky as we only use total black or invisible pixels. To get rid of this, we need to blur the
texture, or at least the values that lead to the texture (which should be faster). Using a full
Gaussian blur costs tremendous performance (again). After a few tries I managed to
implement simplified Gaussian blur with performance improved from 50ms per calculation
to approx. 1ms by only having blur errors of <1% of the pixels.
Image: Taking the theory to the battlefield, here’s the same in practice. Terrain visible for
the selected army is shown lighter, while unseen terrain is colored darker. In this debug-
image, the red areas mark terrain that is visible, but could conceal enemy troops (within
the sparse woods). NOTE: the image is pre-alpha, and all visuals are subject to change.
If you have survived my technical torture this far, a few lighter topics for the end: We have
also implemented the reduction of the line of sight due to smoke from weapons and also
taking into account different weather situations. But there is another goodie: concealment.
Even if enemy units are within visible FOW ranges, they may still be concealed because of
terrain (eg. placed in a corn field), cover (eg. hidden behind a stone wall or in a trench),
laying down, or simply the small size, or skill in use of terrain, of the unit (eg. scouts,
skirmishers).
Oliver
Fog of war. Not the lack of clarity described by von Clausewitz, but the visual
representation of what your units can see, as it’s commonly called in games. In Grand
Tactician’s huge real-time battles visibility in combat requires quite complex calculations,
as we take into account line of sight, weather, terrain concealment, and even smoke from
weapons. Oliver surfaced from the matrix momentarily to explain more, from the technical
point of view.