Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 12 additions & 24 deletions src/rendering/tsl/WaterMaterial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
* Designed to work with UnifiedWaterMesh.
*
* Features:
* - Physically-correct fresnel via IOR (no manual calculation)
* - Animated multi-layer normal maps for wave detail
* - Depth-based shallow/deep color blending
* - Quality-scaled wave distortion
* - PBR fresnel and specular via MeshStandardNodeMaterial
*
* Architecture:
* - Uses MeshPhysicalNodeMaterial for proper PBR with IOR support
* - Uses MeshStandardNodeMaterial for consistent PBR rendering
* - colorNode provides BASE color only (no manual lighting)
* - normalNode provides actual normals in [-1,1] range
* - normalNode provides normals in [0,1] encoded range (TSL convention)
* - PBR pipeline handles fresnel, specular, environment reflections
*/

Expand All @@ -35,7 +35,7 @@ import {
attribute,
time,
} from 'three/tsl';
import { MeshPhysicalNodeMaterial } from 'three/webgpu';
import { MeshStandardNodeMaterial } from 'three/webgpu';
import type { WaterQuality } from '@/rendering/water/UnifiedWaterMesh';

/**
Expand Down Expand Up @@ -112,15 +112,12 @@ const QUALITY_SETTINGS: Record<WaterQuality, QualitySettings> = {
},
};

// Water Index of Refraction - physically correct value
const WATER_IOR = 1.33;

/**
* TSL Water Material class
* Provides animated water surface with physically-based rendering
*/
export class TSLWaterMaterial {
public material: MeshPhysicalNodeMaterial;
public material: MeshStandardNodeMaterial;

// Uniforms for runtime updates
private uTimeScale = uniform(1.0);
Expand Down Expand Up @@ -155,9 +152,8 @@ export class TSLWaterMaterial {
private createMaterial(
config: WaterMaterialConfig,
settings: QualitySettings
): MeshPhysicalNodeMaterial {
// Use MeshPhysicalNodeMaterial for IOR support (proper fresnel)
const material = new MeshPhysicalNodeMaterial();
): MeshStandardNodeMaterial {
const material = new MeshStandardNodeMaterial();

// Scaled time for animation
const scaledTime = time.mul(this.uTimeScale);
Expand Down Expand Up @@ -211,8 +207,8 @@ export class TSLWaterMaterial {
const ny = wave2.mul(0.025).add(wave3.mul(0.012)).mul(distortion);
const nz = float(1.0);

// Return actual normal vector in [-1,1] range
return normalize(vec3(nx, ny, nz));
// TSL normalNode expects [0,1] encoded normals (same as normal map textures)
return normalize(vec3(nx, ny, nz)).mul(0.5).add(0.5);
}

// Texture-based normals with two scrolling layers
Expand Down Expand Up @@ -249,8 +245,8 @@ export class TSLWaterMaterial {
blended.z
);

// Return actual normal vector in [-1,1] range
return normalize(scaled);
// TSL normalNode expects [0,1] encoded normals
return normalize(scaled).mul(0.5).add(0.5);
})();

// =========================================================================
Expand All @@ -276,14 +272,6 @@ export class TSLWaterMaterial {
// Water is a dielectric (non-metallic)
material.metalnessNode = float(0.0);

// IOR for physically-correct fresnel (water = 1.33)
// This replaces manual fresnel calculation entirely
// Note: ior is a regular property, not a node property
material.ior = WATER_IOR;

// Note: Environment map reflections are handled by the scene's environment
// rather than per-material envMap in TSL node materials

// Opaque rendering for proper depth sorting with terrain
material.transparent = false;
material.side = THREE.FrontSide; // Water viewed from above
Expand Down Expand Up @@ -337,7 +325,7 @@ export class TSLWaterMaterial {
/**
* Get the underlying Three.js material
*/
public getMaterial(): MeshPhysicalNodeMaterial {
public getMaterial(): MeshStandardNodeMaterial {
return this.material;
}

Expand Down
Loading