Random Talk: The Consistent World of Noise

New Features in Realm Obj-C & Swift

We’ve released version 2.3 of Realm Objective‑C and Realm Swift, which includes several improvements to the Realm Mobile Platform features, such as sync progress notifications, backup recovery, and more flexible sharing mechanisms.

Chance permeates our human existence - but it’s our instinct to seek order in chaos. In this try! Swift talk, we’ll explore the fishy realm of randomness, and when it’s just too unnatural for our apps - let’s bend it to our will by making it evolve into coherent patterns with the GameplayKit framework.

We’ll use the latest iOS 10 APIs and procedural noise to generate harmonious digital worlds, landscapes, and textures - a comforting way to mine some creativity from silicon chips.


Introduction (0:00)

Let’s talk about random things. What is randomness? What does it mean that a number is random? Is the number 42 random? It’s hard to fathom this without getting all metaphysical and pondering upon the meaning of life, the universe, and everything.

Yet, in ancient times, it wasn’t bearded philosophers and mathematicians who asked these questions. It was gamblers, fortune tellers, and wizards. The characters we normally associate with games.

As a game developer, randomness is of specific importance to me. Let’s play a practical game to see how good we are at this, at defining randomness.

What is randomness (1:08)

Let’s say we have a coin tossed several times in sequence. White is for heads. Red is for tails. One of the two series of tosses on slide 3 is random, and the other is not. Now this is a great party trick.

The second sequence is really random. The first one was made up by a human and is a complete fake.

It so happens that humans, when asked to generate randomness, tend to alternate a lot. When you try to enter a random string on your keyboard you turn into a keyboard cat, and you keep banging your paws against alternate sides of the keyboard, right? You don’t just press the same key over and over again.

Humans tend to do a lot of that switching back and forth. But you can call their bluff, and there’s a probability formula for that, as you can see.

N = 2ʳ
32 = 2⁵

In 32 tosses (N), such as these, it is likely that you will get a sequence of five identical outcomes in a row (ʳ). It is even more likely that you’ll get four.

The thing with the human mind is that it searches for patterns everywhere, even when there isn’t one. It tries to fill in the gaps heuristically. People try to find meaning in meaningless sequences, as we all do when we reflect on events in our lives. Our idea of randomness is defective.

Humans cannot reliably generate randomness, nor will they know a series that’s random, even if it attacked them in broad daylight. If we can’t reliably produce randomness, we rely on machines instead.

But what are the devices that the machines can resort to? There are two paths that can be taken here. One is true randomness, and the other, not so true, namely pseudorandomness.

Both perspectives have their own pros and cons, and depend on what kind of mechanism or stream is tapped into for generating random numbers. In essence, true random number generators use a very physical source to feed. That entropy which they plug into can be anything from background noise to radioactive decay to fluid motion.

There used to be a random number generator that was based on recording lava lamps, called Lavarand. The distribution is even, so each outcome is equally probable, and each successive number is completely independent of the previous ones. No number has any bearing on the rest.

Pseudorandom number generators, on the other hand, rely on math, using algorithms, or functions that spit out random numbers, and then recycle them in some way. The probabilities of generating given numbers are, more or less, equal, depending on how advanced the algorithm is.

Finally, they’re not independent, which means that one generated number influences the fate of the next ones in a predictable way. We call that deterministic. In this talk, we’re trying to escape entropy; we’re trying to get away from cryptography. For us, determinism is a good thing, so we’ll focus on the pseudorandom.

Pseudorandom (5:36)

First, let’s tap into a simple pseudorandom number generator and ask it to generate some numbers for us. It all starts with a magic number called “seed.” Let’s feed our generator on slide 9 with the number 1 as the seed, and see what random numbers it can churn out for us.

Let us now look under the hood, and indeed, there is a function that grabs this seed:

(2 *  + 3) mod 9

It does some computations, outputs the number it computed, stores the state of that number, and fills the gap with it again. We rinse and repeat until that number is the same as the seed. Then, the pattern will repeat. That would be the period of this function.

The bottom line is that the seed allows us to generate the exact same sequence every time.

Now, this was a terribly primitive generator, but it gives you an idea of how prudent you must be with these for your applications.

You might want to select the technique of random number generation carefully. That’s where GameplayKit can come in. Let’s take a look at what it offers.

For pseudorandom number generation algorithms, you can choose from three of those in the GKRandomSource superclass. First, there’s one with an unspeakably awful name: Linear Congruential Random Number Generator.

let randomSource = GKLinearCongruentialRandomSource()


X𝑛+1 = (a * X𝑛 + c) mod m

If you look closely, you’ll notice that that is the very same formula that we used under the hood in our previous example. However, here, the parameters will be sufficiently ginormous, so, in practice, you won’t encounter the problems of our primitive example. It does have a slight preference towards certain numbers, but it is fast.

The Mersenne Twister is a more modern algorithm:

let randomSource = GKMersenneTwisterRandomSource()


Period: 2¹⁹⁹³⁷ - 1

How many numbers it can generate in a sequence before it repeats itself is larger than the number of particles in the observable universe by a few times. It provides quality randomness. It is slightly slower though.

The familiar-sounding ARC4 source uses the well-known ARC4 algorithm, and stands in neutral ground between speed and randomness:

let randomSource = GKARC4RandomSource()

Overall, for simple simulations, really, any of these is sufficiently random, or random enough. But if you’re into generating vast amounts of random numbers at once, it is good to know the ups and downs of each.

An honorable mention goes to the convenience of shuffling. GameplayKit has a very, very handy method to take care of all that. Just rely on its shared random instance, and call array by shuffling objects:

let fruitDeck = ["🍉", "🍌", "🍏", "🍒"]


let shuffledDeck = GKARC4RandomSource.sharedRandom()
.arrayByShufflingObjects(in: fruitDeck)

Speaking of shuffling, interestingly, with one early iteration of the iPod, some users complained that their playlists weren’t shuffled properly. They would get the same songs, or songs by the same artist, played back to back, and that didn’t feel random. In fact, it was really random, but it didn’t fit their idea of randomness.

So, Steve Jobs made sure the algorithm was fixed to make it less random with more alternation, fewer streaks, kind of like the fake coin example that we saw earlier.

Distribution (9:48)

One great way of curbing randomness, and getting a grasp on it, is distribution, which is the layout of all possible values and their probabilities. GameplayKit offers three out of the box distributions we can apply to one of our random sources:

  • GKGaussianDistribution
  • GKShuffledDistribution
  • GKRandomDistribution

The best-known one is the Gaussian distribution, the famous bell curve, also known as the normal distribution, where the average values are the most common, and those in the extremes are the rarest.

Let’s use our Gaussian distribution over dice rolls. On slide 22 and onwards, you can see a graph for the distribution, and the example results of our dice rolls.

If we roll the die, say, six times, you get the mid-range numbers, like three and four, with the highest probability of occurrence.

let 🎲 = GKGaussianDistribution.d6()
for roll in 1...6 { !.nextInt() }


// Results: 4, 5, 4, 4, 3, 3

The shuffle distribution, also called fair distribution, is uniform, so any value is equally likely to occur, but it avoids using the same values in a sequence:

let 🎲 = GKShuffledDistribution.d6()
for roll in 1...6 { !.nextInt() }


// Results: 6, 2, 4, 1, 3, 5

It completely avoids streaks, so it’ll try to exhaust all possibilities before repeating any number.

Finally, the random distribution, which is the default, the basic superclass, uses the uniform distribution. All numbers have an equal chance of being rolled. And that is all there is to it:

let 🎲 = GKRandomDistribution.d6()
for roll in 1...6 { !.nextInt() }


// Results: 1, 1, 1, 6, 5, 5

However, for our considerations for many apps and games, you might want to pick the right distribution, not necessarily the default one, and here’s why.

Look at these raccoons, grown to completely random heights on slide 27. Whatever they were eating, it must have been radioactive. The variation in the complexity of life is never completely random, but, in fact, predictable, even the general height of raccoons.

let random = GKRandomDistribution(lowestValue: 3, highestValue: 10)
raccoon.setScale = CGFloat(random.nextInt()) * 0.1

The way to make these randomized raccoon sizes look more natural is by spreading them over the Gaussian distribution:

let gaussian = GKGaussianDistribution(lowestValue: 3, highestValue: 10)
raccoon.setScale = CGFloat(gaussian.nextInt()) * 0.1

Now only a select few will be abnormally huge or super short. Yet, this idea applies to so much more than just physical characteristics.

Take a UFO invasion over this forsaken village on slide 28. If we applied pure randomness, some ships will be in lockstep, moving together, precisely on point:

let random = GKRandomDistribution(lowestValue: 10, highestValue: 25)
for ufo in ufoNode.children {
 ufo.run(SKAction.moveTo(y: -1500, duration: TimeInterval(random.nextInt())))
}

This is not an air show, nor an exercise. Invasion is serious business. With Gaussian distribution, their speed is random, yet more elastic, so you’ll see less lockstepping:

let gaussian = GKGaussianDistribution(lowestValue: 10, highestValue: 25)
for ufo in ufoNode.children {
 ufo.run(SKAction.moveTo(y: -1500, duration: TimeInterval(gaussian.nextInt())))
}

Units will fly more gracefully, there’ll be a leader, and some aliens lagging behind, but their average speed will be maintained.

Regardless of probabilistic distribution, the randomly generated numbers are too random to create content themselves, to become content directly. Look at these numbers connected on slide 30. It’s a complete white plunging noise. It’s rugged and rough. A complete mess.

Now, wouldn’t it be nice to have random numbers that form a continuum that makes sense and to infuse them with memory, so that values have consequence in relation to the next ones in smooth transitions instead of these chaotic spikes?

Procedural noise (13:56)

This need is fulfilled by a procedural noise. Where the random sequence is sleek and coherent. Where it unravels controllably. Where it’s consistent and offers some degree of familiarity.

Instead of white noise, you get velvet that’s random, yet organic. The mother of coherent noise is Perlin noise. It was invented by Ken Perlin, in the 1980s, while he was working on the CGI for the movie Tron. This brought Perlin an Academy Award for technical achievement as late as 1997, because originally, the Academy had disqualified Tron because they thought the use of computers in graphics was cheating.

In a nutshell, a noise function is nothing else but a seeded random number generator, but the values are interpolated to make it smooth, and then the smooth functions are added on top of each other to create coherent fractal noise.

Let’s take a look at raw Perlin noise and its example uses.

Think of it this way: Perlin noise in one dimension is like a mountain range, or a wave. Something undulating. The use case for one-dimensional Perlin noise is anything wobbly, such as hand-drawn lines of a shaky hand.

Two-dimensional Perlin noise is commonly used for texture generation and because it can be easily scaled and transformed, you can grab a simple color gradient and have your own machine churn out naturally-looking textures.

Perlin noise can span over three, and more, dimensions. You can use that to generate Minecraft-like terrained worlds with hills and caves, and whatnot.

Let’s go into the rabbit hole and check out GameplayKit’s built-in noise sources that will be available starting with iOS 10. The selection is a real cliffhanger, so let’s take a look at a few of these. How do you generate such noise with GameplayKit exactly?

let noiseSource = GKPerlinNoiseSource()
let noise = GKNoise(noiseSource:noiseSource)

First, you initialize a noise source, choosing the general style of noise. Perlin, in this case. This is the very noise generation algorithm whose parameters you can tweak. Then, you feed this abstract source in to a noise object, which would be its actual representation.

Keep in mind that the noise source generates a field of values that’s practically infinite in three dimensions, and that’s what the noise object embodies. The noise source, the algorithm, can then be tailored by setting several properties.

For Perlin, one of these would be octaveCount, which is how many successive noise functions, with higher frequencies, you want on top of one another, increasing the level of complexity and turning Perlin noise into a fractal.

Or frequency itself, which controls the number of visible features. If we set up a Perlin noise generation algorithm accordingly, we can create a pretty-looking texture that’s infinite in extent:

let noiseSource = GKPerlinNoiseSource()
noiseSource.frequency = 1
noiseSource.octaveCount = 6
noiseSource.lacunarity = 2
noiseSource.persistence = 0.5
let noise = GKNoise(noiseSource:noiseSource)

No need for trial and error here because these also happen to be the default values, so you don’t even have to set that.

Now, the noise source generates values between minus one and one, which makes it scalable. When creating a noise object you point these values to some colors, creating a nice gradient:

// ADD COLOR


let noiseSource = GKPerlinNoiseSource()
let noise = GKNoise(noiseSource:noiseSource)
noise.gradientColors = [-1 : !, 0 : ", 1 : ⚪]


// MAP TO TEXTURE


let map = GKNoiseMap(noise: noise)
let sprite = SKSpriteNode(texture: SKTexture(noiseMap: map))

Then, you sample a slice of the noise to create a noise map, like grabbing a cheese slice from a block of cheese, and you put it in a texture. That’s how we get a real cloud texture, like the one you see in the background, in just a few lines.

On a side note, you can not only tinker with source parameters, but the noise object itself is easily transformable. You can use numerous translations to change its character. You can invert, recalculate, add noises, apply turbulence, move scale, rotate, and more.

Animation (19:03)

Let’s see some action with what we’ve generated so far. One great application is animation. In this example, the noise map samples a slightly further region of the noise object with each frame, creating an animation of moving clouds.

let moveMap = SKAction.run {
    map = GKNoiseMap(noise: noise,
    size: vector2(2, 2),
    origin: (vector2(map.origin.x+0.01, 0)),
    sampleCount: vector2(300, 300),
    seamless: false)
    sprite.texture = SKTexture(noiseMap: map)
}


let moveMapForever = SKAction.repeatForever(
(SKAction.sequence([moveMap,
SKAction.wait(forDuration: 0.1)])))
sprite.run(moveMapForever)

This can go on and on, indefinitely, without taking up much memory, and without storing any textures anywhere beforehand.

Better yet, if we move through the very noise object, cutting down through it, slicing deeper with each frame, you’ll get an animation of billowing and dissipation.

noise.move(by: (vector3(0.0, 0.01, 0.0)))
let newMap = GKNoiseMap(noise: noise)
let newTexture = SKTexture(noiseMap: newMap)
sprite.texture = newTexture
sprite2.texture = newTexture

Here I’m using two mirrored sprites, re-texturing them as we go through the noise to create an infinite inkblot test effect.

Importantly, Perlin noise does not only apply to visual representations, but can be hooked up to almost anything. After all, it’s just a field of numbers, so maybe movement.

myCamera.position.x = CGFloat(noise.value(atPosition: (vector2(Float(waveStep), 0))))*100
myCamera.position.y = CGFloat(noise.value(atPosition: (vector2(0, Float(waveStep)))))*100
waveStep += 0.01

Here, I’m sampling noise values to drive the zoomed camera for a shaky hand effect. If you put in completely random values there, instead of coherent noise, you would get nonsense. The camera would be all over the place.

How about a creepy cyber punk vaporwave avatar, and his minute facial expressions as another application?

The different Perlin noise instances have their values hooked up continuously to facial muscles, making this even creepier, and more uncanny than if it were simply randomized.

let timeStep = waveStep/3 + 0.3
    * sin(M_2_PI * waveStep/3)


let eyeBallFactorX = CGFloat(
    noiseHead.value(atPosition:
    (vector2(Float(timeStep), 0))))


let eyeBallFactorY = CGFloat(
    noiseHead.value(atPosition:
    (vector2(Float(timeStep+10)/2, 0))))

But there are other styles of noise in GameplayKit, such as the ridged noise source, which is like Perlin but with more spiky peaks. It’s good for generating Himalayan mountain ranges or electricity of spells, or cheesy fire, because we can.

let source = GKRidgedNoiseSource()
source.lacunarity = 2
source.frequency = 3
source.octaveCount = 6


noise.move(by: (vector3(0.0, 0.01, 0.0)

With some simple tweaks, I also created stone and marble textures. The Voronoi noise is interesting. It’s botanical, organic, you can create cool cellular worlds or cracked crystalline structures.

Or the cylinder noise. I applied a little turbulence to generate a natural-looking texture of wood. This could go on and on endlessly, like the noise itself. Let’s use everything at once to make something cool.

Here’s a final example of different noise types applied to generate the textures of water, sand, tree-covered hills, and mountain peaks.

let combinedNoise = GKNoise(
    componentNoises: [noiseWater, noiseSand, noiseTrees, noiseMountains],
    selectionNoise: ridgedNoise, componentBoundaries: [-0.7, -0.6 ,0.5],
    boundaryBlendDistances: [0, 0.1, 0.3])

These noise objects can be combined creatively on a much grander scale. I’ve mapped them to a ridged noise source so that the lowest values are textured with water, and these biomes continue all the way up until the mountain peaks.

The noise objects are really lightweight, so why not use them to texturize a prototype of an entire planet?

let ball = SCNSphere(radius: 100)
let materials = SCNMaterial()
materials.locksAmbientWithDiffuse = true
materials.diffuse.contents =
    UIImage(cgImage: (spriteCombined.
    texture?.cgImage())!)
ball.firstMaterial = materials
ball.isGeodesic = true

The nature of random coherent noise is, perhaps, best visible when it’s mapped to a 3D terrain geometry object in SceneKit, for example.

Here, you have a visualization of the relatively smooth Perlin, and the more peaky ridged noise, and how lucid they are. Pure randomness would just be completely illogical.

But why limit yourself to a planet when you can make something of more magnitude, such as a procedurally-generated universe around the player in your game? The procedural generation of textures and terrain stems from the old school demo scene, where they packed huge worlds into tiny executables.

Determinism / Conclusion (23:23)

These worlds, generated by coherent noise, are random, yet deterministic. This means you can create a multitude of familiar-looking textures, surfaces, or other random pieces of art at run time.

Still, that procedural noise is, in fact, a seeded pseudorandom number generator inside, just like the one I presented at the beginning of this talk. Using the same seed property value, you’ll be able to recreate the exact same sequence. One number will be able to conjure up the same world, or the same universe, for your player.

Randomness is, indeed, a very subtle field, and using it in your apps can be tricky. Whatever you generate, it may be too random, or not random enough.

Einstein himself had a bit of a problem with this in his famous saying, “God does not play dice with the universe.” Indeed, much of our world can be only understood through the principles and patterns of randomness.

I hope I’ve managed to make you question your perception of it. I encourage you to try and control it. This is impossible in life, so, at least, maybe, in your apps and games, and make the previously white noise coherent and intelligible using the right tools, such as GameplayKit and iOS 10.

Resources


Natalia Berdys

Natalia Berdys

Natalia Berdys is an independent iOS developer from Poland. Within 2 years, she managed to become a self-taught developer, get a Mobile Engineering degree, speak at Apple WWDC and take her apps to #1 in 47 countries. Since she also holds a Master's Degree in American Literature, she has a very humanistic and poetic view of programming. Previously with Tutu Lab, now evolving into her next form.

Transcribed by Hilary Fosdal