About Me My Projects
A New Technique: Hexagonal Fog Maps
2024

The Goal


GIF of the Fog

The Fog in Game

This technique is something I worked on for the game Busharium, on which I was the sole programmer. Click here for my writeup on making the game, as well as links for playing the game, or viewing the source code.



The Requirements

GIF of Fog Moving

The Fog Moving in Game


We needed a way to demonstrate the border of play in the game. This border expands as the player progresses so we really wanted to have a way to transition smoothly from one radius to the next. The complicated aspect is we wanted it to fit to a hexgrid. After spending a good bit of time researching, I was unable to find any examples of someone doing this, or use of the technique I'll be describing.

Brainstorming and Deadends

So first step, and by far the most complicated part of the process, was to generate a distance field to use in a shader that would render the fog.
If you would rather skip to the method I went with, click here.

Below are the different methods I brainstormed:

Deadend 1

My first thought was essentially to run a compute shader that would perform SAT (Seperate Axis Theorum) to generate an SDF (Signed Distance Field) of the playable space. This would run each time the radius of the fog would change. It became clear very quickly that this wasn't going to work out, due to the amount of branching that would be required. It was going to take a good bit of work to get going and I was very concerned it wouldn't be performant. In theory if it took too long I could generate a map in advance while the player was playing, but since we wanted a nice transition that wouldn't be feasible for memory.


Deadend 2

The second approach was a lot more promising performance-wise, and something I discussed with a mentor (Finn Morgan). Since only the space near the edge of the playable space would ever have a gradient of fog, thats all we'd need to worry about generating distance information for. Additionally, since the idea was for the outer edge of the playable space to be fully in fog, but have it spill onto the outer tiles a bit, it didn't really need to be an SDF. It could just be a UDF (Unsigned Distance Field).

Result of Stamping

The method we discussed essentially involved having a premade UDF of a single hexagon, and doing a top-down orthographic render of the scene, drawing this image on all the tiles a distance of the radius +1 from the centre. These drawn UDFs would be sized such that the outer tiles would haved the distance information drawn onto them using the GL_MIN blend method. After getting my head around the details properly though, it became clear to me that this wouldn't be able to handle the transition to other radii, as there was no way to render intermediate stages. Different radii have different amounts of outer hexagons, and this technique relied on rendering whole hexagons.

Hexagon UDF


Third Time's a Charm

I realised I could reuse my existing hex-coord conversion functions to generate distance information in "Hex-Space". This would allow me to set each pixel in a buffer's colour to be the distance from the hex its contained in, to the centre. Then all I'd need to do is generate the fractional portion, and remap it when rendering it (to only display fog at the desired ranges). One of the biggest benefits of this idea is that it would be a static image that never changes, and therefore doesn't need to be generated at runtime. I'll go into this more in the next sections as its the method I went with.

Hexgrid Distance Field

For more information on hexagonal coordinate systems
as well as some interactive diagrams, click here.

Generating the Fog Map

Image of Tile Distance From Centre

Tile Distance From Centre

The step I knew would be easy to implement was getting the 'tile distance'. This is basically just how many hex tiles away from the centre a given pixel is. All I needed to do was convert the pixel coordinates into axial (hex) coordinates, and get the magnitude of their offset from the centre. You can see above how this looks in an image.

Image of Sub-tile Distance

Sub-tile Distance

The more tricky step was to get distances within the hex tiles. This couldn't just be a standard distance equation because different sections of the hex grid will vary in pixel distance by too much. The metric I ended up going with was using the distance from the border to the next highest magnitude. This meant I would essentially do an oriented sweep within each tile from each edge facing a further tile, until each of the pixels in the hex were filled. This resulted in tiles with gradients that lined up with their neighbours.

High Contrast Image to Show Banding

Banding Caused by Floating Point Inaccuracy

The only thing left to do now was combine the two values together, and render the fog! But there was a problem. Floating point precision is limited, and the two values use different value ranges. The 'tile distance' ranges from integers of 0 to N (where N is the radius of the hex grid), and 'sub-tile distance' ranges from floats of 0.0f to 1.0f. This meant that the further tiles in the image would lose detail in the gradient and cause the banding effect seen above.

The Underlying Texture Used for the Fog

Final Fogmap Texture

The first thing I tried to fix the banding was using doubles for the equations since they were never going to be performed at runtime. This helped a bit, but the image format I was using only supports single precision floats. So then I decided to save the values into two different channels in the texture. The only remaining problem is that the format I'm using utilises a shared exponent for each channel. So to get around that I simply divide the 'tile distance' by 10.0f, meaning I'd need to multiply it by 10.0f in the shader used to render it.

Rendering the Fog

GIF of Basic Fog Moving

Basic Demonstration of Fog Moving

When using the fogmap you have access to a wide range of values, but through clamping and remapping it can be used to render fog in only certain ranges. The way its rendered could vary, from just displaying the value to the screen, to indexing into a noise map, or even as a value to be used in volumetrics.

As a basic proof that it was working I just displayed the remapped and clamped fog value. The ranges could be changed at runtime to animate the fog moving.

GIF of Final Fog Moving

Fog Moving in Game

Busharium (the game this was for), is a heavily stylised game. The goal the artists had was for any frame of the game to look like a watercolour painting, drawing inspiration from Australian watercolour, and Ken Sugimori's original Pokemon concepts. The game didn't really have much use of gradients so I simplified the rendering to make it so any given pixel was either in fog, or out of fog, no partial values. I also set up some noise and animated it over time to bring some more life to it, and displayed an outline like the game's assets have.

Post Mortem

Overall I'm really happy with how this technique came out. Being able to animate the fog moving is an effect that added a lot to the sense of progression in the game. I imagine this could be used in other games that take place on a hex grid, though I do see it being limited in use since it can only be used for a uniform expansion.

I'd be interested to see if I could work out a way to smooth the gradient a bit more at the seams between layers, as when animating it with a range instead of a cutoff the thickness of the gradient shifts. I'm not entirely sure if that would even be possible due to the nature of hexagonal coordinates, but I'd be interested to investigate that in the future.

The technique met all the needs of this project though and for that I am happy.

GIF of the Fog

The Final Version in Game


This was a really interesting research project, and I'm glad for the opportunity to fun free with it and experiment. It was a good opportunity to practice my skills in graphics programming, debugging, and algorithms. And it was very fulfilling and satisfying coming out of it with a cool looking technique.

I've often used and tweaked techniques described by others, but this was one of my first experiences developing one myself, and the interest I recieved from my peers was very encouraging. I'd love to get the chance to develop new techniques like this again in the future.