Kevin Song
I'm a student at UT (that's the one in Austin) who studies things.
It's often faster to render certain effects on the GPU, even in 2D. This is, for example, how Processing achieves better performance in P2D
mode.
Shaders take part in a procedure that tells the GPU how to draw things.
Not really.
Partially because AMD's OpenGL support at the time was kinda trash.
OpenGL does extra work as part of its attempt to be nice and easy to work with. However, this work is not always necessary.
If you don't have a minimal working example by the time of your presentation, I'd be a little concerned. It doesn't have to be pretty or complete, but the basic outline should be in place.
You don't need a complete product to present the idea of what you're working on and challenges you're facing. Most tech companies present with products that are utterly broken.
I am going to get way too excited and talk too much. It is not critical that you remember everything (or really, anything) about what's discussed here.
If you want more details on any of these ideas, there are slides at the end with links where you can dig into the subjects discussed.
a method of creating data algorithmically as opposed to manually, typically through a combination of human-generated assets and algorithms coupled with computer-generated randomness and processing power.
Thanks, Wikipedia!
float x = random();
Without rand()
, every program we write is totally predictable. But how can we implement rand()
if all code is completely predictable??
Okay not really.
Random Number Generators (RNGs) on your computer do not actually try to make random numbers. Instead, they try to make a sequence that is unpredictable: even if you've seen a lot of output, it's still hard to predict the next number.
But if you have the right key....
The "Linear Congruential Generator"
int seed; // Initialized elsewhere
int m = 2^16 + 1;
int a = 75;
int c = 74;
int random(){
int next = a * seed + c;
next = next % m;
seed = next;
return next;
}
[4, 8, 4, 7, 2, 6, 1, 6, 4, 1, 0, 1, 4, 4, 8, 3, 1, 0, 9, 8, 4, 1, 2, 8, 0, 5, 1, 1, 4, 7, 3, 7, 9, 6, 0, 4, 7, 9, 8, 0, 5, 1, 2, 7, 3, 7, 4, 9, 1, 1, 2, 0, 6, 8, 4, 0, 9, 3, 6, 3, 8, 7, 8, 4, 5, 3, 8, 8, 4, 2, 6, 0, 3, 1, 3, 2, 6, 8, 3, 1, 2, 5, 1, 2, 1, 0, 3, 2, 6, 4, 8, 7, 8, 7, 2, 1, 8, 0, 2, 5]
With seed = 100
This RNG is fast, but tends to generate low-quality randomness. Avoid it where possible.
If we know the seed used for an RNG, we can perfectly replicate the sequence of "random" numbers that the RNG generates.
[10, 1, 7, 8, 10, 1, 4, 8, 8, 5, 3, 1, 9, 8, 6, 2, 4, 6, 1, 7, 3, 10, 6, 7, 7, 5, 5, 8, 3, 5, 6, 3, 8, 4, 8, 10, 7, 1, 10, 1, 4, 3, 4, 5, 9, 6, 4, 6, 9, 8, 7, 8, 2, 10, 6, 9, 3, 4, 7, 4, 1, 1, 8, 5, 10, 2, 9, 2, 3, 7, 10, 6, 10, 3, 2, 2, 8, 3, 4, 6, 7, 7, 8, 4, 5, 3, 10, 9, 3, 2, 5, 8, 5, 3, 3, 3, 8, 6, 6, 7, 4, 1, 9, 1, 6, 6, 4, 2, 5, 8, 7, 10, 3, 7, 8, 4, 9, 5, 9, 8, 10, 8, 2, 3, 8, 8, 7, 3, 7, 9, 10, 6, 9, 7, 8, 3, 9, 8, 2, 7, 1, 1, 8, 9, 2, 1, 6, 2, 3, 2, 10, 8, 8, 3, 8, 7, 9, 8, 6, 5, 8, 7, 6, 9, 9, 3, 4, 6, 10, 1, 4, 10, 10, 8, 1, 2, 5, 7, 4, 5, 2, 1, 10, 9, 2, 5, 3, 8, 3, 6, 10, 7, 4, 6, 3, 2, 10, 4, 7, 4, 2, 5, 8, 9, 7, 8, 9, 4, 5, 10, 6, 2, 1, 4, 7, 10, 7, 7, 4, 2, 7, 2, 1, 6, 5, 6, 8, 7, 4, 5, 8, 5, 9, 8, 9, 5, 7, 8, 6, 6, 5, 8, 8, 8, 10, 8, 2, 9, 6, 7, 3, 10, 7, 10, 2, 10, 5, 1, 10, 6, 6, 2, 7, 10, 6, 5, 10, 3, 4, 3, 5, 2, 9, 1, 4, 10, 2, 7, 4, 10, 10, 8, 10, 1, 7, 4, 6, 10, 4, 9, 10, 6, 1, 10, 1, 1, 10, 6, 7, 6, 10, 3, 3, 2, 10, 6, 3, 1, 9, 9, 6, 2, 7, 8, 10, 6, 1, 3, 8, 8, 10, 8, 8, 10, 3, 6, 7, 6, 9, 9, 8, 7, 4, 2, 7, 3, 1, 3, 4, 10, 7, 6, 9, 7, 1, 10, 1, 3, 5, 4, 1, 10, 3, 2, 10, 6, 3, 5, 1, 7, 10, 7, 2, 6, 4, 8, 3, 3, 7, 8, 6, 1, 3, 3, 10, 7, 3, 9, 7, 2, 7, 2, 4, 2, 7, 5, 5, 9, 2, 6, 9, 10, 8, 10, 4, 9, 5, 1, 3, 7, 4, 2, 9, 8, 2, 10, 7, 10, 4, 5, 6, 2, 4, 10, 9, 9, 1, 2, 7, 1, 1, 7, 6, 3, 1, 6, 6, 8, 3, 1, 4, 3, 5, 4, 4, 4, 7, 10, 9, 10, 10, 7, 9, 1, 3, 1, 2, 1, 1, 3, 2, 7, 2, 1, 7, 3, 10, 7, 1, 9, 3, 1, 7, 10, 8, 8, 6, 3, 1, 7, 8, 8, 6, 2, 3, 5, 5, 3, 2, 8, 1, 3, 5, 2, 9, 5, 3, 2, 9, 2, 5, 10, 6, 1, 10, 5, 9, 7, 9, 3, 1, 1, 10, 1, 4, 9, 1, 4, 3, 5, 7, 3, 1, 6, 9, 2, 8, 5, 8, 8, 3, 3, 1, 7, 10, 3, 8, 3, 6, 9, 8, 3, 1, 8, 10, 7, 3, 8, 5, 4, 8, 9, 6, 10, 9, 7, 3, 6, 6, 3, 1, 8, 1, 6, 9, 9, 1, 7, 10, 2, 1, 3, 1, 1, 6, 7, 4, 8, 10, 2, 2, 2, 6, 8, 7, 5, 3, 3, 7, 9, 9, 4, 4, 3, 7, 4, 9, 10, 8, 7, 4, 2, 9, 3, 10, 10, 9, 3, 2, 7, 8, 2, 4, 1, 8, 3, 3, 6, 7, 4, 4, 6, 5, 10, 5, 3, 1, 8, 7, 6, 6, 1, 6, 5, 9, 10, 1, 7, 5, 8, 3, 2, 7, 7, 2, 2, 8, 3, 2, 7, 9, 2, 1, 9, 9, 5, 10, 8, 2, 7, 2, 7, 8, 4, 7, 1, 8, 8, 10, 8, 7, 5, 9, 3, 8, 3, 4, 8, 8, 10, 8, 7, 5, 9, 8, 10, 1, 9, 9, 6, 8, 5, 3, 4, 4, 6, 10, 3, 9, 1, 9, 10, 1, 1, 3, 7, 5, 3, 4, 1, 3, 8, 6, 1, 9, 4, 1, 1, 3, 8, 8, 6, 1, 7,6, 5, 1, 3, 5, 1, 3, 4, 2, 9, 8, 5, 6, 6, 9, 7, 6, 10, 5, 7, 1, 2, 8, 6, 8, 7, 8, 6, 2, 3, 10, 4, 9, 6, 5, 4, 1, 10, 7, 1, 3, 3, 8, 6, 7, 2, 7, 4, 4, 10, 2, 8, 1, 9, 5, 8, 2, 10, 2, 4, 3, 3, 8, 7, 9, 4, 7, 4, 7, 7, 4, 4, 6, 8, 3, 4, 10, 1, 9, 3, 4, 2, 7, 9, 1, 10, 9, 4, 10, 6, 6, 8, 10, 3, 8, 4, 7, 7, 8, 9, 2, 7, 10, 9, 2, 4, 1, 2, 2, 5, 4, 5, 6, 5, 9, 6, 3, 6, 10, 3, 2, 1, 8, 2, 7, 10, 10, 1, 3, 10, 3, 7, 4, 8, 8, 2, 3, 2, 6, 5, 3, 1, 8, 9, 1, 1, 1, 8, 9, 7, 9, 9, 5, 10, 10, 6, 5, 10, 3, 8, 6, 6, 2, 5, 5, 6, 9, 2, 5, 7, 6, 8, 7, 7, 3, 5, 2, 3, 9, 10, 3, 1, 8, 6, 7, 5, 3, 6, 4, 3, 1, 1, 10, 3, 10, 9, 9, 3, 1, 10, 5, 6, 8, 6, 5, 5, 2, 4, 6, 2, 2, 10, 4, 8, 9, 4, 10, 10, 5, 9, 5, 1, 9, 7, 6, 8, 10, 1, 6, 6, 8, 8, 5, 9, 8, 1, 6, 5, 10, 10, 8, 3, 4, 7, 7, 6, 5, 2, 6, 2, 2, 2, 5, 7, 4, 3, 9, 3, 1, 9, 9, 5, 3, 6, 10, 4, 10, 2, 3, 6, 1, 6, 8, 4, 8, 1, 10, 8, 10, 3, 1, 9, 5, 9, 3, 9]
random.seed(10)
b = [ random.randint(1, 10) for _ in range(1000) ]
A video game written in 1980 at UC Santa Cruz.
Upon death, the player character is completely lost, along with all progress, and a new player character is created.
Gave rise to the descriptor of a game being "Rogue-like". Today, this is often shortened to "roguelike" and treated almost as its own genre.
For various reasons, the dungeons are randomly generated every time the character dies.
size(500, 500);
noFill();
stroke(0);
strokeWeight(4);
for(int i = 0; i < 5; i++){
int x = (int)random(0, 400);
int y = (int)random(0, 400);
int w = (int)random(0, 100);
int h = (int)random(0, 100);
rect(x,y,w,h);
}
Worst 4 out of 8 attempts!
It is hard to get a procedural generation system that feels good. Procgen is a terrible way to be lazy---you'll probably spend 3x as much effort tuning the generation system as creating content to get similar quality.
size(500, 500);
noFill();
stroke(0);
strokeWeight(4);
int numRooms = 0;
while(numRooms < 10){
params = genRandomNumbers();
if( intersectsExistingRoom(params) ){
continue;
}
if( areaTooLow(params) ){
continue;
}
if( isOffscreen(params) ){
continue;
}
rect(x,y,w,h);
}
I was using the biggest, fastest computer I could get my hands on, but it only had 128K of memory
Create a 3x3 grid on the screen, and only allow one room per grid cell. Reject that room if it's too small or falls too close to another one (but do not try again).
Then link up rooms to rooms that are close to each other until every room has at least one connection.
Sadly, the design of many of these systems is not preserved in plain text (you have to read C source code to understand what the game does).
Fortunately, Rogue has generated a huge number of spiritual successors, the so-called roguelike and roguelite genres.
Input | Output |
---|---|
1 | 11 |
0 | 1[0]0 |
Consist of a series of rules and a starting symbol.
Start: 0
Iter 1: 1[0]0
Iter 2: 11[1[0]0]1[0]0
Iter 3: 1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0
At each iteration, apply all possible rules to symbols from the previous iteration.
Symbol | Meaning |
---|---|
1 | Draw a straight line |
0 | Draw a line terminated by a leaf |
[ | Push current transform and turn left 45° |
] | Pop last transform and turn right 45° |
In fact, with very little extra work, we can create some very complex shapes with L-Systems.
Input | Output |
---|---|
X | F+[[X]-X]-F[-FX]+X |
F | FF |
Symbol | Meaning |
---|---|
F | Draw a straight line |
- | Turn right 25° |
+ | Turn left 25° |
[ | Push current transform |
] | Pop last transform |
X does not have a meaning for drawing (only used for the generation phase)
Mortimer von Chappuis, CC-BY-SA 4.0, https://en.wikipedia.org/wiki/L-system#/media/File:Fractal_Farn.gif
As pretty as L-Systems are, they do have the drawback of being deterministic. You can change the number of iterations you want to run, but at each iteration, the shape is the same.
Solution: add stochastic production rules.
Input | Output | Probability |
---|---|---|
1 | 11 | 100% |
0 | 1[0]0 | 75% |
0 | 0 | 15% |
0 | 1 | 10% |
When we see a "0", apply one of the rules w/ appropriate probability.
Input | Output | Probability |
---|---|---|
1 | 11 | 100% |
0 | 1[0]0 | 75% |
0 | 0 | 15% |
0 | 1 | 10% |
How can we pick one of the three "0" rules?
float r = random();
if(r < 0.75){
applyFirstRule();
} else if (r < 0.9){
applySecondRule();
}else{
applyThirdRule();
}
So all we need to specify is:
Input | Output | Probability |
---|---|---|
1 | 11 | 100% |
0 | 1[0]0 | 75% |
0 | 0 | 15% |
0 | 1 | 10% |
Symbol | Meaning |
---|---|
1 | Draw a straight line |
0 | Draw a line terminated by a leaf |
[ | Push current transform and turn left 45° |
] | Pop last transform and turn right 45° |
Sakurambo, CC-BY-SA 3.0, https://en.wikipedia.org/wiki/L-system#/media/File:Fractal-plant.svg
If we can go 29,999,984 blocks in the X or Z direction, then there must be \(2 \times 29,999,984 = 59,999,968\) blocks along each side of the Minecraft world. This means there are \(59,999,968^2 \times \text{height}\) blocks in the world. The wiki tells us that we can go down to y=-64 or up to y=320 in the world, so we get 382 blocks in the y-direction. This adds up to \(1,382,398,525,440,393,216\) blocks in the overworld (plus whatever is in the Nether or the End). The Minecraft wiki lists 876 different block types, which means we would need at least 10 bits per block to store the block type, which gives us just over 13,823,985 terabytes of data in order to store a single Minecraft world in its entirety. At current hard drive prices, it would cost approximately $230 million dollars for this much storage.
Create another layer of noise for temperature, then use elevation + temperature to decide biome (e.g. low + cold = cold ocean, high + cold = frozen peaks)
Attempt 1: Give each pixel a random color between 0-255.
We want noise that's smoothly changing!
Terrain where heights are randomly chosen every 5 feet are not going to be fun for the player to explore, or look very realistic.
Value noise: notice how there are clear diagonal streaks running through the image, almost like a quilt.
No observable visual patterns.
Instead of randomizing the value of the noise, we randomize its direction of increase.
Image and following images By Matthewslf - Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=77692523https://en.wikipedia.org/wiki/Perlin_noise
Note that noise value always increases when traveling along a red arrow and decreases when traveling directly opposite it.
One of the classic gradient noise functions.
Can be used to generate values over an area:
But still need to enforce logical constraints (e.g. rivers cannot flow over peaks), noise alone cannot account for this.
Build a biome map. This uses value noise in pixel-randomness and then applies a series of rules to try to smooth out biome weirdness, e.g. joining landmasses together and avoiding oceanic puddles.
What system does this look like?
Use Perlin noise to decide on temperatures. Apply another blending layer (another CA) to smooth out temperature differences so that we don't have a snow field next to a hot desert.
Also make rare biomes, hilly biomes, etc. variants here.
Now that our map has several large regions, there's a patchy look, where large biomes meet each other. Run edge detection over the biome map (again, Zucconi's words, not mine!) to find "seams" that we can place rivers along.
Terrain! Terrain! Terrain!
Use Perlin noise (sampled at several scales) to set the heights of terrain in the world, along with a modified noise algorithm (Perlin worms) to carve out caves and overhangs.
Cannot recommend this article highly enough.
https://www.alanzucconi.com/2022/06/05/minecraft-world-generation/
Very detailed, relatively accessible explanation (low math!) and you should recognize many of the concepts in there.
Terrain Generation Algorithm
Random Seed
Is enough to store 13 million terabytes worth of info!
https://nylander.wordpress.com/2004/08/16/perlin-noise/
Not traditionally considered part of procgen, but I will argue that there's a case for certain AI systems being part of a procedurally generated system.
Classic game AI: interaction on timescale of minutes, player learns and adapts to game AI.
AI for procedurally generated experiences: repeated interaction over hours, player adapts to AI, AI adapts to player (or appears to!)
You're an engineer who shows up to retrieve some data from a space station.
Unfortunately, the space station is completely wrecked due to an attack by xenomorphs, and you're trapped on the station.
Now you have to survive while the alien tries to turn you into an hors d'oeuvres.
Director AI
Alien AI
"Steve"
A concept which first appears in Left 4 Dead as a way to manage tension of the player.
General cycle:
Fun fact, in early builds of the game, playtesters would run up behind the alien and stay right behind it to avoid detection. To avoid this, the developers placed an "eye" on the alien's tail.
Player Spotted?
Attack Path to Player?
Attempt to attack
Move around
yes
yes
no
Player heard?
no
Move toward sound
Investigate surroundings
yes
no
Player Spotted?
Attack Path to Player?
Attempt to attack
Move around
yes
yes
no
Player heard?
no
Move toward sound
Investigate surroundings
yes
no
Evade player and ambush
player has a flamethrower
Many, many additional details (e.g. how the alien AI is allowed to detect you, how quickly it picks up on clues, etc.) meticulously hand-tuned by the developers while making the game.
An experience which often feels handcrafted for the player, even though it is utterly unique.
But there are lots more ideas in each category that we haven't covered, and a few categories that we've still skipped!
(arguably with mixed results)
A crucial component of procedurally generated content is that it is cohesive: that it fits well with other content in the game.
Generative AI has only begun to be able to do this, and it's not clear that it can be consistently consistent without lots of engineering effort.
Exciting work on this front, but no major games using stable diffusion or LLMs for procedural generation yet.
https://www.freecodecamp.org/news/random-number-generator/
https://www.pcg-random.org/
Note: The author of the technical pieces is the creator of the PCG (a particular RNG algorithm), and is naturally biased in favor of it, but the explanations on this site and its blog are still pretty good.
https://www.alanzucconi.com/2022/06/05/minecraft-world-generation/
Dungeon Hacks by David L. Craddock
UT has infinite free access through the library, https://search.lib.utexas.edu/permalink/01UTAU_INST/be14ds/alma991058320981006011
For a quick overview of Perlin noise, the Wikipedia article is pretty good: https://en.wikipedia.org/wiki/Perlin_noise
For a more in-depth view of implementing Perlin noise, try this article: https://adrianb.io/2014/08/09/perlinnoise.html. Unfortunately, there are few resources on Perlin noise generation that don't involve some level of mathematics.
Ken Perlin introduced an improved variant of Perlin noise with fewer directional artifacts and faster generation in higher dimensions. It is known as simplex noise. https://en.wikipedia.org/wiki/Simplex_noise
Alan Zucconi's article is a fantatstic dive into the world generation algorithm, including its phases, how Perlin noise is used, and even some modifications that happened in 1.18. https://www.alanzucconi.com/2022/06/05/minecraft-world-generation/
If you would like to see what it might look like to modify world generation yourself, take a look at the Sponge docs. Sponge is a Minecraft server which lets you write custom plugins in Java, so the code should be somewhat familiar.
https://docs.spongepowered.org/5.1.0/en/plugin/wgen/customwgen.html
https://lsystem.club/ has a short tutorial on deterministic and stochastic L-systems, along with visualizations of various L-systems that you can modify and play around with.
Houdini, a 3D modeling/animation application, has an L-System module for rapidly generating geometry. You can see its documentation at https://www.sidefx.com/docs/houdini/nodes/sop/lsystem.html
Articles:
Videos:
http://pcg.wikidot.com/ has algorithmic details, papers, and blogs on procedural content generation.
By Kevin Song
I'm a student at UT (that's the one in Austin) who studies things.