Skip to content

Commit 5fe3ed0

Browse files
committed
expose some water settings
1 parent 7125da4 commit 5fe3ed0

File tree

7 files changed

+72
-24
lines changed

7 files changed

+72
-24
lines changed

index.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
<title>WebGPU Water</title>
1313
<link rel="preconnect" href="https://fonts.googleapis.com" />
1414
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
15-
<link rel="preload" href="https://fonts.gstatic.com/s/materialicons/v142/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2" as="font" type="font/woff2" crossorigin />
1615
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
1716
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
1817
<style type="text/css">

src/main.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ async function init(): Promise<void> {
186186
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
187187
});
188188

189+
// Water rendering uniforms (density, causticIntensity, ior, fresnelMin)
190+
const waterUniformBuffer = device.createBuffer({
191+
size: 16,
192+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
193+
});
194+
189195
// --- Lighting ---
190196

191197
/** Current light direction (normalized) */
@@ -228,6 +234,7 @@ async function init(): Promise<void> {
228234
lightUniformBuffer,
229235
sphereUniformBuffer,
230236
shadowUniformBuffer,
237+
waterUniformBuffer,
231238
tileTexture,
232239
tileSampler,
233240
skyTexture,
@@ -244,8 +251,6 @@ async function init(): Promise<void> {
244251
const radius = 0.25;
245252
/** Current sphere velocity */
246253
let velocity = new Vector();
247-
/** Gravity acceleration vector */
248-
const gravity = new Vector(0, -15, 0);
249254
/** Whether physics (gravity/buoyancy) is enabled */
250255
let useSpherePhysics = false;
251256
/** Whether simulation is paused */
@@ -255,6 +260,7 @@ async function init(): Promise<void> {
255260
const gui = new GUI({ title: 'Settings' });
256261
gui.close(); // Collapse by default
257262

263+
const waterFolder = gui.addFolder('Water');
258264
const objectFolder = gui.addFolder('Object');
259265
const sceneFolder = gui.addFolder('Scene');
260266

@@ -264,6 +270,9 @@ async function init(): Promise<void> {
264270
object: 'Sphere',
265271
useDensity: false,
266272
density: 0.9,
273+
causticsIntensity: 0.2,
274+
ior: 1.333,
275+
fresnelMin: 0.25,
267276
};
268277

269278
objectFolder
@@ -287,7 +296,7 @@ async function init(): Promise<void> {
287296
(document.activeElement as HTMLElement)?.blur();
288297
});
289298

290-
const densityCheckbox = objectFolder
299+
objectFolder
291300
.add(settings, 'useDensity')
292301
.name('Enable Density')
293302
.onChange(() => {
@@ -319,6 +328,27 @@ async function init(): Promise<void> {
319328
(document.activeElement as HTMLElement)?.blur();
320329
});
321330

331+
waterFolder
332+
.add(settings, 'causticsIntensity', 0.0, 1.0, 0.01)
333+
.name('Caustics')
334+
.onChange(() => {
335+
(document.activeElement as HTMLElement)?.blur();
336+
});
337+
338+
waterFolder
339+
.add(settings, 'ior', 1.0, 1.5, 0.001)
340+
.name('Refraction')
341+
.onChange(() => {
342+
(document.activeElement as HTMLElement)?.blur();
343+
});
344+
345+
waterFolder
346+
.add(settings, 'fresnelMin', 0.0, 1.0, 0.01)
347+
.name('Reflection')
348+
.onChange(() => {
349+
(document.activeElement as HTMLElement)?.blur();
350+
});
351+
322352
// Initialize sphere position
323353
sphere.update(center.toArray(), radius);
324354

@@ -661,6 +691,15 @@ async function init(): Promise<void> {
661691
updateLight();
662692
}
663693

694+
// Update water rendering uniforms (density, caustics, IOR, fresnel)
695+
// Do this before simulation steps so caustics update uses correct values
696+
water.updateWaterParameters(
697+
settings.useDensity ? settings.density : 0.0,
698+
settings.causticsIntensity,
699+
settings.ior,
700+
settings.fresnelMin
701+
);
702+
664703
if (!paused) {
665704
// --- Physics Update ---
666705

@@ -724,10 +763,6 @@ async function init(): Promise<void> {
724763
water.updateCaustics();
725764
}
726765

727-
// Update water rendering uniforms (density)
728-
// If density is disabled, we use 0 to disable absorption effect in shader
729-
water.updateDensity(settings.useDensity ? settings.density : 0.0);
730-
731766
// Update camera uniforms
732767
updateUniforms();
733768

src/shaders/common/bindings.wgsl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ struct ShadowUniforms {
2727
// Water rendering uniforms
2828
struct WaterUniforms {
2929
density : f32,
30+
causticIntensity : f32,
31+
ior : f32,
32+
fresnelMin : f32,
3033
}
3134

3235
// Pool uniforms (camera matrices and eye position)

src/shaders/water/caustics.frag.wgsl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
@binding(0) @group(0) var<uniform> light : LightUniforms;
66
@binding(1) @group(0) var<uniform> sphere : SphereUniforms;
77
@binding(4) @group(0) var<uniform> shadows : ShadowUniforms;
8+
@binding(5) @group(0) var<uniform> water : WaterUniforms;
89
@binding(2) @group(0) var waterSampler : sampler;
910
@binding(3) @group(0) var waterTexture : texture_2d<f32>;
1011

@@ -15,7 +16,7 @@ fn fs_main(@location(0) oldPos : vec3f, @location(1) newPos : vec3f, @location(2
1516
let oldArea = length(dpdx(oldPos)) * length(dpdy(oldPos));
1617
let newArea = length(dpdx(newPos)) * length(dpdy(newPos));
1718

18-
var intensity = oldArea / newArea * 0.2;
19+
var intensity = oldArea / newArea * water.causticIntensity;
1920

2021
// Calculate sphere shadow
2122
let IOR_AIR = 1.0;

src/shaders/water/surface-above.frag.wgsl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
// Physical constants
1919
const IOR_AIR : f32 = 1.0;
20-
const IOR_WATER : f32 = 1.333;
2120
const ABOVEwaterColor : vec3f = vec3f(0.25, 1.0, 1.25);
2221
const UNDERwaterColor : vec3f = vec3f(0.4, 0.9, 1.0);
2322

@@ -103,6 +102,7 @@ fn getWallColor(point: vec3f, IOR_AIR: f32, IOR_WATER: f32, poolHeight: f32) ->
103102
fn getSurfaceRayColor(origin: vec3f, ray: vec3f, waterColor: vec3f) -> vec3f {
104103
var color : vec3f;
105104
let poolHeight = 1.0;
105+
let IOR_WATER = waterUniforms.ior;
106106

107107
// Check sphere intersection first (only if sphere is enabled)
108108
var q = 1.0e6;
@@ -160,8 +160,8 @@ fn fs_main(@location(0) worldPos : vec3f) -> @location(0) vec4f {
160160

161161
// ABOVE WATER VIEW: Looking down at water surface
162162
let reflectedRay = reflect(incomingRay, normal);
163-
let refractedRay = refract(incomingRay, normal, IOR_AIR / IOR_WATER);
164-
let fresnel = mix(0.25, 1.0, pow(1.0 - dot(normal, -incomingRay), 3.0));
163+
let refractedRay = refract(incomingRay, normal, IOR_AIR / waterUniforms.ior);
164+
let fresnel = mix(waterUniforms.fresnelMin, 1.0, pow(1.0 - dot(normal, -incomingRay), 3.0));
165165

166166
let reflectedColor = getSurfaceRayColor(worldPos, reflectedRay, ABOVEwaterColor);
167167
let refractedColor = getSurfaceRayColor(worldPos, refractedRay, ABOVEwaterColor);

src/shaders/water/surface-under.frag.wgsl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
// Physical constants
1919
const IOR_AIR : f32 = 1.0;
20-
const IOR_WATER : f32 = 1.333;
2120
const ABOVEwaterColor : vec3f = vec3f(0.25, 1.0, 1.25);
2221
const UNDERwaterColor : vec3f = vec3f(0.4, 0.9, 1.0);
2322

@@ -103,6 +102,7 @@ fn getWallColor(point: vec3f, IOR_AIR: f32, IOR_WATER: f32, poolHeight: f32) ->
103102
fn getSurfaceRayColor(origin: vec3f, ray: vec3f, waterColor: vec3f) -> vec3f {
104103
var color : vec3f;
105104
let poolHeight = 1.0;
105+
let IOR_WATER = waterUniforms.ior;
106106

107107
// Check sphere intersection first (only if sphere is enabled)
108108
var q = 1.0e6;
@@ -161,8 +161,8 @@ fn fs_main(@location(0) worldPos : vec3f) -> @location(0) vec4f {
161161
// UNDERWATER VIEW: Looking up at water surface
162162
normal = -normal; // Flip normal for underwater
163163
let reflectedRay = reflect(incomingRay, normal);
164-
let refractedRay = refract(incomingRay, normal, IOR_WATER / IOR_AIR);
165-
let fresnel = mix(0.5, 1.0, pow(1.0 - dot(normal, -incomingRay), 3.0));
164+
let refractedRay = refract(incomingRay, normal, waterUniforms.ior / IOR_AIR);
165+
let fresnel = mix(waterUniforms.fresnelMin, 1.0, pow(1.0 - dot(normal, -incomingRay), 3.0));
166166

167167
let reflectedColor = getSurfaceRayColor(worldPos, reflectedRay, UNDERwaterColor);
168168
let refractedColor = getSurfaceRayColor(worldPos, refractedRay, vec3f(1.0)) * vec3f(0.8, 1.0, 1.1);

src/water.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ export class Water {
159159
* @param lightUniformBuffer - Light direction buffer
160160
* @param sphereUniformBuffer - Sphere position/radius buffer
161161
* @param shadowUniformBuffer - Shadow toggle flags buffer
162+
* @param waterUniformBuffer - Water rendering uniforms buffer
162163
* @param tileTexture - Pool tile texture
163164
* @param tileSampler - Tile texture sampler
164165
* @param skyTexture - Skybox cubemap texture
@@ -172,6 +173,7 @@ export class Water {
172173
lightUniformBuffer: GPUBuffer,
173174
sphereUniformBuffer: GPUBuffer,
174175
shadowUniformBuffer: GPUBuffer,
176+
waterUniformBuffer: GPUBuffer,
175177
tileTexture: GPUTexture,
176178
tileSampler: GPUSampler,
177179
skyTexture: GPUTexture,
@@ -186,17 +188,12 @@ export class Water {
186188
this.lightUniformBuffer = lightUniformBuffer;
187189
this.sphereUniformBuffer = sphereUniformBuffer;
188190
this.shadowUniformBuffer = shadowUniformBuffer;
191+
this.waterUniformBuffer = waterUniformBuffer;
189192
this.tileTexture = tileTexture;
190193
this.tileSampler = tileSampler;
191194
this.skyTexture = skyTexture;
192195
this.skySampler = skySampler;
193196

194-
// Create water uniform buffer (density)
195-
this.waterUniformBuffer = this.device.createBuffer({
196-
size: 16, // density (4) + padding (12)
197-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
198-
});
199-
200197
// Create double-buffered simulation textures
201198
this.textureA = this.createTexture();
202199
this.textureB = this.createTexture();
@@ -764,6 +761,7 @@ export class Water {
764761
{ binding: 2, resource: this.sampler },
765762
{ binding: 3, resource: this.textureA.createView() },
766763
{ binding: 4, resource: { buffer: this.shadowUniformBuffer } },
764+
{ binding: 5, resource: { buffer: this.waterUniformBuffer } },
767765
],
768766
});
769767

@@ -790,11 +788,23 @@ export class Water {
790788
}
791789

792790
/**
793-
* Updates the water density uniform buffer.
791+
* Updates the water rendering uniform buffer.
794792
*
795793
* @param density - Water density (absorption coefficient)
794+
* @param causticIntensity - Intensity of caustics
795+
* @param ior - Index of refraction
796+
* @param fresnelMin - Minimum fresnel reflection
796797
*/
797-
updateDensity(density: number): void {
798-
this.device.queue.writeBuffer(this.waterUniformBuffer, 0, new Float32Array([density]));
798+
updateWaterParameters(
799+
density: number,
800+
causticIntensity: number,
801+
ior: number,
802+
fresnelMin: number
803+
): void {
804+
this.device.queue.writeBuffer(
805+
this.waterUniformBuffer,
806+
0,
807+
new Float32Array([density, causticIntensity, ior, fresnelMin])
808+
);
799809
}
800810
}

0 commit comments

Comments
 (0)