<script type="importmap">
	{
		"imports": {
			"three": "https://threejs.org/build/three.module.js",
			"three/": "https://threejs.org/"
		}
	}
</script>
body{
	overflow: hidden;
	margin: 0;
}
import {
	Clock,
	Color,
	DirectionalLight,
	LinearFilter,
	LinearMipMapLinearFilter,
	LoadingManager,
	MathUtils,
	Mesh,
	MeshStandardMaterial,
	Object3D,
	PerspectiveCamera,
	PlaneGeometry,
	RepeatWrapping,
	RGBAFormat,
	Scene,
	ShaderMaterial,
	sRGBEncoding,
	TextureLoader,
	Vector2,
	Vector3,
	WebGLRenderer
} from "three";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls.js";

/* = Variables ===============================================================*/
// Common
let GrdSiz = 804.67;				// Size of Grid in meters
	GrdSiz = 1000;
let GrdRCs = 2;
let WtrCol = 0x1040f0;				// Water (Tropical)
	WtrCol = 0x081080;				// Water (Navy)
// Animated
let segNum = 500;					// Segments per Grid (fewer = sharper waves)
let GrdPtr = [0];
let WavMZV = [0];
let WavMXV = [0];
let geoWav, matWav;
// Textures
let NrmSrc = ["https://threejs.org/examples/textures/waternormals.jpg"];
let WtrNrm = 0;						// Pointer to Water Normal Map
let MapSrc = ["https://threejs.org/examples/textures/water.jpg"]; // ### Address of Perlin or Displacement Map
let WtrMap;           // ### Pointer to Perlin or Displacement Map
let WtrRep = 1; 					// Wrap Reps
let LodFlg = 0;						// Load Flag
// Uniform
let gu = {							// Uniform
		time: {value: 0},
		grid: {value: GrdSiz},
    image: {value: WtrMap}, // ###
	};

/* = Basic Values ============================================================*/
// Display
let	scene = new Scene();
	scene.background = new Color(0x1732c1);
let	renderer = new WebGLRenderer({antialias: true});
	renderer.setPixelRatio(window.devicePixelRatio);
	renderer.setSize(window.innerWidth, window.innerHeight);
	renderer.outputEncoding = sRGBEncoding;
	document.body.appendChild(renderer.domElement);
	window.addEventListener("resize", onWindowResize, false);
// Light
let dirLight = new DirectionalLight(0xffffff,1);
//	dirLight.position.set(0,2000,-1000);	// Default position
	dirLight.position.set(0,2000,0);	// High Noon
	scene.add(dirLight);
// Camera
let	camera = new PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 15000);
let	controls = new OrbitControls(camera, renderer.domElement);
	camera.position.set(0,100,200);
	controls.update();
// Clock
let clock = new Clock();
let	etime;
// Loading Manager
	// Create a loading manager to set RESOURCES_LOADED when appropriate.
	// Pass loadingManager to all resource loaders.
let loadingManager = new LoadingManager();
let RESOURCES_LOADED = false;
	loadingManager.onLoad = function(){
		console.log("loaded all resources");
		RESOURCES_LOADED = true;
		initAll();
	};
let txtrLoader = new TextureLoader(loadingManager);

/* = Main Program ============================================================*/
	loadAll();
	rendAll();

/* 0 Load All ================================================================*/

function loadAll() {	
	// Normal Map
	txtrLoader.load(NrmSrc, function(texture) {
		texture.format = RGBAFormat;
		texture.magFilter = LinearFilter;
		texture.minFilter = LinearMipMapLinearFilter;
		texture.generateMipmaps = true;
		texture.wrapS = texture.wrapT = RepeatWrapping;
		texture.offset.set(0,0);
		texture.repeat.set(WtrRep,WtrRep);
		texture.needsUpdate = true
		WtrNrm = texture;
	});
  // ### Perlin or Displacement Map
  	txtrLoader.load(MapSrc, function(texture) {
		texture.format = RGBAFormat;
		texture.magFilter = LinearFilter;
		texture.minFilter = LinearMipMapLinearFilter;
		texture.generateMipmaps = true;
		texture.wrapS = texture.wrapT = RepeatWrapping;
		texture.offset.set(0,0);
		texture.repeat.set(WtrRep,WtrRep);
		texture.needsUpdate = true
		gu.image.value = texture; // assing to uniform's value
	});
}

/* 1 Initialize ==============================================================*/
function initAll() {
	let n, zx;
/* = Main Program ============================================================*/
	// Planes with Extended Material -----------------------------------------
	geoWav = new PlaneGeometry(GrdSiz,GrdSiz,segNum,segNum);
	geoWav.rotateX(-Math.PI * 0.5);
	matWav = new MeshStandardMaterial({
//		normalMap: WtrNrm,
		metalness: 0.5,
		roughness: 0.6,
		onBeforeCompile: shader => {
			shader.uniforms.time = gu.time;
			shader.uniforms.grid = gu.grid;
      shader.uniforms.image = gu.image; // ###
			shader.vertexShader = `
				uniform float time;
				uniform float grid;
        uniform sampler2D image;  // ###
				varying float vHeight;
				vec3 moveWave(vec3 p){
					// Angle = distance offset + degree offset
					vec3 retVal = p;
					float ang;
					float kzx = 360.0/grid;
					// Wave1 (135 degrees)
					ang = 50.0*time + -1.0*p.x*kzx + -2.0*p.z*kzx;
					if (ang>360.0) ang = ang-360.0;
					ang = ang*3.14159265/180.0;
					retVal.y = 3.0*sin(ang);
					// Wave2 (090)
					ang = 25.0*time + -3.0*p.x*kzx;
					if (ang>360.0) ang = ang-360.0;
					ang = ang*3.14159265/180.0;
					retVal.y = retVal.y + 2.0*sin(ang);
					// Wave3 (180 degrees)
					ang = 15.0*time - 3.0*p.z*kzx;
					if (ang>360.0) ang = ang-360.0;
					ang = ang*3.14159265/180.0;
					retVal.y = retVal.y + 2.0*sin(ang);
					// Wave4 (225 degrees)
					ang = 50.0*time + 4.0*p.x*kzx + 8.0*p.z*kzx;
					if (ang>360.0) ang = ang-360.0;
					ang = ang*3.14159265/180.0;
					retVal.y = retVal.y + 0.5*sin(ang);
					// Wave5 (270 degrees)
					ang = 50.0*time + 8.0*p.x*kzx;
					if (ang>360.0) ang = ang-360.0;
					ang = ang*3.14159265/180.0;
					retVal.y = retVal.y + 0.5*sin(ang);
					// Add Random Value from Perlin Image
          //?? (for test, this replaces modified Y value)
          vec2 texUV = fract(p.xz/1000.); // ###
          retVal.y += texture(image, texUV).r * 5.; // ###
          //
					return retVal;
				}					
				${shader.vertexShader}
			`.replace(
				`#include <beginnormal_vertex>`,
				`#include <beginnormal_vertex>
					vec3 p = position;
       				vec2 move = vec2(1, 0);
					vec3 pos = moveWave(p);
					vec3 pos2 = moveWave(p + move.xyy);
					vec3 pos3 = moveWave(p + move.yyx);
					vNormal = normalize(cross(normalize(pos2-pos), normalize(pos3-pos)));
				`
			).replace(
				`#include <begin_vertex>`,
				`#include <begin_vertex>
					transformed.y = pos.y;
					vHeight = pos.y;
				`
			);
			shader.fragmentShader = `
				varying float vHeight;
				${shader.fragmentShader}
			`.replace(
				`#include <color_fragment>`,
				`#include <color_fragment>
					diffuseColor.rgb = mix(vec3(0.03125,0.0625,0.5), vec3(0.1,0.2,0.6), smoothstep(0.0, 6.0, vHeight));
					if (vHeight>7.0) {
						diffuseColor.rgb = vec3(0.2,0.3,0.7);	// Adds "foam" highlight to highest waves
					}
				`
			);
		}
	});
	// Compute Starting Z and X Values
	zx = -0.5*(GrdRCs)*GrdSiz+0.5*GrdSiz;
	for (let i = 0; i < GrdRCs; i++) {
		WavMZV[i] = zx;
		WavMXV[i] = zx;
		zx = zx + GrdSiz;
	}
	// 4 Adjacent Planes
	n = 0;
	for (let z = 0; z < GrdRCs; z++) {		// Row X2
		for (let x = 0; x < GrdRCs; x++) {	// Column X2
			GrdPtr[n] = new Mesh(geoWav,matWav);
			scene.add(GrdPtr[n]);
			GrdPtr[n].position.set(WavMXV[x],0,-WavMZV[z]);
			n++;
		}
	}
	//
	LodFlg = 1;
}

/* 2 Render ==================================================================*/
function rendAll() {
	requestAnimationFrame(rendAll);
	if (LodFlg > 0) {
		etime = clock.getElapsedTime();
		gu.time.value = etime;
//  	WtrNrm.offset.x -= .0005;
//		WtrNrm.offset.y += .00025;
	}
	controls.update();
   	renderer.render(scene, camera);
}

/* Window Resize Input ========================================================*/
function onWindowResize() {
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();
	renderer.setSize(window.innerWidth, window.innerHeight);
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.