Creative Tinkering


1D wave dynamics rendered on canvas

The interactive sample above is the result of researching and experimentation to model somewhat realistic wave movement and interactions. My purpose is to create an interactive 2D water surface in Unity utilizing GPU compute.

Realtime Applications and games have always struggled with rendering believeable water. There is just overwhelming amount of complexity in water interactions and everybody has a really good idea what water should look like, because it is such an integral part of our lives.

Only way to create truly realistic water would be to solve forces and movements for countless particles and then somehow render them. This is actually standard practise in offline rendering. But as one might quess, this method is not fast enough for realtime graphics 🙂


http://support.nextlimit.com/download/attachments/22217656/hym_core_splash_01.jpg?version=1&modificationDate=1414684853000&api=v2

So, as for everything in realtime rendering, water must be somehow approximately modelled to convey the essence of it.

Algorithm

There are of course many different ways to approximate wave movement. I will be describing the method that I used to produce the sample above.

The most visual property of a wave is its height. Other properties such as compression, surface tension, depth of the water, surface profile under water, etc are important for absolute realism but can be ignored, because their effects are not as visible as wave height. So only height must be stored to describe our wave.

Of course water waves do not only oscillate in spatial dimensions but also in time. This means that we also need to store the height at last frame to calculate velocities.

The algorithm itself is very simplistic;


/*
on every frame {
    calculate and apply velocities.
    diffuse heights between adjecent cells.
}
*/

Velocity
Calculating the correct velocity is crucial to get the ‘springy’ oscillation of the wave in time. This is why last frame wave height must be stored and available. Velocity is simply current cell height subtracted with last height.


float currentHeight = 1.0f;
float oldHeight = 1.6f;
float velocity = currentHeight - oldHeight;

Now that we have our velocity we can calculate a new height for given cell like this:


float newHeight = currentHeight - velocity;
newHeight *= 0.97; // to dampen the waves in time, '0.97' was arrived at by experimentation.

It is always good idea to keep equations simple and apply damping factor to physics simulations, because this code will probably be running for millions of frames so values cannot cascade to unimagineable ranges 🙂

After the new height has been calculated we must update the current and old height values:


float currentHeight = 1.0f;
float oldHeight = 1.6f;
float velocity = currentHeight - oldHeight;
float newHeight = currentHeight - velocity;
newHeight *= 0.97;

oldHeightOUT = currentHeight;
currentHeightOUT = newHeight;

Now there is wave oscillation in time on any cell that has an absolute height greater than 0. But there is no interaction between cells yet. This is where diffusion comes in.

Diffusion
Diffusion in this context is the amount that adjecent cells influence their values. This is what allows the oscillations to move across cells meaning across space.

Altough physically wave propagation is affected by many different forces and vectors, we can simply “blur” adjecent height values to get a similar plausible effect. This blur must be ‘weighted’ to allow for different characteristics of motion.

I am using a simple Linear Interpolation (Lerp) to ‘weight’ the ‘blur’.


float blendFactor = 0.99 // by experimentation
// high blendFactor might seem counterintuitive
// but it works because of how the velocities are calculated
// and we are only modifying "currenHeight" 
// "oldHeight" will remain the same and will affect velocities

// for each cell
float blurred = currentHeight;
blurred += Lerp( currentHeight, currentHeightLeft, blendFactor );
blurred += Lerp( currentHeight, currentHeightRight, blendFactor );
blurred /= 3; // simple averaging
currentHeight = blurred;

In case the waves are not propagating enough, the diffusion step can be applied multiple times to “blur” more.

This is it! This is the method driving the sample at the top of this page. Source code of the sample can be viewed of course.
source

I will be creating a 2D dynamic and interactive water surface in unity by extrapolating this example, so stay tuned for that.

Leave a Reply

Your email address will not be published. Required fields are marked *