# Wave simulations on ShaderToy

**UPDATE:** A new wave sim shader has been since published that explicitly discretizes the wave equation: Simple Discrete Waves from user sibaku, which is a great reference implementation. In addition the author links to this useful reference.

Support for multiple render buffers was recently added to ShaderToy, and one of the cool applications that emerged is solving the wave equation on a water surface. I experimented with this after seeing tomkh’s shader titled Wave Propagation Effect. This was based in turn on an implementation detailed in a (fantastically old-school!) web page from Hugo Elias here. Elias in turn mentions getting source code from somewhere else but doesn’t attribute the source.

It took me some time to understand the optimised code and relate it to a simulation of the 2D wave equation, a 2nd order differential equation which gives an acceleration at each point on the surface. There is no clear integration of the acceleration onto surface velocities, the only data stored are the surface heights. I made this post to highlight the following subtleties:

- The velocity is actually computed from surface heights at previous frames. Since the state in the buffer is persistent, we can get at the previous frame wave heights and current frame wave heights, and the difference between these gives the velocity. This is similar to Position Based Dynamics (PBD).
- The time step is fixed. Any integration (i.e. adding the acceleration from the spatial Laplacian), or adding a damping force, should be multiplied by time. Not doing so means the simulation progress with frame count instead of real time, so the simulation runs slower at lower frame rates. On ShaderToy, using multiple buffers allows multiple simulation steps per frame. Like the original from tomkh, Milky uses 2 buffers, so two simulation steps per frame. I did try a version with 1 buffer but it felt really sluggish (tomkh has a 1-buffer of his shader running here, compare the wave speed to his 2-buffer version linked above).
- The wave speed is fixed/baked in. This takes another important factor out of the simulation, which could e.g. be used to modify viscosity.

The stability of the sim can be understood using the simple Courant–Friedrichs–Lewy (CFL) condition. This gives an elegant bound on the simulation params for stability. Every simulation step the code is looking at the neighbouring water heights (immediate neighbour texels only). This effectively places a speed limit on how fast information can travel – if each surface height update only looks at immediate neighbours, how could a wave travel further than one grid unit per frame? This is exactly the CFL condition – it is a triangle relationship between the wave speed , time step and grid resolution which ensures the distance travelled by a wave in a frame is less than 1 grid unit, and can be written as follows in our case:

If faster waves are desired (larger ), one must reduce the timestep (do more simulation steps per frame), or increase the grid spacing (less fidelity in simulation).

These factors are all baked down in the current shaders to something unconditionally stable. This is all well and good until one desires to have the simulation run in real-time, or to set wave speed, or to change simulation grid resolution without (significantly) affecting dynamics. I thought it would be useful to pull these factors out and did so in the comments at the bottom of the Buf A&B shaders of my Milky shader here, but stuck with the optimised version for the speed and tight code.

## Leave a Comment