Dunes

Creating Procedural Dunes for Dune: Part One (2021) #

video: https://vimeo.com/288103307

When I worked on Dune: Part One (2021), I faced the challenging task of procedurally creating dunes. Luckily, I found this amazing blog, which provided a lot of inspiration, particularly with its interesting implementation of the Abelian sandpile model. Unfortunately, the original code was written in VEX and was too slow for our needs. After some experimentation, I created an OpenCL version, which significantly improved the performance and produced impressive results. I used HeightFields with an openCL node.

Learning resources

The code I ended up with can be found here:

Some interesting bits: To determine how sand grains redistribute to neighboring cells when a cell topples, we need a way to identify those neighbors. The following arrays help achieve this:

// roadmap x neighbour
constant int xo[] = {-1, 0, 1,
                    -1, 0, 1,
                    -1, 0, 1 };
// roadmap y neighbour
constant int yo[] = { 1, 1, 1,
                    0, 0, 0,
                    -1,-1,-1 };

Together, they describe the coordinates of the 8 surrounding neighbors as well as the cell itself.

This loop iterates over the 9 possible positions (8 neighbors plus the cell itself). For each iteration:

  • A seed is generated using the current iteration and cell index.
  • randval is computed using the random function to get a random neighbor index.
  • The difference in height between the current cell and the selected neighbor is checked against a threshold.
  • If the difference meets the threshold, the direction for redistribution is set to the random neighbor, and the height is adjusted by subtracting the mass times a mask value.
    for(int i=0; i<9; i++)
    {
        float seed = iter[0] + idx;
        uint randval = random(seed, idx);
        
        int nptidx = n[randval];
        
        int dif = (height[idx] - height[nptidx]) >= treshold;
        if (dif)
        {
            dir[idx] = randval;
            height[idx] -= mass * mask[idx];
            break;
        }
    }

A random function to find where to redistribute grains among those 8 directions

// random function                     
static uint random(float seed, int idx) {
    uint s = *(uint*)(&seed);
    s ^= idx << 3;
    s *= 179424691;
    s ^= s << 13 | s >> (32-13);
    s ^= s >> 17 | s << (32-17);
    s ^= s << 23;
    s *= 179424691;
    s = s % 9;
    return s;
}

You can check out the final result in my reel and the breakdown from dneg