|
|
@@ -0,0 +1,364 @@
|
|
|
+import * as THREE from 'three'
|
|
|
+
|
|
|
+const lakeVertexShader = `
|
|
|
+uniform float iTime;
|
|
|
+uniform float waveHeight;
|
|
|
+uniform float waveFrequency;
|
|
|
+uniform float flowSpeed;
|
|
|
+uniform vec2 flowDirection;
|
|
|
+
|
|
|
+varying vec2 vUv;
|
|
|
+varying vec3 vWorldPosition;
|
|
|
+varying vec4 vClipSpace;
|
|
|
+varying vec2 vWorldUV;
|
|
|
+varying float vWaveHeight;
|
|
|
+
|
|
|
+float calcVertexWave(vec2 pos, float time, vec2 dir, float amp, float freq, float speed) {
|
|
|
+ return amp * sin(dot(pos, dir) * freq + time * speed);
|
|
|
+}
|
|
|
+
|
|
|
+void main() {
|
|
|
+ vUv = uv;
|
|
|
+ vec2 flowDir = normalize(flowDirection);
|
|
|
+ float time = iTime * 0.5;
|
|
|
+
|
|
|
+ vec4 worldPos = modelMatrix * vec4(position, 1.0);
|
|
|
+ vec2 wavePos = worldPos.xz * waveFrequency;
|
|
|
+
|
|
|
+ float h = 0.0;
|
|
|
+ h += calcVertexWave(wavePos, time, flowDir, 1.00, 1.0, 1.20 * flowSpeed);
|
|
|
+ h += calcVertexWave(wavePos, time, vec2(-flowDir.y, flowDir.x), 0.60, 1.8, 1.50 * flowSpeed);
|
|
|
+ h += calcVertexWave(wavePos, time, flowDir * 0.7, 0.40, 2.7, 1.90 * flowSpeed);
|
|
|
+ h += calcVertexWave(wavePos, time, vec2(flowDir.y, -flowDir.x), 0.25, 3.9, 2.40 * flowSpeed);
|
|
|
+
|
|
|
+ h *= waveHeight * 0.2;
|
|
|
+
|
|
|
+ vec3 displacedPos = position;
|
|
|
+ displacedPos.z += h;
|
|
|
+
|
|
|
+ vec4 newWorldPos = modelMatrix * vec4(displacedPos, 1.0);
|
|
|
+ vWorldPosition = newWorldPos.xyz;
|
|
|
+ vWorldUV = newWorldPos.xz;
|
|
|
+ vWaveHeight = h;
|
|
|
+
|
|
|
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(displacedPos, 1.0);
|
|
|
+ vClipSpace = gl_Position;
|
|
|
+}
|
|
|
+`;
|
|
|
+
|
|
|
+const lakeFragmentShader = `
|
|
|
+uniform float iTime;
|
|
|
+uniform vec2 iResolution;
|
|
|
+uniform sampler2D depthSampler;
|
|
|
+uniform float cameraNear;
|
|
|
+uniform float cameraFar;
|
|
|
+uniform float waterLevel;
|
|
|
+uniform float waveHeight;
|
|
|
+uniform float waveFrequency;
|
|
|
+uniform float flowSpeed;
|
|
|
+uniform vec3 shallowColor;
|
|
|
+uniform vec3 deepColor;
|
|
|
+uniform float foamIntensity;
|
|
|
+uniform float alpha;
|
|
|
+uniform float specIntensity;
|
|
|
+uniform float specPower;
|
|
|
+uniform float fresnelPower;
|
|
|
+uniform float fresnelIntensity;
|
|
|
+uniform float depthRange;
|
|
|
+uniform vec3 cameraPos;
|
|
|
+uniform vec3 sunDirection;
|
|
|
+uniform vec2 flowDirection;
|
|
|
+uniform sampler2D waterNormalMap;
|
|
|
+uniform float waterNormalStrength;
|
|
|
+uniform float waterNormalTiling;
|
|
|
+uniform float collisionFoamThreshold;
|
|
|
+uniform float collisionFoamStrength;
|
|
|
+uniform vec3 collisionFoamColor;
|
|
|
+uniform float collisionFoamTime;
|
|
|
+
|
|
|
+varying vec2 vUv;
|
|
|
+varying vec3 vWorldPosition;
|
|
|
+varying vec4 vClipSpace;
|
|
|
+varying vec2 vWorldUV;
|
|
|
+varying float vWaveHeight;
|
|
|
+
|
|
|
+float hash(vec2 p) {
|
|
|
+ vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
|
+ p3 += dot(p3, p3.yzx + 33.33);
|
|
|
+ return fract((p3.x + p3.y) * p3.z);
|
|
|
+}
|
|
|
+
|
|
|
+float noise(vec2 p) {
|
|
|
+ vec2 i = floor(p);
|
|
|
+ vec2 f = fract(p);
|
|
|
+ f = f * f * (3.0 - 2.0 * f);
|
|
|
+ float a = hash(i);
|
|
|
+ float b = hash(i + vec2(1.0, 0.0));
|
|
|
+ float c = hash(i + vec2(0.0, 1.0));
|
|
|
+ float d = hash(i + vec2(1.0, 1.0));
|
|
|
+ return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
|
+}
|
|
|
+
|
|
|
+float fbm(vec2 p) {
|
|
|
+ float value = 0.0;
|
|
|
+ float amplitude = 0.5;
|
|
|
+ float frequency = 1.0;
|
|
|
+ for (int i = 0; i < 5; i++) {
|
|
|
+ value += amplitude * noise(p * frequency);
|
|
|
+ frequency *= 2.0;
|
|
|
+ amplitude *= 0.5;
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+}
|
|
|
+
|
|
|
+float linearizeDepth(float depth) {
|
|
|
+ float z = depth * 2.0 - 1.0;
|
|
|
+ return (2.0 * cameraNear * cameraFar) / (cameraFar + cameraNear - z * (cameraFar - cameraNear));
|
|
|
+}
|
|
|
+
|
|
|
+struct Wave {
|
|
|
+ vec2 dir;
|
|
|
+ float amp;
|
|
|
+ float freq;
|
|
|
+ float speed;
|
|
|
+ float steepness;
|
|
|
+};
|
|
|
+
|
|
|
+#define NUM_WAVES 8
|
|
|
+
|
|
|
+vec2 getFlowDir(float angleOffset) {
|
|
|
+ float angle = atan(flowDirection.y, flowDirection.x);
|
|
|
+ return vec2(cos(angle + angleOffset), sin(angle + angleOffset));
|
|
|
+}
|
|
|
+
|
|
|
+void computeWaves(out Wave waves[NUM_WAVES]) {
|
|
|
+ vec2 mainDir = normalize(flowDirection);
|
|
|
+
|
|
|
+ waves[0] = Wave(mainDir, 1.00, 1.0, 1.20, 0.30);
|
|
|
+ waves[1] = Wave(getFlowDir(0.6), 0.60, 1.8, 1.50, 0.25);
|
|
|
+ waves[2] = Wave(getFlowDir(-0.5), 0.40, 2.7, 1.90, 0.20);
|
|
|
+ waves[3] = Wave(getFlowDir(1.2), 0.25, 3.9, 2.40, 0.15);
|
|
|
+ waves[4] = Wave(getFlowDir(-0.9), 0.18, 5.2, 2.80, 0.12);
|
|
|
+ waves[5] = Wave(getFlowDir(0.3), 0.12, 6.8, 3.30, 0.10);
|
|
|
+ waves[6] = Wave(getFlowDir(-0.7), 0.08, 8.5, 3.90, 0.08);
|
|
|
+ waves[7] = Wave(normalize(mainDir + vec2(0.3, 0.5)),0.05, 10.5, 4.50, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+float calcWaveHeight(vec2 pos, float time, Wave w) {
|
|
|
+ return w.amp * sin(dot(pos, w.dir) * w.freq + time * w.speed);
|
|
|
+}
|
|
|
+
|
|
|
+vec2 calcWaveDerivative(vec2 pos, float time, Wave w) {
|
|
|
+ float cosVal = cos(dot(pos, w.dir) * w.freq + time * w.speed);
|
|
|
+ return w.amp * w.freq * w.dir * cosVal;
|
|
|
+}
|
|
|
+
|
|
|
+float totalWaveHeight(vec2 pos, float time, float wh, float wf) {
|
|
|
+ Wave waves[NUM_WAVES];
|
|
|
+ computeWaves(waves);
|
|
|
+ float h = 0.0;
|
|
|
+ for (int i = 0; i < NUM_WAVES; i++) {
|
|
|
+ h += calcWaveHeight(pos * wf, time, waves[i]);
|
|
|
+ }
|
|
|
+ return h * wh;
|
|
|
+}
|
|
|
+
|
|
|
+void computeWaveData(vec2 pos, float time, float wh, float wf, out float height, out vec3 normal, out vec2 displacement) {
|
|
|
+ Wave waves[NUM_WAVES];
|
|
|
+ computeWaves(waves);
|
|
|
+
|
|
|
+ height = 0.0;
|
|
|
+ vec2 deriv = vec2(0.0);
|
|
|
+ vec2 disp = vec2(0.0);
|
|
|
+
|
|
|
+ for (int i = 0; i < NUM_WAVES; i++) {
|
|
|
+ Wave w = waves[i];
|
|
|
+ float h = w.amp * sin(dot(pos * wf, w.dir) * w.freq + time * w.speed);
|
|
|
+ float cosVal = cos(dot(pos * wf, w.dir) * w.freq + time * w.speed);
|
|
|
+ height += h;
|
|
|
+ deriv += w.amp * w.freq * w.dir * wf * cosVal;
|
|
|
+ disp += w.amp * w.dir * cosVal * w.steepness;
|
|
|
+ }
|
|
|
+
|
|
|
+ height *= wh;
|
|
|
+ deriv *= wh;
|
|
|
+ displacement = disp * wh * 0.3;
|
|
|
+
|
|
|
+ vec3 n = normalize(vec3(-deriv.x, 1.0, -deriv.y));
|
|
|
+ normal = n;
|
|
|
+}
|
|
|
+
|
|
|
+float eddyNoise(vec2 pos, float time) {
|
|
|
+ vec2 p = pos * 0.8 + time * 0.08;
|
|
|
+ float n1 = noise(p);
|
|
|
+ float n2 = noise(p * 2.3 + 1.7);
|
|
|
+ float n3 = noise(p * 4.7 + 3.2);
|
|
|
+ return (n1 * 0.6 + n2 * 0.3 + n3 * 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+float getCollisionFoam(vec2 pos, float depthDiff) {
|
|
|
+ float foam = smoothstep(collisionFoamThreshold, 0.0, depthDiff);
|
|
|
+ float collisionNoise = fbm(pos * 4.0 + collisionFoamTime * 0.3);
|
|
|
+ foam *= collisionNoise * collisionFoamStrength;
|
|
|
+ return clamp(foam, 0.0, 1.0);
|
|
|
+}
|
|
|
+
|
|
|
+void main() {
|
|
|
+ vec2 screenUV = vClipSpace.xy / vClipSpace.w * 0.5 + 0.5;
|
|
|
+
|
|
|
+ float sceneDepth = texture(depthSampler, screenUV).r;
|
|
|
+
|
|
|
+ float linearSceneDepth = linearizeDepth(sceneDepth);
|
|
|
+ float linearWaterDepth = linearizeDepth(gl_FragCoord.z);
|
|
|
+ float bufferDepth = max(abs(linearSceneDepth - linearWaterDepth), 0.01);
|
|
|
+ if (sceneDepth >= 1.0) {
|
|
|
+ bufferDepth = depthRange * 0.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ float worldHeightDepth = clamp((waterLevel - vWorldPosition.y) / depthRange, 0.0, 1.0);
|
|
|
+
|
|
|
+ float waterDepth = max(bufferDepth, worldHeightDepth * depthRange);
|
|
|
+
|
|
|
+ float depthFactor = clamp(waterDepth / depthRange, 0.0, 1.0);
|
|
|
+ depthFactor = max(depthFactor, worldHeightDepth);
|
|
|
+
|
|
|
+ vec2 flowDir = normalize(flowDirection);
|
|
|
+ float time = iTime * 0.5;
|
|
|
+
|
|
|
+ vec2 wavePos = vWorldPosition.xz * waveFrequency;
|
|
|
+
|
|
|
+ float waveH;
|
|
|
+ vec3 waveN;
|
|
|
+ vec2 waveDisp;
|
|
|
+ computeWaveData(wavePos, time, waveHeight * 0.2, waveFrequency, waveH, waveN, waveDisp);
|
|
|
+
|
|
|
+ vec2 displacedPos = wavePos + waveDisp * 0.3;
|
|
|
+
|
|
|
+ float choppyH;
|
|
|
+ vec3 choppyN;
|
|
|
+ vec2 choppyDisp;
|
|
|
+ computeWaveData(displacedPos, time, waveHeight * 0.15, waveFrequency * 1.3, choppyH, choppyN, choppyDisp);
|
|
|
+
|
|
|
+ vec3 finalNormal = normalize(waveN + choppyN * 0.4);
|
|
|
+
|
|
|
+ float eddy = eddyNoise(vWorldPosition.xz, time);
|
|
|
+ float eddyAngle = eddy * 4.0 - 2.0;
|
|
|
+ vec2 eddyDir = vec2(cos(eddyAngle), sin(eddyAngle));
|
|
|
+ vec3 eddyNormal = normalize(vec3(eddyDir.x * 0.08, 1.0, eddyDir.y * 0.08));
|
|
|
+
|
|
|
+ finalNormal = normalize(finalNormal + eddyNormal * 0.15);
|
|
|
+
|
|
|
+ vec2 waterUV = vWorldUV * waterNormalTiling + iTime * flowDir * flowSpeed * 0.10;
|
|
|
+ vec3 waterMap = texture2D(waterNormalMap, waterUV).rgb;
|
|
|
+ waterMap = waterMap * 2.0 - 1.0;
|
|
|
+ vec3 waterPerturb = vec3(waterMap.x * 0.3, 0.0, waterMap.y * 0.3) * waterNormalStrength;
|
|
|
+ finalNormal = normalize(finalNormal + waterPerturb);
|
|
|
+
|
|
|
+ vec3 viewDir = normalize(cameraPos - vWorldPosition);
|
|
|
+ vec3 halfVec = normalize(viewDir + sunDirection);
|
|
|
+
|
|
|
+ float NdotL = max(dot(finalNormal, sunDirection), 0.0);
|
|
|
+ float NdotH = max(dot(finalNormal, halfVec), 0.0);
|
|
|
+ float NdotV = max(dot(finalNormal, viewDir), 0.0);
|
|
|
+
|
|
|
+ float specular = pow(NdotH, specPower) * specIntensity;
|
|
|
+
|
|
|
+ float fresnel = fresnelIntensity * pow(1.0 - NdotV, fresnelPower);
|
|
|
+
|
|
|
+ vec3 waterCol = mix(shallowColor, deepColor, depthFactor);
|
|
|
+
|
|
|
+ float shallowGlow = exp(-waterDepth * 1.5) * 0.3;
|
|
|
+ waterCol += shallowColor * shallowGlow;
|
|
|
+
|
|
|
+ float foamNoise = fbm(vWorldPosition.xz * 3.0 + iTime * 0.2 + waveDisp * 2.0);
|
|
|
+ float foamShore = smoothstep(0.3, 1.5, waterDepth);
|
|
|
+ float foam = (1.0 - foamShore) * foamNoise * foamIntensity * 1.5;
|
|
|
+
|
|
|
+ float wavePeakFoam = smoothstep(0.6, 1.0, abs(waveH) / max(waveHeight * 0.1, 0.01));
|
|
|
+ foam += wavePeakFoam * foamNoise * foamIntensity * 0.3;
|
|
|
+
|
|
|
+ foam = clamp(foam, 0.0, 1.0);
|
|
|
+
|
|
|
+ float collisionFoam = getCollisionFoam(vWorldPosition.xz, waterDepth);
|
|
|
+ foam = clamp(foam + collisionFoam, 0.0, 1.0);
|
|
|
+
|
|
|
+ vec3 ambientLight = vec3(0.45, 0.50, 0.55);
|
|
|
+ vec3 sunLight = vec3(1.8, 1.7, 1.5);
|
|
|
+
|
|
|
+ vec3 skyReflection = mix(
|
|
|
+ vec3(0.4, 0.5, 0.6),
|
|
|
+ vec3(0.7, 0.8, 0.9),
|
|
|
+ fresnel
|
|
|
+ );
|
|
|
+
|
|
|
+ vec3 diffuse = NdotL * sunLight;
|
|
|
+ vec3 lighting = diffuse + ambientLight;
|
|
|
+
|
|
|
+ vec3 finalColor = waterCol * lighting;
|
|
|
+
|
|
|
+ finalColor += specular * sunLight * 0.3 * (1.0 - fresnel * 0.5);
|
|
|
+
|
|
|
+ vec3 foamColor = vec3(0.95, 0.97, 1.0);
|
|
|
+ finalColor = mix(finalColor, foamColor, foam);
|
|
|
+
|
|
|
+ finalColor = mix(finalColor, collisionFoamColor, collisionFoam);
|
|
|
+
|
|
|
+ vec3 fresnelColor = mix(vec3(0.55, 0.65, 0.75), sunLight * 0.25, fresnel);
|
|
|
+ finalColor = mix(finalColor, fresnelColor, fresnel * 0.45);
|
|
|
+
|
|
|
+ float edgeFade = smoothstep(0.0, 0.02, screenUV.x) * smoothstep(0.0, 0.02, screenUV.y) *
|
|
|
+ smoothstep(0.0, 0.02, 1.0 - screenUV.x) * smoothstep(0.0, 0.02, 1.0 - screenUV.y);
|
|
|
+
|
|
|
+ finalColor *= 1.0 + foam * 0.2;
|
|
|
+
|
|
|
+ float finalAlpha = alpha * (0.6 + 0.4 * (1.0 - depthFactor));
|
|
|
+
|
|
|
+ gl_FragColor = vec4(finalColor, finalAlpha * edgeFade);
|
|
|
+}
|
|
|
+`;
|
|
|
+
|
|
|
+const textureLoader = new THREE.TextureLoader();
|
|
|
+
|
|
|
+const waterNormalTexture = textureLoader.load(
|
|
|
+ new URL('../assets/waternormals.jpg', import.meta.url).href
|
|
|
+);
|
|
|
+waterNormalTexture.wrapS = THREE.RepeatWrapping;
|
|
|
+waterNormalTexture.wrapT = THREE.RepeatWrapping;
|
|
|
+
|
|
|
+export const StylizedWaterMaterial = new THREE.ShaderMaterial({
|
|
|
+ transparent: true,
|
|
|
+ side: THREE.DoubleSide,
|
|
|
+ depthWrite: false,
|
|
|
+ uniforms: {
|
|
|
+ iTime: { value: 0 },
|
|
|
+ iResolution: { value: new THREE.Vector2(1, 1) },
|
|
|
+ depthSampler: { value: null },
|
|
|
+ cameraNear: { value: 0.1 },
|
|
|
+ cameraFar: { value: 1000.0 },
|
|
|
+ waterLevel: { value: 0.0 },
|
|
|
+ waveHeight: { value: 0.6 },
|
|
|
+ waveFrequency: { value: 1.0 },
|
|
|
+ flowSpeed: { value: 0.5 },
|
|
|
+ shallowColor: { value: new THREE.Color('#566c67') },
|
|
|
+ deepColor: { value: new THREE.Color('#0a2a4a') },
|
|
|
+ foamIntensity: { value: 0.25 },
|
|
|
+ alpha: { value: 0.85 },
|
|
|
+ specIntensity: { value: 2.0 },
|
|
|
+ specPower: { value: 64.0 },
|
|
|
+ fresnelPower: { value: 2.5 },
|
|
|
+ fresnelIntensity: { value: 1.0 },
|
|
|
+ depthRange: { value: 44.0 },
|
|
|
+ cameraPos: { value: new THREE.Vector3(0, 0, 0) },
|
|
|
+ sunDirection: { value: new THREE.Vector3(0.5, 0.8, 0.3).normalize() },
|
|
|
+ flowDirection: { value: new THREE.Vector2(1.0, 0.0) },
|
|
|
+ waterNormalMap: { value: waterNormalTexture },
|
|
|
+ waterNormalStrength: { value: 2.0 },
|
|
|
+ waterNormalTiling: { value: 0.26 },
|
|
|
+ collisionFoamThreshold: { value: 0.10 },
|
|
|
+ collisionFoamStrength: { value: 0.5 },
|
|
|
+ collisionFoamColor: { value: new THREE.Color(1, 1, 1) },
|
|
|
+ collisionFoamTime: { value: 0 },
|
|
|
+ },
|
|
|
+ vertexShader: lakeVertexShader,
|
|
|
+ fragmentShader: lakeFragmentShader
|
|
|
+});
|