Having read through a few of the articles, papers and presentations linked in the Introduction's References, we quickly discovered that this was going to be a very involved effort. Rather than dive directly into the most advanced techniques we started by trying to get the effect as simply as possible.
From our days in the Flash world, we'd spent a fair amount of time playing with procedural texture generation using Perlin noise (Video). Obviously our initial reaction was to generate a Perlin noise texture, animate it and use the values to displace the vertices on the ocean plane.
We're using Unity as our development platform and while it does have a Perlin Noise function built in, it only supports two dimensions. This works well for creating a noise texture but we also need to animate the texture over time, so we need a third dimension. This means we'll need to find a library or implement one ourselves.
As we started to do our research we thought ahead to what we needed to support. Whatever we chose needed to be fast so we could run it in real time and it needed to scale so we could have an infinite ocean that we could travel forever on.
The cool thing about noise functions is that by using higher dimensions, you can create a tileable image. This would allow us to generate one image, reuse it and still be able to span the entire world. Offsetting two layers of these images at different speeds would effectively allow us to have an infinite plane without noticeable repetition.
This lead us to Simplex Noise instead of Perlin Noise as it performs better at the higher dimension calculations.
Simplex noise on its own isn't very interesting. This is because you need to layer multiple versions of the noise at different octaves and persistence in order to generate an interesting image. A great explanation of this can be found here. It uses Perlin noise instead of Simplex but the theory is the same. The process of layering everything together is called Fractional Brownian Motion or FBM.
Now that we are able to generate the textures, we can look at applying them to the ocean plane in game and displace the vertices. This is done in the shader by performing a texture lookup in the vertex shader, reading the value from the texture and displacing the vertex in the y direction by that value. Values less than 0.5 greyscale drop below the base height line and values greater grow above.
We can then animate the texture over time by sampling the FBM in the z dimension to give a more "watery" type of animation.
And finally we can animate across the ocean plane by offsetting in the x and y dimensions to simulate the flow of the water.
We now have a somewhat passable animated ocean patch that we can play with. We're getting something that physically resembles water to a degree although it looks a bit more cloud like than waves. The next test is to see if we'll be able to render it nicely so that it looks like water. In order to do that we're going to need more information that describes the surface. We know how high each point on the surface is but we'll also need to know what the normal is at a given point so we know how light will interact with it.
Fortunately there is a filter called the Sobel Filter that we can apply to the generated image that does exactly that.
When we run this on our already animated setup, we get something that displays the normals of our water.
At this point we now have enough information to be able to do a decent job at rendering the look of the water in addition to its physical shape. Unfortunately, there are a few issues with this approach.
The performance isn't bad, as we can get everything real time, but this is just one patch without anything else going on in the scene. We haven't included any rendering to make it look pretty, there's no gameplay or anything else and we're already using 26ms a frame! We can definitely optimize but we're not expecting to get down to the 2 to 4 ms goal and still be able to hit all the objectives.
Speaking of which, while the FBM of Simplex Noise looks watery, it's not really what an ocean looks like in terms of waves and having sharp crests. It's ok to make the choice on something less performant if you're getting the right effect out of it, but for this approximation it doesn't feel worth it.
The attempt itself was interesting and we were able to create a good noise library in the process but in order to achieve our goal, we're going to need to change our approach and read a few more white papers.