Hi folks,
Last week we went over 3 common noise algorithms. Today we will continue where we left off and show 3 more! Worley, Voronoi, and fractal noise.
Tutorial Difficulty: Intermediate
Worley Noise
Worley noise functions a bit differently from the other noise algorithms because there is no interpolation involved. The concept is simple, scatter a bunch of points randomly and use the distance to the closest point to determine the output brightness. 0.0 at the point center and 1.0 for cell unit a way.
Let's break this down into simple steps:
Step 1: Round To Cells
Like before, round down the input coordinates to cells:
vec2
cell =
floor
(p);
And we'll also store our distance value in a float:
float dist = 9.0;
I'm using 9 because it's our maximum distance, that we should never exceed (even in higher dimensions).
Step 2: Sample Point
Compute the difference from p
to a random point in the cell:
vec2
worley_dif = hash2(cell) + cell - p;
hash2 gives us a random x and y value between 0 and 1. Then add cell so the point position ranges from cell
to cell+1.0
and subtract p
to find the relative difference.
We can then calculate the distance by using the length of this difference:
dist =
length
(worley_dif);
Doing so gives us the result on the left:
So we now have our random points and we just want it to smoothly blend with the neighboring cells as shown above on the right. You might be able to guess the last step.
Step 3: Sample Neighbors
Loop through the 3x3 cell area instead
for
(
int
x = -1; x<=1; x++)
for
(
int
y = -1; y<=1; y++)
And we'll add this offset before sampling each cell:
vec2
sample_cell = cell +
vec2
(x,y);
So we'll use our sample_cell
when computing worley_dif
instead:
vec2
worley_dif = hash2(sample_cell) + sample_cell - p;
And we'll just use minimum distance across all neighbor cells:
dist =
min
(dist,
length
(worley_dif));
This gives us the desired cellular pattern we were looking for!
Again, I'll link a ShaderToy example at the end.
Voronoi Noise
Voronoi noise is a simple variation of Worley noise. Instead of using the distance to nearest point, we'll pick a random value for every point and use the closest points value for the output.
Step 1: Voronoi Cells
So use your Worley function as a base, but before the loops, add another vec2:
vec2
voronoi_cell = cell;
This will be used to store the cell coordinates of the nearest point.
Step 2: Find Nearest Cell
Inside the loop, we need more than just the minimum distance to neighbor cells. We'll put our Worley distance value into a new float:
float
new_dist
= length
(worley_dif);
And when our new distance value is smaller than our old one:
if
(dist > new_dist)
Replace our old distance with the new, smaller value:
dist = new_dist;
This does the same thing as min
, but we'll also store our nearest sample cell:
voronoi_cell = sample_cell;
Step 3: Final Hash
And finally, once the loop is complete, we can use our hash function on the coordinates of the nearest Voronoi cell:
return
hash1(voronoi_cell);
This gives you a neat honeycomb-like pattern:
Fractal Noise
Now let's take a look at fractal noise! Fractal noise is when you layer multiple noise "octaves" at different scales and intensities to produce a more natural look. This can be used for clouds, smoke, fire or even terrain generation!
The neat part is that it can be applied to any of the other noise algorithms:
Let's break it down!
Step 1: Initialize
The first step is to initialize the variables we need:
float
noise_sum = 0.0;
//Noise total across octaves
float
weight_sum = 0.0;
//Weight total across octaves
float
weight = 1.0;
//Octave weight
int
oct = 6;
//Number of octaves
float
per = 0.5;
//Octave persistence (intensity)
Step 2: Octaves
A simple loop through octaves will do:
for(
int
i = 0; i < oct; i++)
For each octave, we want to add a weighted layer of noise.
noise_sum += value_noise(p) * weight;
And add the current weight to a total for averaging.
weight_sum += weight;
Next, we'll multiply our octave weight by the persistence value.
weight *= per;
This means with a persistence value of 0.5, each octave has half the intensity of the octave before it. Play around with different values to see how it looks.
And finally, quite importantly, we rotate and scale each octave:
p *=
mat2
(1.6, 1.2, -1.2, 1.6);
This matrix scales coordinates by 2 and rotates by about 143 degrees. The rotation helps break up any patterns that might arise from noise octaves overlapping each. p *= 2.0
also works, but doesn't look as good.
Step 3: Weighted Average
And finally, just return the weighted average:
return noise_sum / weight_sum;
In case you didn't know, a weighted average is an average where some values have more of an effect than others. In this case, If you have a persistence value of 0.5, the first octave contributes to half of the average, the second octave: one-quarter, the third: one-eighth, and so on.
It's a great trick to learn and use in any shader where you need to blend multiple values together with different weights!
Conclusion
That covers it for tonight. Let me know if there are any other noise algorithms you'd like me to go over.
You can take a look at the code I used in my ShaderToy example here!
And you're bored, check out this 3D game I made in 2 weeks using just GameMaker (no 3D modeling/animation software, just shader code!)
Thanks for reading! Enjoy your night!
Great article ! Little typo to fix, missing "to" in "This will be used store the cell coordinates of the nearest point."