Recreating Noita's Sand Simulation in C and OpenGL | Game Engineering

308,659
78
Published 2020-04-23
Exploring and attempting to recreate Noita's "Falling-Sand" Simulation from scratch using C and OpenGL.

Be sure to like and subscribe, you cool people. That way I can continue to make more vids
like this.

NOTE: There's a typo @2:07. I wrote "bytes" when it should be "bits" for the gs_vec2, color_t, and b32 member variables for the particle struct. So the overall size should be drastically different (only 24 bytes in total). Sorry for the confusion!

00:00 - Introduction
00:33 - Gunslinger
00:58 - Research/Resources
01:13 - Cellular Automata
01:52 - Sand Algorithm
03:15 - Water
05:25 - Wood/Walls
05:53 - Fire
06:32 - Gunpowder/Salt/Lava/Oil/Acid
07:37 - Polish/UI/Drag-Drop Images
08:56 - Final Sand Sim Presentation / Exploding Pictures

Project source:
github.com/GameEngineering/EP01_SandSim

Gunslinger:
github.com/MrFrenik/gunslinger/

Get Noita:
store.steampowered.com/app/881100/Noita/

Get Powder Toy:
powdertoy.co.uk/

Music:
Blue Dot Sessions - Lakdeside Path: www.sessions.blue/?fwp_sessions=duck-lake

Resources Used:
Nolla Games GDC:    • Exploring the Tech and Design of Noita  
Noita Gameplay Showcase -    • Noita gameplay showcase  
Noita Technical Explanation -    • Noita Gameplay - Explaining what ever...  
Game Of Life:    • An Introduction to Conway's The Game ...  
Game Of LIfe:    • epic conway's game of life  

Stuff To Read:
medium.com/starts-with-a-bang/it-from-bit-is-the-u…
80.lv/articles/noita-a-game-based-on-falling-sand-…
maxbittker.com/making-sandspiel

Special Thanks:
Guilherme Teres from Uniday Studio:    / @unidaystudio  

Everyone in my Discord channel. Thanks for hanging around and being awesome.

All Comments (21)
  • @johnjackson9767
    Edit: Wall of Text Warning - At 02:07, the size of the b32, gs_vec2, and color_t member variables should only be represented as "bits", not "bytes". Therefore the overall size is not the grossly exaggerated amount shown here, but 24 bytes. Thanks to @Nyzcere for pointing that out! There's always a mistake you miss! - I've gotten a concern about the memory storage for the particle data. Something else I'm regretting not going into more detail. To clarify, the texture size is not the screen's full resolution in this implementation. It's 629 x 424 for a total of ~266.7k particles. Total memory storage for this buffer is therefore ~6.4MB. I think that's acceptable for a small game world like this. The size of this particle data can definitely be lowered though! If you pack the life time and color information into 4 bytes (24 bits for the life and 8 bits for a color lookup index into a shared table), you could drastically reduce the size. However, the goal of the vid was to show off a concept and demonstrate an implementation - not to achieve extreme efficiency. - It's also been requested to explain a bit more about how the rendering occurs, since that seems to be a large concern for people who might attempt something like this. For the particles' updates, they really are that simple, naive and brute force. It seems odd, but the resolution of my world shown is roughly 260k particles, and it easily hits a 16ms frame time. However as you scale the world, this won't cut it. So you have some options. You could split it up into sizeable chunks, say 512x512 (like Noita does), and then partition those chunks into active/inactive buckets and only update the active particles. To further speed this up, you could multi-thread the simulation. Now, here's the catch: Noita and my simulations are both single-buffered and run on the CPU. So that means multi-threading is...challenging. You have to be careful at the boundaries of your chunks, since there's a large chance you'll have a lot of data-races at boundary walls. Noita handles this by splitting its update frame into 4 sections - it creates groups of chunks to update in a "checker like" pattern, so it maintains a large separation area between chunks. This works well enough in practice so that a particle has a large enough area it can travel in a single frame between neighboring chunks without having to worry about getting accessed twice in different threads. You could also move the entire sim to the GPU, but that's a different beast altogether. :) The rendering is done in a single draw call. I keep a 2d array of 32-bit rgba color data which maps directly to the pixel data. There is also a GPU texture that has the exact same dimensions as this CPU texture. As I update pixels, I change the color data as well. Then, when I'm done with the sim in the frame, I push the entire color buffer and copy its data into the GPU texture. Therefore rendering the scene is just a single draw call to present the buffer to screen. I also have 3 other calls for post processing. All in all, the entire scene is rendered in 5 calls: - one to render the simulation to an offscreen target - three for post processing (bright filter, bloom, and composite) - one for final presentation to back buffer - Salt isn't less dense than water, I misspoke. But, it looks cool. That's what matters, right? - @championchap brought up a point that I sneakily didn't address in the video (I'm starting to realize that I need to address everything much more fully than I did). The "simple" sand falling algorithm works great as a base, and adding forces, like gravity and velocity, do work to add some variation for a more "realistic" behavior set. However, if the velocity fails to move the particle in any way, the simulation falls back to this simple algorithm and it can look "out of place" with how uniformly it falls. Think about it - we specifically tell the sand to follow those rules in a fall through pattern - look down, then look down and left, then down and right. What you can do to force some variation is to alternate how to iterate through the columns of your data. Easiest way I've found is to add a frame counter and then on even frames, you iterate left-to-right. For odd frames, right-to-left. This adds enough variation to that falling pattern that it greatly helps out with this issue of unwanted uniformity. - Also, thanks for all the kind words and motivation, everyone. Subscribe and be on the lookout for the next one. I've already started working on it 😀
  • @OussamaBarkouki
    I just found the 3blue1brown of computer graphics, and I love it.
  • @Miziziziz
    Wow really high quality vid, please make more like this, subbed
  • @Volvith
    "Noita" *POLYMORPHINE TRAUMATIC STRESS DISORDER FLASHBACKS*
  • @koenbrink
    This is one of those videos that could last days and I would still be sad when it ended
  • @Fezezen
    I think I found my next little programming challenge for myself.
  • @skaruts
    On a loosely related note, it always amuses me when Game Of Life is said to have 4 rules. It's really just 2 rules: 1- living cell dies if not next to 2 or 3 alive neighbors 2- dead cell is revived if next to 3 alive neighbors
  • @UnidayStudio
    What an amazing video! I learned a lot here, thank you! I can't wait for the next one!
  • 4:25 Some tips: If you notice, the ripples and interactions in the water are a bit similar so you can set the delay time for each particle and random time differently. And we have realistic water!
  • @Ganerrr
    what happens when two particles want to move in the same place? this is something i've always had problems with when creating simulations like this, as top leftness always gets preference
  • @trulyinfamous
    I really love modular systems. Being able to set up a system, iterate on it all you want, and add new things based on that system is cool.
  • @5daydreams
    Okay YOU REALLY deserve more views. This is high quality content, right there
  • @TomDale
    Awesome video. You’re like the Bob Ross of sand simulators. Keep it coming!
  • @rje613
    This maybe one of my favorite coding videos of all time. Very precise and very informative. Good job dude
  • @dreamhollow
    Things like this are exactly why I wanted to get into game design in the first place. I am utterly fascinated by game physics, especially convincing fluid simulations.
  • @nobo_dev9526
    Really well explained, and a high production value. Hoping to see more :) P.s. at 2:07 you accidentally switched from counting in bytes to counting in bits, the total should be much smaller
  • @booseloose8992
    I attempted this a while back. I can't believe I didn't try to travel to each cell along a path to handle velocity. I guess I thought it would be too taxing on my computer to run smoothly but, looking at your results, it seems to work well enough. Great video, I really enjoyed it!
  • @LBandy
    Insanely informative and visually stunning. Thanks a lot for sharing!