OCEAN MODULE v2.0
In General
Four adjacent squares of waves. Click here for a display that also shows fps and displacement and normal maps.

The simplest way to create an single ocean wave is to use a variation of the sine formula. However, creating a realistic display requires a lot of waves. The iFFT (or Tessendorf) wave generator uses an inverse of the FFT (Fast Fourier Transform) method to quickly generate a lot of waves.

Unfortunately, we were unable to find a current example of a three.js iFFT wave generator. The best example we found was a three.js wave generator created in 2015 by Jérémy Bouny, based on a 2014 js version created by David Li which was adapted to three.js by Aleksandr Albert.

We have updated and revised that wave generator to create this module. We combined all of the programs and shaders into a single program. We eliminated the portion of the program and the shaders used to create the final display. Instead, the program simply creates displacement and normal maps which you can use with standard three.js materials. We eliminated the program dealing with reflections since you can create reflections using three.js materials.

  Waves from the movie "Dunkirk" compared to waves generated by this module.
Using this Module in Your Program

This is Version2 of the program. For Version 1, see here.

Load Module into Program

You can insert these kinds of instructions at the beginning of your program to load the module:

   <script type="importmap">
      {
         "imports": {
            "three": "../3js/r154/build/three.module.js",
            "three/addons/": "../3js/r154/examples/jsm/"
         }
      }
   </script>

   <script type="module">
      import * as THREE from "three";
      import {Ocean} from "three/addons/air/Ocean2.js";	// [v2: file name changed]
      [etc]

The "3js/r154 directory" is the place on our webpage where we store the three.js programs for revision 152. To insure that the three.js programs load properly, we use the same directory structure that three.js uses with their examples. The "build" directory contains the standard three.module.js program. The "examples/jsm" directory contains various standard three.js modules. Within "examples/jsm" we created a custom directory "air" which holds Ocean.js and our other custom modules.

Variables

The Ocean module uses the following variable to store data.

    let Ocean1 = 0;
    let wav1_ = {
        // Sources
        Res: GrdRes,	// Resolution - segments per square (default = 512)
        Siz: GrdSiz,	// Size of Smallest Square = default = 3200m = 2 miles
        WSp: WndSpd,	// Wind Speed
        WHd: WndHdg,	// Wind Heading
        Chp: Choppy,	// default = 1
        // Results
        Dsp: 0,		// Displacement Map Address
        Nrm: 0,		// Normal Map Address
    };
    let wavSp1 = 1.0;	// Animation Speed Adjustment [v2: moved from wav_ and renamed]
    let wavTm1 = 0;	// Elapsed Time (as Adjusted) [v2: moved from wav_ and renamed]

You can use whatever name you like for ocean and wav_, as long as you correctly reference the variables when calling Ocean. In this case, we are using the suffix "1" in preparation for someday loading more than one instance.

Initialization

The Ocean module is initialized using this command:

    Ocean1 = new Ocean(renderer, wav1_);		[v2: no longer references camera or scene]

where renderer, camera and scene are the names of the variables for your renderer, camera and scene.

When you later run ocean.render, the resulting displacement and normal maps will be saved in wav_.Dsp and wav_.Nrm. You can link these to your material, using something like this:

   material = new THREE.MeshPhysicalMaterial({
      color: 0x102080,	// Water color
      normalMap: wav_.Nrm,	// Normal Map
      displacementMap: wav_.Dsp,	// Displacement Map
      envMap: envMap,	// (Optional)
      normalScale: new THREE.Vector2(2.5,2.5),
      metalness: 1.0,		// 1 for max reflection
      roughness: 0.7,		// 0 for max reflection
      reflectivity: 0.5,	// 1 for max reflection
      envMapIntensity: 5,	// max reflection suggested = 5
      premultipliedAlpha: true,
   });	

You can use any three.js material that accepts displacement and normal maps.

If, like us, you display your waves using a field of adjacent grids, you can create a second texture for the more distant grids that includes only the normal map. You will want to displace those outer grids downard enough to avoid apparent gaps. Here is a program that uses that method. As you can see, the ocean surface appears seamless.

Animating the Waves

To animate the waves you need to compute the adjusted time variable and render the Ocean by adding the following commands to your render loop:

    wavTm1 = difTim*wavSp1 || 0.0;	[v2: variable moved from wav_ and renamed]
    Ocean1.render(wavTm1);		[v2: only reference only time variable]
A Simple Timer

Here is a simple timer you can use to compute wavTm1.

Initialize the clock in the variables section with this command and general variables:

    let clock = new THREE.Clock();
    let oldTim, nowTim, difTim = 0;

In your render loop, insert the following:

    nowTim = clock.getElapsedTime();
    difTim = nowTim-oldTim;
    oldTim = nowTim;
    wavTm1 = difTim*wavSp1 || 0.0;
Limitations

The good news about sine wave generators is that you can tile the results seamlessly. The bad news is that this tiling starts looking bad as tile size decreases (or as altitude increases). Thus, this wave generator will generate great results, but only within a certain range.

The standard method of solving this problem is to create "cascading" maps. Since the iFFT method creates waves using a specified range of frequencies, you can easily split the wave frequencies into higher and lower frequencies. You then use only the higher (shorter) frequencies to create the standard displacement and normal maps. And you use the lower (longer) frequency waves to create larger displacement and normal maps. And then you combine the two. We have not made this improvement to the module.

A final limitation is that, although Oceans.js generates a 3-way displacement map, Three.js apparently can only handle vertical displacements. This limitation may be a problem only in certain conditions, such as those involving close-up views with high windspeed and choppiness settings. You should be able to determine the impact by comparing the your results to the waves in David Li's model, which uses 3-way displacement.

The Zip File
Here is a zip file that includes the following:

  • Ocean2.js
  •       the Ocean class module.
  • iFFT_LAB2_mod.html
  •       a program that uses the Ocean module.
  • iFFT_LAB2.html
  •       a version of the above that you can run locally.