Explorar el Código

新增水材质

BAI hace 1 mes
padre
commit
5eb967127e
Se han modificado 3 ficheros con 495 adiciones y 63 borrados
  1. BIN
      src/assets/FX.jpeg
  2. 131 63
      src/components/Scene3D.vue
  3. 364 0
      src/components/waterNew.ts

BIN
src/assets/FX.jpeg


+ 131 - 63
src/components/Scene3D.vue

@@ -3,7 +3,7 @@ import { onMounted, onUnmounted, ref, watch } from 'vue'
 import * as THREE from 'three'
 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
 import { Sky } from 'three/examples/jsm/objects/Sky.js'
-import { Water, createWaterNormalTexture } from './Water'
+import { StylizedWaterMaterial } from './waterNew'
 import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
 
 const containerRef = ref<HTMLDivElement>()
@@ -17,17 +17,24 @@ const cameraInfo = ref({
 })
 
 const waterParams = ref({
-  alpha: 0.65,
-  sunColor: '#ffffff',
-  waterColor: '#78a6a0',
-  distortionScale: 30.0,
-  noiseScale: 1.0,
-  fresnelBias: 0.15,
-  fresnelPower: 4.0,
-  fresnelStrength: 1.0,
+  alpha: 0.85,
+  waterColor: '#566c67',
+  deepColor: '#0a2a4a',
   flowSpeed: 0.5,
   flowDirectionX: 1.0,
   flowDirectionY: 1.0,
+  waveHeight: 0.6,
+  waveFrequency: 1.0,
+  foamIntensity: 0.25,
+  specIntensity: 2.0,
+  specPower: 64.0,
+  fresnelPower: 2.5,
+  fresnelIntensity: 1.0,
+  depthRange: 44.0,
+  waterNormalStrength: 2.0,
+  waterNormalTiling: 0.26,
+  collisionFoamThreshold: 0.10,
+  collisionFoamStrength: 0.5,
 })
 
 const showCoordinatePanel = ref(false)
@@ -39,13 +46,13 @@ let camera: THREE.PerspectiveCamera
 let renderer: THREE.WebGLRenderer
 let controls: OrbitControls
 let sky: Sky
-let water: Water
 let waterMesh: THREE.Mesh
 let sunDirection: THREE.Vector3
 let animationId: number
 let tilesetGroup: THREE.Group | null = null
 let raycaster: THREE.Raycaster
 let mouse: THREE.Vector2
+let depthRenderTarget: THREE.WebGLRenderTarget
 
 function createSky() {
   sky = new Sky()
@@ -67,40 +74,24 @@ function createSky() {
 }
 
 function createWaterSurface() {
-  const textureLoader = new THREE.TextureLoader()
-  const waterNormals = createWaterNormalTexture(textureLoader)
-
-  water = new Water(renderer, camera, scene, {
-    textureWidth: 512,
-    textureHeight: 512,
-    waterNormals,
-    alpha: waterParams.value.alpha,
-    sunDirection,
-    sunColor: new THREE.Color(waterParams.value.sunColor),
-    waterColor: new THREE.Color(waterParams.value.waterColor),
-    distortionScale: waterParams.value.distortionScale,
-    noiseScale: waterParams.value.noiseScale,
-    fresnelBias: waterParams.value.fresnelBias,
-    fresnelPower: waterParams.value.fresnelPower,
-    fresnelStrength: waterParams.value.fresnelStrength,
-    flowDirection: new THREE.Vector2(waterParams.value.flowDirectionX, waterParams.value.flowDirectionY),
-    side: THREE.DoubleSide,
-    fog: true,
-  })
-
   waterMesh = new THREE.Mesh(
-    new THREE.PlaneGeometry(200, 200, 60, 60),
-    water.material
+    new THREE.PlaneGeometry(200, 200, 120, 120),
+    StylizedWaterMaterial
   )
-  waterMesh.add(water)
   waterMesh.rotation.x = -Math.PI / 2
   waterMesh.position.set(840.85714, 7.47851, 2179.50782)
   waterMesh.receiveShadow = true
   scene.add(waterMesh)
+  StylizedWaterMaterial.uniforms.cameraPos.value.copy(camera.position)
+  StylizedWaterMaterial.uniforms.sunDirection.value.copy(sunDirection)
+  StylizedWaterMaterial.uniforms.cameraNear.value = camera.near
+  StylizedWaterMaterial.uniforms.cameraFar.value = camera.far
+  const container = containerRef.value!
+  StylizedWaterMaterial.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight)
 }
 
 async function load3DTiles() {
-  const tilesetUrl = 'http://localhost:9003/model/scence/tileset.json'
+  const tilesetUrl = 'http://192.168.0.110:9003/model/scence/tileset.json'
   const gltfLoader = new GLTFLoader()
   
   try {
@@ -237,6 +228,11 @@ function initScene() {
   renderer.shadowMap.type = THREE.PCFShadowMap
   container.appendChild(renderer.domElement)
 
+  const pixelWidth = Math.floor(container.clientWidth * window.devicePixelRatio)
+  const pixelHeight = Math.floor(container.clientHeight * window.devicePixelRatio)
+  depthRenderTarget = new THREE.WebGLRenderTarget(pixelWidth, pixelHeight)
+  depthRenderTarget.depthTexture = new THREE.DepthTexture(pixelWidth, pixelHeight)
+
   controls = new OrbitControls(camera, renderer.domElement)
   controls.enableDamping = true
   controls.dampingFactor = 0.03
@@ -250,7 +246,7 @@ function initScene() {
   }
   controls.target.set(842.3117, 9.27789, 2178.09268)
   controls.maxPolarAngle = Math.PI / 2.1
-  controls.minDistance = 20
+  controls.minDistance = 5
   controls.maxDistance = 100
 
   createSky()
@@ -326,8 +322,18 @@ function onMouseClick(event: MouseEvent) {
 function animate() {
   animationId = requestAnimationFrame(animate)
   sky.material.uniforms.time.value += 0.001
-  water.material.uniforms.time.value += 0.005 * waterParams.value.flowSpeed
-  water.render()
+
+  waterMesh.visible = false
+  const prevRT = renderer.getRenderTarget()
+  renderer.setRenderTarget(depthRenderTarget)
+  renderer.render(scene, camera)
+  renderer.setRenderTarget(prevRT)
+  waterMesh.visible = true
+
+  StylizedWaterMaterial.uniforms.depthSampler.value = depthRenderTarget.depthTexture
+  StylizedWaterMaterial.uniforms.iTime.value += 0.016
+  StylizedWaterMaterial.uniforms.collisionFoamTime.value += 0.016
+  StylizedWaterMaterial.uniforms.cameraPos.value.copy(camera.position)
 
   controls.update()
 
@@ -355,6 +361,10 @@ function onResize() {
   camera.aspect = width / height
   camera.updateProjectionMatrix()
   renderer.setSize(width, height)
+  const pixelWidth = Math.floor(width * window.devicePixelRatio)
+  const pixelHeight = Math.floor(height * window.devicePixelRatio)
+  depthRenderTarget.setSize(pixelWidth, pixelHeight)
+  StylizedWaterMaterial.uniforms.iResolution.value.set(width, height)
 }
 
 onMounted(() => {
@@ -376,19 +386,28 @@ onUnmounted(() => {
   
   renderer.dispose()
   controls.dispose()
+  depthRenderTarget.dispose()
 })
 
 watch(() => waterParams.value, (newParams) => {
-  if (water && water.material && water.material.uniforms) {
-    water.material.uniforms.alpha.value = newParams.alpha
-    water.material.uniforms.sunColor.value.set(newParams.sunColor)
-    water.material.uniforms.waterColor.value.set(newParams.waterColor)
-    water.material.uniforms.distortionScale.value = newParams.distortionScale
-    water.material.uniforms.noiseScale.value = newParams.noiseScale
-    water.material.uniforms.fresnelBias.value = newParams.fresnelBias
-    water.material.uniforms.fresnelPower.value = newParams.fresnelPower
-    water.material.uniforms.fresnelStrength.value = newParams.fresnelStrength
-    water.material.uniforms.flowDirection.value.set(newParams.flowDirectionX, newParams.flowDirectionY)
+  if (StylizedWaterMaterial && StylizedWaterMaterial.uniforms) {
+    StylizedWaterMaterial.uniforms.alpha.value = newParams.alpha
+    StylizedWaterMaterial.uniforms.flowSpeed.value = newParams.flowSpeed
+    StylizedWaterMaterial.uniforms.flowDirection.value.set(newParams.flowDirectionX, newParams.flowDirectionY)
+    StylizedWaterMaterial.uniforms.waveHeight.value = newParams.waveHeight
+    StylizedWaterMaterial.uniforms.waveFrequency.value = newParams.waveFrequency
+    StylizedWaterMaterial.uniforms.shallowColor.value.set(newParams.waterColor)
+    StylizedWaterMaterial.uniforms.deepColor.value.set(newParams.deepColor)
+    StylizedWaterMaterial.uniforms.foamIntensity.value = newParams.foamIntensity
+    StylizedWaterMaterial.uniforms.specIntensity.value = newParams.specIntensity
+    StylizedWaterMaterial.uniforms.specPower.value = newParams.specPower
+    StylizedWaterMaterial.uniforms.fresnelPower.value = newParams.fresnelPower
+    StylizedWaterMaterial.uniforms.fresnelIntensity.value = newParams.fresnelIntensity
+    StylizedWaterMaterial.uniforms.depthRange.value = newParams.depthRange
+    StylizedWaterMaterial.uniforms.waterNormalStrength.value = newParams.waterNormalStrength
+    StylizedWaterMaterial.uniforms.waterNormalTiling.value = newParams.waterNormalTiling
+    StylizedWaterMaterial.uniforms.collisionFoamThreshold.value = newParams.collisionFoamThreshold
+    StylizedWaterMaterial.uniforms.collisionFoamStrength.value = newParams.collisionFoamStrength
   }
 }, { deep: true })
 </script>
@@ -484,6 +503,20 @@ watch(() => waterParams.value, (newParams) => {
         <span class="slider-value">{{ waterParams.alpha.toFixed(2) }}</span>
       </div>
     </div>
+    <div class="water-section">
+      <div class="section-label">浪高</div>
+      <div class="slider-item">
+        <input type="range" v-model.number="waterParams.waveHeight" min="0" max="2" step="0.05" />
+        <span class="slider-value">{{ waterParams.waveHeight.toFixed(2) }}</span>
+      </div>
+    </div>
+    <div class="water-section">
+      <div class="section-label">波浪频率</div>
+      <div class="slider-item">
+        <input type="range" v-model.number="waterParams.waveFrequency" min="0.1" max="5" step="0.1" />
+        <span class="slider-value">{{ waterParams.waveFrequency.toFixed(1) }}</span>
+      </div>
+    </div>
     <div class="water-section">
       <div class="section-label">水流速度</div>
       <div class="slider-item">
@@ -506,38 +539,52 @@ watch(() => waterParams.value, (newParams) => {
       </div>
     </div>
     <div class="water-section">
-      <div class="section-label">水颜色</div>
+      <div class="section-label">水颜色</div>
       <div class="color-item">
         <input type="color" v-model="waterParams.waterColor" />
         <span class="color-value">{{ waterParams.waterColor }}</span>
       </div>
     </div>
     <div class="water-section">
-      <div class="section-label">太阳光颜色</div>
+      <div class="section-label">深水颜色</div>
       <div class="color-item">
-        <input type="color" v-model="waterParams.sunColor" />
-        <span class="color-value">{{ waterParams.sunColor }}</span>
+        <input type="color" v-model="waterParams.deepColor" />
+        <span class="color-value">{{ waterParams.deepColor }}</span>
+      </div>
+    </div>
+    <div class="water-section">
+      <div class="section-label">水深范围</div>
+      <div class="slider-item">
+        <input type="range" v-model.number="waterParams.depthRange" min="1" max="50" step="0.5" />
+        <span class="slider-value">{{ waterParams.depthRange.toFixed(1) }}</span>
+      </div>
+    </div>
+    <div class="water-section">
+      <div class="section-label">水法线强度</div>
+      <div class="slider-item">
+        <input type="range" v-model.number="waterParams.waterNormalStrength" min="0" max="2" step="0.05" />
+        <span class="slider-value">{{ waterParams.waterNormalStrength.toFixed(2) }}</span>
       </div>
     </div>
     <div class="water-section">
-      <div class="section-label">波纹强度</div>
+      <div class="section-label">水纹平铺</div>
       <div class="slider-item">
-        <input type="range" v-model.number="waterParams.distortionScale" min="0" max="100" step="1" />
-        <span class="slider-value">{{ waterParams.distortionScale.toFixed(0) }}</span>
+        <input type="range" v-model.number="waterParams.waterNormalTiling" min="0.01" max="5" step="0.01" />
+        <span class="slider-value">{{ waterParams.waterNormalTiling.toFixed(2) }}</span>
       </div>
     </div>
     <div class="water-section">
-      <div class="section-label">噪声缩放</div>
+      <div class="section-label">高光强度</div>
       <div class="slider-item">
-        <input type="range" v-model.number="waterParams.noiseScale" min="0.1" max="5" step="0.1" />
-        <span class="slider-value">{{ waterParams.noiseScale.toFixed(1) }}</span>
+        <input type="range" v-model.number="waterParams.specIntensity" min="0" max="5" step="0.1" />
+        <span class="slider-value">{{ waterParams.specIntensity.toFixed(1) }}</span>
       </div>
     </div>
     <div class="water-section">
-      <div class="section-label">菲涅尔偏移</div>
+      <div class="section-label">高光锐度</div>
       <div class="slider-item">
-        <input type="range" v-model.number="waterParams.fresnelBias" min="0" max="1" step="0.01" />
-        <span class="slider-value">{{ waterParams.fresnelBias.toFixed(2) }}</span>
+        <input type="range" v-model.number="waterParams.specPower" min="1" max="256" step="1" />
+        <span class="slider-value">{{ waterParams.specPower.toFixed(0) }}</span>
       </div>
     </div>
     <div class="water-section">
@@ -550,8 +597,29 @@ watch(() => waterParams.value, (newParams) => {
     <div class="water-section">
       <div class="section-label">菲涅尔强度</div>
       <div class="slider-item">
-        <input type="range" v-model.number="waterParams.fresnelStrength" min="0" max="3" step="0.1" />
-        <span class="slider-value">{{ waterParams.fresnelStrength.toFixed(1) }}</span>
+        <input type="range" v-model.number="waterParams.fresnelIntensity" min="0" max="3" step="0.1" />
+        <span class="slider-value">{{ waterParams.fresnelIntensity.toFixed(1) }}</span>
+      </div>
+    </div>
+    <div class="water-section">
+      <div class="section-label">泡沫强度</div>
+      <div class="slider-item">
+        <input type="range" v-model.number="waterParams.foamIntensity" min="0" max="2" step="0.05" />
+        <span class="slider-value">{{ waterParams.foamIntensity.toFixed(2) }}</span>
+      </div>
+    </div>
+    <div class="water-section">
+      <div class="section-label">碰撞泡沫阈值</div>
+      <div class="slider-item">
+        <input type="range" v-model.number="waterParams.collisionFoamThreshold" min="0" max="2" step="0.05" />
+        <span class="slider-value">{{ waterParams.collisionFoamThreshold.toFixed(2) }}</span>
+      </div>
+    </div>
+    <div class="water-section">
+      <div class="section-label">碰撞泡沫强度</div>
+      <div class="slider-item">
+        <input type="range" v-model.number="waterParams.collisionFoamStrength" min="0" max="3" step="0.1" />
+        <span class="slider-value">{{ waterParams.collisionFoamStrength.toFixed(1) }}</span>
       </div>
     </div>
   </div>

+ 364 - 0
src/components/waterNew.ts

@@ -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
+});