From 6da77e8495d3d1c3763552b6711524b52d351927 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Mon, 6 Apr 2026 17:42:56 +0200 Subject: [PATCH] Update water material properties and tests --- src/rendering/water-material.js | 21 ++++++++++++++++----- src/rendering/water-material.ts | 21 ++++++++++++++++----- tests/domain/water-material.test.ts | 29 ++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/rendering/water-material.js b/src/rendering/water-material.js index f4917235..27862a38 100644 --- a/src/rendering/water-material.js +++ b/src/rendering/water-material.js @@ -118,22 +118,32 @@ export function createWaterMaterial(options) { }); const waveStrength = Math.max(0, options.waveStrength); const waveAmplitude = 0.016 + Math.min(0.12, waveStrength * 0.06); + const clampedOpacity = Math.max(0.12, Math.min(1, options.opacity)); + const transmission = options.isTopFace + ? Math.max(0.16, Math.min(0.72, 0.86 - clampedOpacity * 0.55)) + : Math.max(0.08, Math.min(0.42, 0.46 - clampedOpacity * 0.32)); + const attenuationDistance = options.isTopFace + ? 1.4 + clampedOpacity * 2.2 + : 0.8 + clampedOpacity * 1.1; + const emissiveIntensity = options.isTopFace + ? 0.16 + waveStrength * 0.14 + clampedOpacity * 0.08 + : 0.05 + clampedOpacity * 0.05; const material = new MeshPhysicalMaterial({ color: options.colorHex, emissive: options.colorHex, - emissiveIntensity: options.isTopFace ? 0.08 + waveStrength * 0.12 : 0.03, + emissiveIntensity, roughness: options.isTopFace ? 0.08 : 0.22, metalness: 0.02, transparent: true, - opacity: options.opacity, - transmission: options.isTopFace ? 0.86 : 0.42, + opacity: 1, + transmission, thickness: options.isTopFace ? 1.8 : 0.85, ior: 1.325, reflectivity: options.isTopFace ? 0.45 : 0.16, clearcoat: options.isTopFace ? 0.85 : 0.18, clearcoatRoughness: options.isTopFace ? 0.12 : 0.2, attenuationColor: waterColor, - attenuationDistance: options.isTopFace ? 3.5 : 1.7, + attenuationDistance, envMapIntensity: options.isTopFace ? 1.2 : 0.9, depthWrite: false, side: DoubleSide @@ -208,8 +218,9 @@ export function createWaterMaterial(options) { float sparkle = sin(vWaterLocalPos.x * 5.5 + waterTime * 1.4) * sin(vWaterLocalPos.y * 4.6 - waterTime * 1.1); float foam = clamp(max(edgeFoam * 0.42, contactFoam) * (0.45 + waterWaveStrength * 0.7) + max(0.0, sparkle) * 0.06, 0.0, 0.72); diffuseColor.rgb = mix(diffuseColor.rgb, vec3(0.97, 0.99, 1.0), foam); + diffuseColor.rgb = mix(diffuseColor.rgb, diffuse.rgb * 1.12, 0.32 + (1.0 - transmissionFactor) * 0.22); diffuseColor.rgb += vec3(0.08, 0.12, 0.18) * fresnel * 0.18; - diffuseColor.a = clamp(diffuseColor.a + foam * 0.16, 0.0, 1.0); + diffuseColor.a = 1.0; }`); }; return { diff --git a/src/rendering/water-material.ts b/src/rendering/water-material.ts index a07e8d56..f009407a 100644 --- a/src/rendering/water-material.ts +++ b/src/rendering/water-material.ts @@ -173,22 +173,32 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria }); const waveStrength = Math.max(0, options.waveStrength); const waveAmplitude = 0.016 + Math.min(0.12, waveStrength * 0.06); + const clampedOpacity = Math.max(0.12, Math.min(1, options.opacity)); + const transmission = options.isTopFace + ? Math.max(0.16, Math.min(0.72, 0.86 - clampedOpacity * 0.55)) + : Math.max(0.08, Math.min(0.42, 0.46 - clampedOpacity * 0.32)); + const attenuationDistance = options.isTopFace + ? 1.4 + clampedOpacity * 2.2 + : 0.8 + clampedOpacity * 1.1; + const emissiveIntensity = options.isTopFace + ? 0.16 + waveStrength * 0.14 + clampedOpacity * 0.08 + : 0.05 + clampedOpacity * 0.05; const material = new MeshPhysicalMaterial({ color: options.colorHex, emissive: options.colorHex, - emissiveIntensity: options.isTopFace ? 0.08 + waveStrength * 0.12 : 0.03, + emissiveIntensity, roughness: options.isTopFace ? 0.08 : 0.22, metalness: 0.02, transparent: true, - opacity: options.opacity, - transmission: options.isTopFace ? 0.86 : 0.42, + opacity: 1, + transmission, thickness: options.isTopFace ? 1.8 : 0.85, ior: 1.325, reflectivity: options.isTopFace ? 0.45 : 0.16, clearcoat: options.isTopFace ? 0.85 : 0.18, clearcoatRoughness: options.isTopFace ? 0.12 : 0.2, attenuationColor: waterColor, - attenuationDistance: options.isTopFace ? 3.5 : 1.7, + attenuationDistance, envMapIntensity: options.isTopFace ? 1.2 : 0.9, depthWrite: false, side: DoubleSide @@ -281,8 +291,9 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria float sparkle = sin(vWaterLocalPos.x * 5.5 + waterTime * 1.4) * sin(vWaterLocalPos.y * 4.6 - waterTime * 1.1); float foam = clamp(max(edgeFoam * 0.42, contactFoam) * (0.45 + waterWaveStrength * 0.7) + max(0.0, sparkle) * 0.06, 0.0, 0.72); diffuseColor.rgb = mix(diffuseColor.rgb, vec3(0.97, 0.99, 1.0), foam); + diffuseColor.rgb = mix(diffuseColor.rgb, diffuse.rgb * 1.12, 0.32 + (1.0 - transmissionFactor) * 0.22); diffuseColor.rgb += vec3(0.08, 0.12, 0.18) * fresnel * 0.18; - diffuseColor.a = clamp(diffuseColor.a + foam * 0.16, 0.0, 1.0); + diffuseColor.a = 1.0; }` ); }; diff --git a/tests/domain/water-material.test.ts b/tests/domain/water-material.test.ts index e32375dd..73137619 100644 --- a/tests/domain/water-material.test.ts +++ b/tests/domain/water-material.test.ts @@ -1,6 +1,7 @@ +import { MeshPhysicalMaterial } from "three"; import { describe, expect, it } from "vitest"; -import { collectWaterContactPatches } from "../../src/rendering/water-material"; +import { collectWaterContactPatches, createWaterMaterial } from "../../src/rendering/water-material"; describe("water material helpers", () => { it("builds contact foam patches for bounds that cross the water surface", () => { @@ -82,4 +83,30 @@ describe("water material helpers", () => { expect(patches).toHaveLength(0); }); + + it("keeps quality water visibly tinted instead of fading to transparent alpha", () => { + const result = createWaterMaterial({ + colorHex: "#4da6d9", + surfaceOpacity: 0.55, + waveStrength: 0.35, + opacity: 0.71, + quality: true, + wireframe: false, + isTopFace: true, + time: 0, + halfSize: { + x: 4, + z: 4 + }, + contactPatches: [] + }); + + expect(result.material).toBeInstanceOf(MeshPhysicalMaterial); + + const material = result.material as MeshPhysicalMaterial; + expect(material.opacity).toBe(1); + expect(material.transmission).toBeGreaterThan(0.16); + expect(material.transmission).toBeLessThan(0.72); + expect(material.emissiveIntensity).toBeGreaterThan(0.16); + }); }); \ No newline at end of file