20201015

WebGL shader rules for rendering dendriform growth

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.

[view in browser]

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);
}
rr



https://michaelerule.github.io/webgpgpu/games/lesson_12_dendrites_variant_3.html

No comments:

Post a Comment