The simplest way to create an ocean wave is to use a variation of the sine formula. However, creating a realistic display requires a lot of waves. But using the sine formula to compute all of these waves is slow. The iFFT (or Tessendorf) wave generator uses an inverse of the FFT (Fast Fourier Transform) method to quickly generate a lot of waves.
There are now several versions of this module, which are all available on our GitHub repository and have all been updated to r180.
The WebGPU versions include:
* Ocean | - | This is the latest version that uses compute shaders and is available in the jsm directory. |
* Ocean4 | - | This is an update of the WebGl2 version and is available in the xtra_perm/jsm directory. |
The WebGL2 version is:
* Ocean3 | - | This version uses texture shaders and is available in the xtra_perm/jsm directory. |
Our initial challenge with creating this module was that 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.
Attila Schroeder created an updated version of the WebGL2 program and assisted us with converting that into the Ocean2 module. In doing so, we combined all of the Jérémy Bouny 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.
Attila then created an improved version of the program and assisted us in converting that into the Ocean3 module. For a short time, we were able to use the module with NodeMaterials which provided better materials.
Attila Schroeder then converted his WebGL2 program to WebGPU. When NodeMaterials were no longer available in WebGL2, he helped us create a GPU module (Ocean4).
Attila then created an improved WebGPU version that primarily uses compute shaders (available only in WebGPU). And, as before, helped us convert his program to create what may become the final module, the one we are using now.
In a three.js program, you import all the three.js and other modules at the beginning of the program. You use this instruction to import the Ocean4 module:
import {Ocean} from "https://PhilCrowther.github.io/Aviation/jsm/Ocean.js";
The Ocean module uses the following variables to store data.
//- Grid Square Data let GrdSiz = 2400; // Smallest Grid Square let GrdSeg = 384; // Segments (256 to 512) //- Module let waves = 0; // Where the Ocean module is stored let wav_ = { // General size: 1024, // resolution of iFFT computation gsiz: GrdSiz, // size of grid square (used to compute Normal Map) lambda: 0.9, renderer: renderer, anisotropy: renderer.getMaxAnisotropy(), // InitSpec Variables (for GrdSiz = 2400 meters) waveLength: 1500, boundaryLow: 0.0001, boundaryHigh: 9999, // Wave Spectrum 1 depth: 100, scaleHeight: 1, windSpeed: 3, windDirection: 315, fetch: 100000, spreadBlend: 1, swell: 0.198, peakEnhancement: 3.3, shortWaveFade: 0.01, fadeLimit: 0.0, // Wave Spectrum 2 d_depth: 100, d_scaleHeight: 1, d_windSpeed: 2, d_windDirection: 315, d_fetch: 300000, d_spreadBlend: 1, d_swell: 0.5, d_peakEnhancement: 3.3, d_shortWaveFade: 0.01, d_fadeLimit: 0.0, };
waves = new Ocean(wav_);
waves.update();You can create a Plane Mesh to display the Ocean waves. Here a simple example:
let WtrGeo = new THREE.PlaneGeometry(GrdSiz,GrdSiz,GrdSeg,GrdSeg); WtrGeo.rotateX(-Math.PI*0.5); // Must rotate plane to make it flat let WtrMat = new THREE.MeshStandardNodeMaterial({ colorNode: color(0x001080), // Basic Color metalness: 0.5, roughness: 0.1, positionNode: positionLocal.add(texture(waves.displacement)), // Displacement Map normalNode: normalMap(texture(waves.normMapTexture),new THREE.Vector2(1,1)) // Normal Map }); let WtrMsh = new THREE.Mesh(WtrGeo,WtrMat); scene.add(WtrMsh);
In a three.js program, you import all the three.js and other modules at the beginning of the program. You use the appropriate instruction to import the module:
import {Ocean} from "https://PhilCrowther.github.io/Aviation/xtra_perm/jsm/Ocean4.js"; // for WebGPU import {Ocean} from "https://PhilCrowther.github.io/Aviation/xtra_perm/jsm/Ocean3.js"; // for WebGL2
The module uses the following variables to store data.
//- Grid Square Data let GrdSiz = 3200; // Smallest Grid Square (2400 meters) let GrdRes = 512; // Resolution of Computation let AnmSpd = 1; // The Animation Speed (can vary with GrdSiz) //- Constants let WndSpd = 20.0; // Wind Speed let WndHdg = 0.0; // Wind Heading let Choppy = 2.0; // Wave Choppiness //- Variables let waves = 0; // Where the Ocean module is stored let wav_ = { 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 NMS: new THREE.Vector2(1,1), // Normal Map Scale (flip Y for left-handed maps) Spd: AnmSpd // The Animation Speed };
waves = new Ocean(renderer,wav_);
waves.update();You can create a Plane Mesh to display the Ocean waves. For WebGPU programs (Ocean4), you can use NodeMaterials. Here a simple example:
let WtrGeo = new THREE.PlaneGeometry(GrdSiz,GrdSiz,GrdSeg,GrdSeg); WtrGeo.rotateX(-Math.PI*0.5); // Must rotate plane to make it flat let WtrMat = new THREE.MeshStandardNodeMaterial({ colorNode: color(0x001080), // Basic Color metalness: 0.5, roughness: 0.1, positionNode: positionLocal.add(texture(waves.dispMapTexture)), // Displacement Map normalNode: normalMap(texture(waves.normMapTexture),new THREE.Vector2(1,1)) // Normal Map }); let WtrMsh = new THREE.Mesh(WtrGeo,WtrMat); scene.add(WtrMsh);For WebGPU programs (Ocean4) and WebGL2 programs (Ocean3), you can use regular Materials. Here a simple example:
let WtrGeo = new THREE.PlaneGeometry(GrdSiz,GrdSiz,GrdSeg,GrdSeg); WtrGeo.rotateX(-Math.PI*0.5); // Must rotate plane to make it flat let WtrMat = new THREE.MeshStandardMaterial({ color: 0x001080, metalness: 0.5, // 1 for max reflection (1.0) roughness: 0.2, // 0 for max reflection (0.7) normalMap: waves.normalMapFramebuffer.texture, // Normal Map // normalScale: new THREE.Vector2(2.5,2.5), // use this to increase wave contrast // Special Instructions Required for Displacement Map onBeforeCompile: shader => { shader.uniforms.dmap = {value: waves.displacementMapFramebuffer.texture}; shader.vertexShader = ` uniform sampler2D dmap; ${shader.vertexShader} ` .replace( `#include`, `#include transformed += vec3(1.0,1.0,1.0)*(texture(dmap, uv).rgb * 1.0 + 0.0); ` ); } }); let WtrMsh = new THREE.Mesh(WtrGeo,WtrMat); scene.add(WtrMsh);