“Cloudy” noise on a hex grid

This article is about noise that looks somewhat like this:

PerlinNoiseFractal

One way to generate noise like that is to add various layers of Perlin noise or smooth noise together, where each layer is more blurry than the next. You can get more details on the case for square grids in this article: How to use Perlin noise in your games.

That article also lists many uses of that type of noise in games. Among other things, it’s useful for generating things such as clouds, maps, and terrain. It’s also useful to simulate certain textures such as marble or wood. You can also use it to place items (such as certain types of building in cities). We used it in Game 16 to drive light colors, giving the maze some visual variety, but still keeping some coherence.

In this article, I will show you a quick-and-dirty way to implement that type of noise on a hex grid. I take a very informal approach which is suitable for the use in games.

[notice]Noise generation can become very technical, and in applications where subtle differences matter, a more mathematically rigorous approach is appropriate.[/notice]

It’s easier to understand the algorithm when it is broken up like it is in this article. Unfortunately, this makes the algorithm quite slow. It’s possible to improve the performance drastically if the steps are re-arranged slightly, if some steps are done in-place, and if other tricks are used to avoid multiple iterations.

[info]Although this tutorial uses some functions that are provided by our Grids library, you should be able to convert the concepts to any hex library.[/info]

The basic idea is simple:

  1. Generate white noise at various resolutions (but in the same sized grid).
  2. Blur the layers (more blurring is required for lower resolutions).
  3. Add them together.

Generating white noise at the highest resolution is easy: simply assign a random value to each cell in the grid.

screen_444

For lower resolutions, it becomes a bit trickier. In the case of square grids, we can sample at a half the resolution by this scheme:

sampledNoise[x, y] = noise[x / 2, y / 2] //Note: I mean floor division, and this is not Grids notation!

We want something similar for hex grids. Although the scheme above will also work for hex grids, it give us parallelogram-shaped patches. What we would really like is hexagonal patches. This is possible, but a bit tricky to understand.

The basic idea is to partition the grid “evenly” into 7 groups, as shown below. (We use the GetColor3_7 method, which is an abbreviation for GetColor(7, 2, 1). You can find out more about grid colorings here: https://gamelogic.co.za/2013/12/18/what-are-grid-colorings/).

screen_458

This assigns to each point a number 0 to 6 that we will call the remainder index of the point.

We then note that every point can be written in the form:

$latex p = au + bv + r$

for some $latex a$ and $latex b$ , where $latex u = [7, 0] $ and $latex v = [2, 1] $ are the sides of the unit parallelogram (one of the smallest parallelograms that repeats in the pattern) , and $latex r$ is one of the points in a hex-shaped patched of 7 hexes at the origin:

[0, -1]
[1, -1]
[-1, 0]
[0, 0]
[1, 0]
[-1, 1]
[0, 1]

All we need to do is solve for a and b, and then use the point [a, b] as the point to sample. You will see this roughly resembles integer division (from there the terminology used in the code). I do not show how the solution is derived here – it’s a lot of math!

public PointyHexPoint Div2(PointyHexPoint point)
{
   int remainderIndex = point.GetColor3_7();
   var evenPoint = point - Remainders[remainderIndex];
   var a = evenPoint.Y;
   var b = Mathi.Div(evenPoint.X - 2*a, 7);

   a = a + 3*b;
   b = -b;

   return new PointyHexPoint(a, b);
}

Here, the Remainders is a list of the 7 points above, sorted by their remainder indices (thus not in the order given above).

When we fill points in one grid, sampled at these “divided” points in a noise grid, the effect is as this below:

screen_445

By re-applying the function to the point before we sample, we get even lower resolutions of noise.

screen_446

screen_449

The next step is to blur these. We will use a hexagonal-box blur for this step. The area of our patches grow by a factor of 7 in each iteration:

float area = Mathf.Pow(7, n - 1);

We then use the formula for centered hexagonal numbers to calculate the right size of the hexagon.

int side = Mathf.FloorToInt((3 + Mathf.Sqrt(9 – 4*3*(1 - area)))/(2 * 3));

We then construct a hex grid of this size, and then blur the original grid as by for each point, averaging all points in the original grid offset from that point by each point in the second grid:

public IGrid Blur(IGrid grid, int n)
{
   float patchArea = Mathf.Pow(7, n - 1);
   int side = Mathf.FloorToInt((3 + Mathf.Sqrt(9 - 4*3*(1-patchArea)))/(2*3));
   var blurredGrid = grid.CloneStructure();
   var blurBox = PointyHexGrid.Hexagon(side); //The cell type is irrelevant

   foreach (var point in grid)
   {
      float sum = 0;
      int sampleCount = 0;

      foreach (var offset in blurBox)
      {
         var samplePoint = point + offset;
         
         if (Grid.Contains(samplePoint))
         {
            sum += grid[samplePoint];
            sampleCount++;
         }

         blurredGrid[point] = sum/sampleCount;
      }
   }

   return blurredGrid;
}

screen_450

screen_451

screen_452

The final step is to scale these together and add them up. We scale the highest resolution by 1, the second by 2, the third by 4, and the fourth by 8. We add then together, and divide by 15 to normalize values between 0 and 1.

screen_453

Once we have the noise, we can use it to drive the various things described earlier. In the image below, we generated three noise sets. Two is used to blend between two colors each; the third is used to blend the images together.

 

screen_454

You can also use the noise to drive tile selection, as shown in the images below:

screen_470screen_465

screen_475

landscapes

You can download a Unity package with the source code here:

CloudyNoise.unitypackage

To check it out,

  1. Import Grids 1.8 into a new project.
  2. Import the package into the project.
  3. Open the CloudyNoise scene.

Note: it is quite slow!

1 thought on ““Cloudy” noise on a hex grid”

Comments are closed.

Scroll to Top