<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);
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.