thebookofshaders/12/README.md

191 lines
13 KiB
Markdown
Raw Normal View History

2016-05-03 18:31:56 +00:00
![](dragonfly.jpg)
2016-05-05 21:09:37 +00:00
## Cellular Noise
2016-05-02 21:19:51 +00:00
2016-09-23 19:59:49 +00:00
In 1996, sixteen years after Perlin's original Noise and five years before his Simplex Noise, [Steven Worley wrote a paper called “A Cellular Texture Basis Function”](http://www.rhythmiccanvas.com/research/papers/worley.pdf). In it, he describes a procedural texturing technique now extensively used by the graphics community.
2015-03-15 15:35:14 +00:00
2016-09-23 19:59:49 +00:00
To understand the principles behind it we need to start thinking in terms of **iterations**. Probably you know what that means: yes, start using ```for``` loops. There is only one catch with ```for``` loops in GLSL: the number we are checking against must be a constant (```const```). So, no dynamic loops - the number of iterations to do must be fixed.
2016-05-03 01:54:13 +00:00
2016-09-23 19:59:49 +00:00
Let's take a look at an example.
2016-05-03 01:54:13 +00:00
2016-08-25 16:49:09 +00:00
### Points for a distance field
2016-09-23 19:59:49 +00:00
Cellular Noise is based on distance fields, the distance to the closest one of a set of feature points. Let's say we want to make a distance field of 4 points. What do we need to do? Well, **for each pixel we want to calculate the distance to the closest point**. That means that we need to iterate through all the points, compute their distances to the current pixel and store the value for the one that is closest.
2016-08-25 16:49:09 +00:00
```glsl
2016-09-23 19:59:49 +00:00
float min_dist = 100.; // A variable to store the closest distance to a point
2016-08-25 16:49:09 +00:00
min_dist = min(min_dist, distance(st, point_a));
min_dist = min(min_dist, distance(st, point_b));
min_dist = min(min_dist, distance(st, point_c));
min_dist = min(min_dist, distance(st, point_d));
```
2016-05-03 18:31:56 +00:00
![](cell-00.png)
2016-05-02 21:19:51 +00:00
2016-09-23 19:59:49 +00:00
This is not very elegant, but it does the trick. Now let's re-implement it using an array and a ```for``` loop.
2016-08-25 16:49:09 +00:00
2016-05-02 21:19:51 +00:00
```glsl
2016-09-23 19:59:49 +00:00
float m_dist = 100.; // minimum distance
2016-05-02 21:19:51 +00:00
for (int i = 0; i < TOTAL_POINTS; i++) {
float dist = distance(st, points[i]);
m_dist = min(m_dist, dist);
}
```
2016-09-23 19:59:49 +00:00
Note how we use a ```for``` loop to iterate through an array of points and keep track of the minimum distance using a [```min()```](../glossary/?search=min) function. Here's a brief working implementation of this idea:
2016-05-03 01:54:13 +00:00
2016-05-02 21:19:51 +00:00
<div class="codeAndCanvas" data="cellnoise-00.frag"></div>
2016-09-23 19:59:49 +00:00
In the above code, one of the points is assigned to the mouse position. Play with it so you can get an intuitive idea of how this code behaves. Then try this:
2016-05-02 21:19:51 +00:00
2016-05-03 01:54:13 +00:00
- How can you animate the rest of the points?
2016-09-23 19:59:49 +00:00
- After reading [the chapter about shapes](../07/), imagine interesting ways to use this distance field!
2016-05-04 09:56:13 +00:00
- What if you want to add more points to this distance field? What if we want to dynamically add/subtract points?
2016-05-02 21:19:51 +00:00
2016-09-23 19:59:49 +00:00
### Tiling and iteration
2016-05-02 21:19:51 +00:00
2016-11-30 20:53:46 +00:00
You probably notice that ```for``` loops and *arrays* are not very good friends with GLSL. Like we said before, loops don't accept dynamic limits on their exit condition. Also, iterating through a lot of instances reduces the performance of your shader significantly. That means we can't use this direct approach for large amounts on points. We need to find another strategy, one that takes advantage of the parallel processing architecture of the GPU.
2016-05-03 18:31:56 +00:00
![](cell-01.png)
2016-05-03 11:47:51 +00:00
2016-09-23 19:59:49 +00:00
One way to approach this problem is to divide the space into tiles. Not every pixel needs to check the distance to every single point, right? Given the fact that each pixel runs in its own thread, we can subdivide the space into cells, each one with one unique point to watch. Also, to avoid aberrations at the edges between cells we need to check for the distances to the points on the neighboring cells. That's the main brillant idea of [Steven Worley's paper](http://www.rhythmiccanvas.com/research/papers/worley.pdf). At the end, each pixel needs to check only nine positions: their own cell's point and the points in the 8 cells around it. We already subdivide the space into cells in the chapters about: [patterns](../09/), [random](../10/) and [noise](../11/), so hopefully you are familiar with this technique by now.
2016-05-03 01:54:13 +00:00
```glsl
// Scale
st *= 3.;
// Tile the space
vec2 i_st = floor(st);
vec2 f_st = fract(st);
```
2016-09-23 19:59:49 +00:00
So, what's the plan? We will use the tile coordinates (stored in the integer coordinate, ```i_st```) to construct a random position of a point. The ```random2f``` function we will use receives a ```vec2``` and gives us a ```vec2``` with a random position. So, for each tile we will have one feature point in a random position within the tile.
2016-05-03 01:54:13 +00:00
2016-05-03 13:41:52 +00:00
```glsl
vec2 point = random2(i_st);
```
2016-05-02 21:19:51 +00:00
2016-05-03 13:41:52 +00:00
Each pixel inside that tile (stored in the float coordinate, ```f_st```) will check their distance to that random point.
2016-05-02 21:19:51 +00:00
```glsl
2016-05-03 13:41:52 +00:00
vec2 diff = point - f_st;
float dist = length(diff);
2016-05-02 21:19:51 +00:00
```
2016-09-23 19:59:49 +00:00
The result will look like this:
2016-05-03 13:41:52 +00:00
2016-05-13 13:53:13 +00:00
<a href="../edit.php#12/cellnoise-01.frag"><img src="cellnoise.png" width="520px" height="200px"></img></a>
2016-05-03 13:41:52 +00:00
2016-09-23 19:59:49 +00:00
We still need to check the distances to the points in the surrounding tiles, not just the one in the current tile. For that we need to **iterate** through the neighbor tiles. Not all tiles, just the ones immediately around the current one. That means from ```-1``` (left) to ```1``` (right) tile in ```x``` axis and ```-1``` (bottom) to ```1``` (top) in ```y``` axis. A 3x3 region of 9 tiles can be iterated through using a double ```for``` loop like this one:
2016-05-03 13:41:52 +00:00
```glsl
for (int y= -1; y <= 1; y++) {
for (int x= -1; x <= 1; x++) {
// Neighbor place in the grid
vec2 neighbor = vec2(float(x),float(y));
...
}
}
```
2016-05-03 18:31:56 +00:00
![](cell-02.png)
2016-09-23 19:59:49 +00:00
Now, we can compute the position of the points on each one of the neighbors in our double ```for``` loop by adding the neighbor tile offset to the current tile coordinate.
2016-05-03 13:41:52 +00:00
2016-05-03 18:31:56 +00:00
```glsl
2016-05-03 13:41:52 +00:00
...
// Random position from current + neighbor place in the grid
vec2 point = random2(i_st + neighbor);
...
```
2016-05-02 21:19:51 +00:00
2016-09-23 19:59:49 +00:00
The rest is all about calculating the distance to that point and store the closest one in a variable call ```m_dist``` (for minimum distance).
2016-05-03 13:41:52 +00:00
```glsl
...
vec2 diff = neighbor + point - f_st;
// Distance to the point
float dist = length(diff);
// Keep the closer distance
m_dist = min(m_dist, dist);
...
```
2016-09-23 19:59:49 +00:00
The above code is inspired by [this article by Inigo's Quilez](http://www.iquilezles.org/www/articles/smoothvoronoi/smoothvoronoi.htm) where he said:
2016-05-03 13:41:52 +00:00
2016-05-04 09:56:13 +00:00
*"... it might be worth noting that there's a nice trick in this code above. Most implementations out there suffer from precision issues, because they generate their random points in "domain" space (like "world" or "object" space), which can be arbitrarily far from the origin. One can solve the issue moving all the code to higher precision data types, or by being a bit clever. My implementation does not generate the points in "domain" space, but in "cell" space: once the integer and fractional parts of the shading point are extracted and therefore the cell in which we are working identified, all we care about is what happens around this cell, meaning we can drop all the integer part of our coordinates away all together, saving many precision bits. In fact, in a regular voronoi implementation the integer parts of the point coordinates simply cancel out when the random per cell feature points are subtracted from the shading point. In the implementation above, we don't even let that cancelation happen, cause we are moving all the computations to "cell" space. This trick also allows one to handle the case where you want to voronoi-shade a whole planet - one could simply replace the input to be double precision, perform the floor() and fract() computations, and go floating point with the rest of the computations without paying the cost of changing the whole implementation to double precision. Of course, same trick applies to Perlin Noise patterns (but i've never seen it implemented nor documented anywhere)."*
2016-05-03 13:41:52 +00:00
2016-09-23 19:59:49 +00:00
Recapping: we subdivide the space in tiles; each pixel will calculate the distance to the point in their own tile and the surrounding 8 tiles; store the closest distance. The result is a distance field that looks like the following example:
2016-05-03 13:41:52 +00:00
<div class="codeAndCanvas" data="cellnoise-02.frag"></div>
2016-09-23 19:59:49 +00:00
Explore this further by:
2016-05-03 13:41:52 +00:00
- Scaling the space by different values.
2016-09-23 19:59:49 +00:00
- Can you think of other ways to animate the points?
2016-05-03 13:41:52 +00:00
- What if we want to compute an extra point with the mouse position?
2016-09-23 19:59:49 +00:00
- What other ways of constructing this distance field can you imagine, besides ```m_dist = min(m_dist, dist);```?
- What interesting patterns can you make with this distance field?
2016-05-02 21:19:51 +00:00
2016-09-23 19:59:49 +00:00
This algorithm can also be interpreted from the perspective of the points and not the pixels. In that case it can be described as: each point grows until it finds the growing area from another point. This mirrors some of the growth rules in nature. Living forms are shaped by this tension between an inner force to expand and grow, and limitations by outside forces. The classic algorithm that simulates this behavior is named after [Georgy Voronoi](https://en.wikipedia.org/wiki/Georgy_Voronoy).
2016-05-02 21:19:51 +00:00
2016-05-03 18:31:56 +00:00
![](monokot_root.jpg)
2016-05-02 21:19:51 +00:00
### Voronoi Algorithm
2016-09-23 19:59:49 +00:00
Constructing Voronoi diagrams from cellular noise is less hard that what it might seem. We just need to *keep* some extra information about the precise point which is closest to the pixel. For that we are going to use a ```vec2``` call ```m_point```. By storing the vector direction to the center of the closest point, instead of just the distance, we will be "keeping" a "unique" identifier of that point.
2016-05-03 21:33:11 +00:00
```glsl
...
if( dist < m_dist ) {
m_dist = dist;
m_point = point;
}
...
```
2016-09-23 19:59:49 +00:00
Note that in the following code that we are not longer using ```min``` to calculate the closest distance, but a regular ```if``` statement. Why? Because we actually want to do something more every time a new closer point appears, namely store its position (lines 32 to 37).
2016-05-03 21:33:11 +00:00
2016-05-02 21:19:51 +00:00
<div class="codeAndCanvas" data="vorono-00.frag"></div>
2016-09-23 19:59:49 +00:00
Note how the color of the moving cell (bound to the mouse position) changes color according to its position. That's because the color is assigned using the value (position) of the closest point.
2016-05-02 11:21:16 +00:00
2016-11-30 20:59:29 +00:00
Like we did before, now is the time to scale this up, switching to [Steven Worley's paper approach](http://www.rhythmiccanvas.com/research/papers/worley.pdf). Try implementing it yourself. You can use the help of the following example by clicking on it. Note that Steven Worley's original approach uses a variable number of feature points for each tile, more than one in most tiles. In his software implementation in C, this is used to speed up the loop by making early exits. GLSL loops don't allow variable number of iterations, so you probably want to stick to one feature point per tile.
2016-05-02 11:21:16 +00:00
2016-05-13 13:53:13 +00:00
<a href="../edit.php#12/vorono-01.frag"><canvas id="custom" class="canvas" data-fragment-url="vorono-01.frag" width="520px" height="200px"></canvas></a>
2016-05-03 21:33:11 +00:00
2016-09-23 19:59:49 +00:00
Once you figure out this algorithm, think of interesting and creative uses for it.
2016-05-05 14:08:15 +00:00
![Extended Voronoi - Leo Solaas (2011)](solas.png)
![Cloud Cities - Tomás Saraceno (2011)](saraceno.jpg)
2016-05-02 11:21:16 +00:00
2016-05-03 18:31:56 +00:00
![Accretion Disc Series - Clint Fulkerson](accretion.jpg)
2016-05-05 14:08:15 +00:00
![Vonoroi Puzzle - Reza Ali (2015)](reza.png)
2016-05-03 21:33:11 +00:00
### Improving Voronoi
2016-09-23 19:59:49 +00:00
In 2011, [Stefan Gustavson optimized Steven Worley's algorithm to GPU](http://webstaff.itn.liu.se/~stegu/GLSL-cellular/GLSL-cellular-notes.pdf) by only iterating trough a 2x2 matrix instead of 3x3. This reduces the amount of work significantly, but it can create artifacts in the form of discontinuities at the edges between the tiles. Take a look to the following examples.
2016-05-03 21:33:11 +00:00
<div class="glslGallery" data="12/2d-cnoise-2x2,12/2d-cnoise-2x2x2,12/2d-cnoise,12/3d-cnoise" data-properties="clickRun:editor,openFrameIcon:false"></div>
2016-05-04 10:10:03 +00:00
2016-09-23 19:59:49 +00:00
Later in 2012 [Inigo Quilez wrote an article on how to make precise Voronoi borders](http://www.iquilezles.org/www/articles/voronoilines/voronoilines.htm).
2016-05-03 21:33:11 +00:00
2016-05-13 13:53:13 +00:00
<a href="../edit.php#12/2d-voronoi.frag"><img src="2d-voronoi.gif" width="520px" height="200px"></img></a>
2016-05-03 21:33:11 +00:00
2017-02-03 12:05:04 +00:00
Inigo's experiments with Voronoi didn't stop there. In 2014 he wrote this nice article about what he calls [voro-noise](http://www.iquilezles.org/www/articles/voronoise/voronoise.htm), an function that allows a gradual blend between regular noise and voronoi. In his words:
2016-05-03 21:33:11 +00:00
2016-05-04 09:56:13 +00:00
*"Despite this similarity, the fact is that the way the grid is used in both patterns is different. Noise interpolates/averages random values (as in value noise) or gradients (as in gradient noise), while Voronoi computes the distance to the closest feature point. Now, smooth-bilinear interpolation and minimum evaluation are two very different operations, or... are they? Can they perhaps be combined in a more general metric? If that was so, then both Noise and Voronoi patterns could be seen as particular cases of a more general grid-based pattern generator?"*
2016-05-03 21:33:11 +00:00
2016-05-13 13:53:13 +00:00
<a href="../edit.php#12/2d-voronoise.frag"><canvas id="custom" class="canvas" data-fragment-url="2d-voronoise.frag" width="520px" height="200px"></canvas></a>
2016-05-03 18:31:56 +00:00
2016-09-23 19:59:49 +00:00
Now it's time for you to look closely at things, be inspired by nature and find your own take on this technique!
2016-05-03 21:33:11 +00:00
2016-05-05 14:36:31 +00:00
![Deyrolle glass film - 1831](DeyrolleFilm.png)
2016-05-03 18:31:56 +00:00
<div class="glslGallery" data="12/metaballs,12/stippling,12/cell,12/tissue,12/cracks,160504143842" data-properties="clickRun:editor,openFrameIcon:false"></div>