Gerstner Water with Sky Box

Threejs Boilerplate : https://github.com/Sean-Bradley/Three.js-TypeScript-Boilerplate Threejs Course : https://sbcode.net/threejs/ Discount Coupons : https://sbcode.net/coupons#threejs

by seanwasere

HTML

<!--
Threejs Boilerplate : https://github.com/Sean-Bradley/Three.js-TypeScript-Boilerplate
Threejs Course : https://sbcode.net/threejs/
Discount Coupons : https://sbcode.net/coupons#threejs
-->

<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>

<script type="importmap">
  {
		"imports": {
			"three": "https://cdn.skypack.dev/three@0.144.0/build/three.module",
      "three/": "https://cdn.skypack.dev/three@0.144.0/",      
      "dat.gui": "https://cdn.skypack.dev/dat.gui"
		}
	}
</script>

<script id="vertexShader" type="x-shader/x-vertex">
  uniform mat4 textureMatrix;
            uniform float time;

            varying vec4 mirrorCoord;
            varying vec4 worldPosition;

            #include <common>
            #include <fog_pars_vertex>
            #include <shadowmap_pars_vertex>
            #include <logdepthbuf_pars_vertex>

            uniform vec4 waveA;
            uniform vec4 waveB;
            uniform vec4 waveC;

            vec3 GerstnerWave (vec4 wave, vec3 p) {
            	float steepness = wave.z;
            	float wavelength = wave.w;
            	float k = 2.0 * PI / wavelength;
            	float c = sqrt(9.8 / k);
            	vec2 d = normalize(wave.xy);
            	float f = k * (dot(d, p.xy) - c * time);
            	float a = steepness / k;

            	return vec3(
            		d.x * (a * cos(f)),
            		d.y * (a * cos(f)),
            		a * sin(f)
            	);
            }

            void main() {
            	mirrorCoord = modelMatrix * vec4( position, 1.0 );
            	worldPosition = mirrorCoord.xyzw;
            	mirrorCoord = textureMatrix * mirrorCoord;

            	vec3 p = position.xyz;
            	p += GerstnerWave(waveA, position.xyz);
            	p += GerstnerWave(waveB, position.xyz);
            	p += GerstnerWave(waveC, position.xyz);
           ...

CSS

body {
  overflow: hidden;
  margin: 0px;
}

JavaScript

import * as THREE from 'three';
import Stats from 'three/examples/jsm/libs/stats.module.js';
import {
  GUI
} from 'dat.gui';
import {
  OrbitControls
} from 'three/examples/jsm/controls/OrbitControls';
import {
  Water
} from 'three/examples/jsm/objects/Water';

let container, stats;
let camera, scene, renderer;
let controls, water;
let clock, delta, boxes, numBoxes;

const waves = {
  A: {
    direction: 0,
    steepness: 0.4,
    wavelength: 60
  },
  B: {
    direction: 30,
    steepness: 0.4,
    wavelength: 30
  },
  C: {
    direction: 60,
    steepness: 0.4,
    wavelength: 15
  },
};

function getWaveInfo(x, z, time) {

  const pos = new THREE.Vector3();
  const tangent = new THREE.Vector3(1, 0, 0);
  const binormal = new THREE.Vector3(0, 0, 1);
  Object.keys(waves).forEach((wave) => {

    const w = waves[wave];
    const k = (Math.PI * 2) / w.wavelength;
    const c = Math.sqrt(9.8 / k);
    const d = new THREE.Vector2(
      Math.sin((w.direction * Math.PI) / 180),
      -Math.cos((w.direction * Math.PI) / 180)
    );
    const f = k * (d.dot(new THREE.Vector2(x, z)) - c * time);
    const a = w.steepness / k;

    pos.x += d.y * (a * Math.cos(f));
    pos.y += a * Math.sin(f);
    pos.z += d.x * (a * Math.cos(f));

    tangent.x += -d.x * d.x * (w.steepness * Math.sin(f));
    tangent.y += d.x * (w.steepness * Math.cos(f));
    tangent.z += -d.x * d.y * (w.steepness * Math.sin(f));

    binormal.x += -d.x * d.y * (w.steepness * Math.sin(f));
    binormal.y += d.y * (w.steepness * Math.cos(f));
    binormal.z += -d.y * d.y * (w.steepness * Math.sin(f));

  });

  const normal = binormal.cross(tangent).normalize();

  return {
    position: pos,
    normal: normal
  };

}

function updateBoxes(delta) {

  const t = water.material.uniforms['time'].value;
  boxes.forEach((b) => {

    const waveInfo = getWaveInfo(b.position.x, b.position.z, t);
    b.position.y = waveInfo.position.y;
    const quat = new THREE.Quaternion().setFromEuler(
      new...