WGSL Noise Functions - From Random Numbers to Textures
A noise function for a shader is a function which inputs at least a coordinate vector (either 2d or 3d) and possibly more control parameters and outputs a value (for the sake of simplicity between 0 and 1) such that the output value is not a simple function of the coordinate vector but contains a good mixture of randomness and smoothness.
Dependent on the type of noise, that may mean different things in practice. A noise function is usually chosen based on its ability to represent a natural shape, so it should emulate structures in nature.
Basic Random Number
At the core of any noise functions is a (pseudo-)random-number generator, i.e. a function which inputs a coordinate vector and outputs a value between 0 and 1. Unlike the full noise function which is supposed to have smoothness, the raw output of the random number generator may jump wildly even on a short distance and is not supposed to resemble anything.
What works well to give an essentially unpredictable output is to use a truncation on a rapidly oscillating function. The following implementations produce viable raw noise for 2d or 3d coordinates:
The Characteristics of Noise
The basic random number function above shows what is essentially a pixel to pixel uncorrelated noise - knowing the value of the function at one pixel, one can't say what the value of the adjacent pixel will be. That's not how random patterns in nature typically look. For instance, while the shape of a cloud is not predictable, if one bit of it is opaque white, it's very likely that everything nearby will also be. That is to say, in many natural random patterns there is a correlation length over which the characteristics of the pattern does not change, and only beyond this correlation length the pattern appears random and unpredictable.
If one takes a picture of a natural random pattern and runs a Fourier transformation over it, these characteristic length scales would emerge was frequencies (or wavelengths) of the Fourier analysis.
In the following, we'll frequently refer to noise 'at a certain wavelength'. The meaning is roughly that the distribution is predictable for any length scale much below that number, but random for any distance above that. This is a statement quite independent of the particular type of noise. Put a different way, noise a 1 m wavelength will create a pattern that looks completely random and point to point uncorrelated when a 10 km x 10 km chunk area is considered, but very homogeneous over a 1 cm x 1 cm patch.
Perlin Noise
Perlin noise, named after its inventor Ken Perlin, is perhaps the best known example of noise in 3d rendering. It gives a smooth, organic distribution of shapes at a certain wavelength, and by adding noise at different wavelengths, a large variety of effects can be achieved.
Basic idea
The basic algorithm is surprisingly simple - to generate Perlin noise at a wavelength L:
• create a regular grid with distance L between grid points
• assign a random number to each of the grid points
• for any coordinate vector which points to a grid point, return the number
• for any other coordinate vector, use a smooth interpolating function between the grid points
The precise shape of the interpolation function influences the visuals a lot - a linear interpolation will give harder results, a soft function like cosine or smoothstep will give a more organic look.
The following WGSL code is a 3d variant of Perlin noise, using the smoothstep function to interpolate between grid points.
Sparse Dot Noise
The idea underlying sparse dot noise is partially similar to Worley noise, except that it's computationally cheaper because it makes the sparseness assumption. Suppose you have a surface which is covered by comparatively rare, dot-like structures. They're irregularly spaced, but at a mean distance of the noise wavelength. Since they are rare, one can safely assume any two won't overlap.
The algorithm to generate such a situation is as follows:
• create a regular grid
• assign random numbers to the grid points
• use one of these to determine whether there is a point in the cell
• if yes, use the next one to determine its radius
• and the last two to position it in the cell such that it can't overlap with any other cell
• draw the dot itself given position and radius
This contains a lot of bodges, especially placing it such that it can't overlap, and if we'd choose a large probability to have a dot in the cell and a large radius, this would never look irregular. This is where the Worley noise algorithm invests lots of effort. However, distributing the dots sparsely, i.e. probability to have a dot small per cell and radius being small as compared to cell area, this actually works nicely.
A WGSL realization of the above algorithm is given by the following code which inputs the maximal dot radius and the dot density per cell as additional control parameters:
Domain (Voronoi) Noise
We have already mentioned above that it's sometimes useful to have a function which segments a surface into domains which can then be textured separately. If the segmentation is to have an organic look, Perlin noise works fine. However, for man-made structures, often a less organic segmentation pattern is useful. Consider for example patches of managed forest or fields (especially in Europe) - they are usually bounded by straight lines, but they may be rather complex polygons rather than simple squares or rectangles. Voronoi noise is a tool to achieve such a segmentation.
The algorithm to generate such a situation is as follows:
• create a regular grid at the desired wavelength
• create a random value associated with each grid point
• by calling the random number function displaced from the grid point, generate an x and y shift
• move the grid points by their x and y shifts
• for any coordinate inside a cell, find the shortest distance to a grid point considering only the four corners of the cell
• return the value associated with that point
(the last two steps are known as Voronoi partitioning). There is (again) a good degree of sloppiness to this algorithm, in particular a distorted regular grid doesn't give the same amount of randomness than covering a surface with randomly placed points, and limiting the Voronoi partitioning to the four grid points framing the cell also sometimes implies that the wrong domain is assigned. However, in doing so, the algorithm avoids the expensive search over multiple nearest neighbour candidates and performs reasonably fast, while yielding results which look good enough.
A WGSL realization of the above algorithm is given by the following code which inputs the domain distortion from a regular grid in x and y directions as additional control parameters:
Topology-Driven Noise - Vertical Strata
Topology-Driven Noise - Slope Lines
Non-Linear Filters
Non-linear filtering as such isn't a technique specific to one noise type, though it has perhaps most applications with Perlin noise. As indicated above, the idea is to map the output of the noise function (which is anywhere between 0 and 1) into the same domain, but non-linearly. For instance, one could map all values below 0.9 to one number and the rest to another number. Such filtering can bring out a surprising number of unexpected shapes out of noise functions.
Visitor:
Copyright (c) 2002-2024 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.