I've been playing around with cellular automata for procedural generation in WebGL. Here's an algorithm for rendering dendriform growth. I was aiming for rivers, but it came out like neurons.
It's built atop a diffusion-limited aggregation cellular automata. Algorithm sketch:
- Each cell looks at the 4 nearest neighbors on the grid. We also check the 4 corners ("far" neighbors) to avoid creating loops.
- Random numbers are provided by a separate noise texture
- State is stored in the channels of an RGBA texture. Each [0,1] channel maps to a [0,255] byte value
- The texture is seeded with at least one "active" pixel (existing water/lake/cell body). The seed is set to active (green=1.0)
- Green channel runs a a diffusion limited aggregation:
- If exactly one nearby pixel is active, then we have a 5% of becoming active.
- Avoid creating loops: don't activate a pixel if more than one "far" neighbor is already active.
- Because pixels act in parallel and at random, sometimes two pixels turn on in the same iteration, creating a loop. Detect and remove these after-the-fact, if they happen.
- Keep track of distance from the "seed" region in blue channel
- Seeds are initialized with blue channel = 255
- Newly activated cells get blue-1
- This counts down to zero, at which point expansion stops
- The texture is also initialized with various water sources / "synapses" scattered about it
- This is stored in the alpha channel
- If an active cell hits a source, it becomes a "river"
- If a cell is active, and an adjacent cell is a "river", and is also further away from the source (check distance in blue channel), then this cell also becomes a "river"
- A separate shader checks which cells are rivers, and generates tile ID values which are then passed to a tile shader.
Here's the the shader that does the work:
void main() { vec2 dx = vec2(1.0/game_size.x,0.0); vec2 dy = vec2(0.0,1.0/game_size.y); vec2 p = gl_FragCoord.xy/game_size; // Get neighborhood vec4 s00 = texture2D(game_state,p-dy-dx); vec4 s01 = texture2D(game_state,p-dy); vec4 s02 = texture2D(game_state,p-dy+dx); vec4 s10 = texture2D(game_state,p-dx); vec4 s11 = texture2D(game_state,p); vec4 s12 = texture2D(game_state,p+dx); vec4 s20 = texture2D(game_state,p+dy-dx); vec4 s21 = texture2D(game_state,p+dy); vec4 s22 = texture2D(game_state,p+dy+dx); // Get noise vec4 n = texture2D(noise,p); // Cells persist bool cell = s11.g==1.0; // How many adjacent? int near = int(s01.g+s10.g+s12.g+s21.g); int far = int(s00.g+s02.g+s20.g+s22.g); // Cells randomly added only if they would not clash float next; if (cell) next = 1.0; else if (near==1&&far<2) { next = float(n.g<0.05); } // Cull overcrowding if (cell&&s11.b>0.0&&near+far>=7&&n.b<0.05) next=0.0; // If newly-created cell // Take max of immediate neighbors minus one float distance = (next>0.5&&!cell)? max(max(s01.b,s10.b),max(s12.b,s21.b))+1.0/256.0 : s11.b; // if a cell nearby is river, and is further away, become river if (cell && s01.a>0.0 && s01.b>distance) s11.a = s01.a; if (cell && s10.a>0.0 && s10.b>distance) s11.a = s10.a; if (cell && s21.a>0.0 && s21.b>distance) s11.a = s21.a; if (cell && s12.a>0.0 && s12.b>distance) s11.a = s12.a; gl_FragColor = vec4(0.0,next,distance,s11.a); }
https://michaelerule.github.io/webgpgpu/games/lesson_12_dendrites_variant_3.html
No comments:
Post a Comment