In this tutorial:

- creating and rendering water ripples

- optimized image generation

- JPEG decoding component for Silverlight

Source code for this sample

Algorithm

The algorithm is based on Hugo Elias’ 2D water tutorial.

On each render step we have the state of the water for the current frame and the previous frame.

The state is stored in two 2-dimensional arrays of integers that are as big as the image.

For each pixel position of the image we store the height of the water (or wave) in that position. 0 means “sea level”. Larger than 0 means that we have a raised wave, less than zero means that we have wave below “sea level”. We need information for both raised and low waves in order to be able to combine them.

On each render step we use data from the current frame (Buffer2) and the previous frame (Buffer1) and   write the results into Buffer1.

  damping = some non-integer between 0 and 1 (I use 0.94)

  for every non-edge element:

  loop

          Buffer2[x, y] = (Buffer1[x-1,y]

                           Buffer1[x+1,y]

                           Buffer1[x,y+1]

                           Buffer1[x,y-1]) / 2 - Buffer2[x,y]

 

          Buffer2[x,y] = Buffer2[x,y] * damping

      end loop

 

      Swap the buffers

      Display Buffer1

  end loop

 

You can go ahead and look at Hugo’s explanation about why does this work, or continue reading here.

Because the 2 buffers contain consecutive steps for the water, we can get the water velocity at a given location [x, y] by subtracting: Buffer2[x, y] – Buffer1[x, y]

Also we want the waves to spread out, so we smooth the buffers on every frame:

     Smoothed[x,y] = (Buffer1[x-1, y] +

                      Buffer1[x+1, y] +

                      Buffer1[x, y-1] +

                      Buffer1[x, y+1]) / 4

 

In the actual algorithm we multiply the smoothed value by 2 in order to decrease the effect of velocity.

And last, the waves lose energy as they travel:

Buffer2[x,y] = Buffer2[x,y] * damping

 

Rendering the Water

The render buffer contains heights of the water in each pixel. We’ll render it using shading and refraction.

The shading variable below determines the direction and intensity of the light. For example, if you set shading = xoffset, you’ll get light straight from the left. I decided to set the light at the bottom-right part of the screen.

For every non-edge pixel in the buffer

     Xoffset = buffer[x-1, y] – buffer[x+1, y]

     Yoffset = buffer[x, y-1] – buffer[x, y+1]

 

     shading = (xoffset - yoffset) / 2

 

// note: x+xoffset and y+yoffset do not wrap around the texture

t = texture[x+Xoffset, y+Yoffset]

 

resultColor = t + Shading

// make sure the color is within limits

resultColor = SaturateTo255Max(resultColor)

 

plot pixel at (x,y) with color resultColor

End loop

 

Creating Drops/Splashes

I made a simple function to create a circular splash, although you can create different splashes to simulate dropping irregular shapes into the water or other motion effects (e.g. star, line or use the letters of your name).

The function creates a splash given its radius at location (cx, cy). The splash begins below water level and rises above at the end.

Splash(cx, cy, radius):

for each y from (cy - radius) to (cy + radius)

    for each x from (cx - radius) to (cx + radius)

        dist = distance from point (x,y) to (cx, cy)

        if (dist < radius) // if within splash circle

            buffer1[x, y] = 255 - (512 * 1 - dist / radius)

        end if

    end loop

end loop

 

The Silverlight Side

The rendering loop is called every 60ms and does this:

1. Add a random splash (rain drop) on the image

2. Calculate the next frame to render

3. Display the next frame

There are 3 components used to render each frame:

Renderer has the “raw” buffers of integers containing wave height for each pixel in the frame. On each frame the renderer mixes its raw buffer with the background image (decoded using FluxJpeg.Core.Image component) and outputs each it to a dynamic image generation surface (EditableImage).

Links

Original 2D water algorithm (Hugo Elias)

This is the algorithm used as a base for the sample.

Dynamic image generation (Joe Stegman)

Optimized dynamic image generation based on Joe's (Nokola)  

I’m using optimized version of Joe’s algorithm to render the effect on screen.

Silverlight JPEG encoder/decoder in C# - FJCore on fluxcapacity.net

The binary is used to decode the JPEG image in Silverlight.