The orx-noise
OPENRNDR extension received new capabilities in this and this commits. This post describes the new functionality and was posted by Edwin on Slack in July 2021.
It starts with functions
We know that simplex3D
is a
(Int, Double, Double, Double) -> Double
Here (Int, Double, Double, Double) -> Double
is our commonly used function signature for noise functions that given an Int
seed and a 3d coordinate returns a scalar noise value. perlin3D
, value3D
etc. use that same signature.
orx-noise
already had a fbm
function that is much like
fun fbm(lacunarity:Double, gain:Double,
seed:Int, x:Double, y:Double, z:Double,
noise: (Int, Double, Double, Double) -> Double): Double
So that too is a scalar noise function, however with a different signature.
orx-noise
also has an fbmFunc3D
function that instead of returning a scalar returns a function with our noise function signature.
inline fun fbmFunc3D(
crossinline noise: (Int, Double, Double, Double) -> Double,
octaves: Int = 8,
lacunarity: Double = 0.5,
gain: Double = 0.5
): (Int, Double, Double, Double) -> Double
You’d use fbmFunc3D
to compose a new function like this:
val fbmSimplex = fbmFunc3D(simplex3D)
Which can be used like any other function.
val noise = fbmSimplex(432, 0.0, 1.0, 2.0)
Since our fbmSimplex has the (Int, Double, Double, Double) -> Double
signature we can apply fbmFunc3D
on it again if we want to:
val fbmFbmSimplex = fbmFunc3D(fbmSimplex, lacunarity = 1.3222)
Or alternatively, to demonstrate how quickly this becomes hard to read:
val fbmFbmSimplex = fbmFunc3D(fbmFunc3D(simplex3D), lacunarity = 1.3222)
To improve readability I introduce the use of extension functions, I assume the reader knows what those are. The fbm
function is an extension function on functions with the (Int, Double, Double, Double) -> Double)
signature:
fun ((Int, Double, Double, Double) -> Double).fbm :
(Int, Double, Double, Double) -> Double
It does the same thing as fbmFunc3D, which is to return a function.So now we can compose our previous noise functions without clutter:
val fbmSimplex = simplex3D.fbm()
val fbmFbmSimplex = simplex3D.fbm().fbm(lacunarity = 1.3222)
Now let’s look into some of the additional tooling I made.
.crossFade()
crossFade
is used for seamless noise looping animations and assumes the z axis of 3D noise functions is used for time. (Perhaps it makes sense to have generalized version of this in which the axis/axes can be specified). The crossFade
function uses the receiver noise function (this
) to calculate a blend. The start
, end
and width
arguments are used to define the loop of the z
value.
fun ((Int, Double, Double, Double) -> Double).crossFade(
start: Double, end: Double, width: Double = 0.5):
(Int, Double, Double, Double) -> Double {
return { seed, x, y, z ->
val a = z.map(start, end, 0.0, 1.0).mod_(1.0)
val f = (a / width).coerceAtMost(1.0)
val o = this(seed, x, y, a.map(0.0, 1.0, start, end)) * f
val s = this(seed, x, y, (a + 1.0).map(0.0, 1.0, start, end) * (1.0 - f))
o + s
}
}
.withVector2Output()
The withVector2Output
function changes the function signature to return Vector2
instead of Double
. So the noise function outputs 2d noise, classically you’d pass in different seeds and juggle a bit with your input coordinates to get uncorrelated but similarly distributed noise for x and y. withVector2Output
does that for you by xor-ing the seed and rotating x,y by 90 degrees. Also here we assume the z axis is used for time.
fun ((Int, Double, Double, Double) -> Double).withVector2Output():
(seed: Int, x: Double, y: Double, z: Double) -> Vector2 =
{ seed, x, y, z -> Vector2(this(seed, x, y, z),
this(seed xor 0x7f7f7f7f, y, -x, z)) }
.gradient()
And finally, we have gradient function. Which returns a function that approximates the gradient of the receiver function, also known as (curl noise):
fun ((Int, Double, Double, Double) -> Vector2).gradient(epsilon: Double = 1e-2 / 2.0):
(Int, Double, Double, Double) -> Vector2 =
{ seed, x, y, z ->
val dfdx = (this(seed, x + epsilon, y, z) - this(seed, x - epsilon, y, z)) / (2 * epsilon)
val dfdy = (this(seed, x, y + epsilon, z) - this(seed, x, y - epsilon, z)) / (2 * epsilon)
dfdx + dfdy
}
Notes and references
- FBM stands for fractal brownian motion.
- Lacunarity
- Gradient
- Tutorials about looping noise by Etienne Jacob
Original text by Edwin, formatted and edited by Abe, reference links by Yann.