@yali-engine/water 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { cascadeBands, donelanBanner, h0Table, jonswap, jonswapPeak, type SeaState, significantHeight } from './spectrum.ts';
2
- export { water, Water, type WaterOptions } from './water.ts';
2
+ export { bakeShore, type Seabed, type ShoreField } from './shore.ts';
3
+ export { water, Water, type WaterOptions, type WaterQuality } from './water.ts';
3
4
  export { WaterWaves, type WaterWavesOptions } from './waves.ts';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // `@yali-engine/water` (ADR-0198, docs/research/water.md): an ocean's FFT cascades and its surface, as a render feature.
2
2
  export { cascadeBands, donelanBanner, h0Table, jonswap, jonswapPeak, significantHeight } from "./spectrum.js";
3
+ export { bakeShore } from "./shore.js";
3
4
  export { water, Water } from "./water.js";
4
5
  export { WaterWaves } from "./waves.js";
5
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yHAAyH;AACzH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAiB,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC7H,OAAO,EAAE,KAAK,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,UAAU,EAA0B,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yHAAyH;AACzH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAiB,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC7H,OAAO,EAAE,SAAS,EAAgC,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAwC,MAAM,YAAY,CAAC;AAChF,OAAO,EAAE,UAAU,EAA0B,MAAM,YAAY,CAAC"}
@@ -0,0 +1,35 @@
1
+ /** The seabed under the water: heights (world y, metres) on a square grid. */
2
+ export interface Seabed {
3
+ /** `size × size` heights, x fastest, z rows: cell (i, j) is at (origin[0] + i·cell, origin[1] + j·cell). */
4
+ readonly heights: Float32Array;
5
+ readonly size: number;
6
+ /** Metres between samples. */
7
+ readonly cell: number;
8
+ /** World (x, z) of sample (0, 0). */
9
+ readonly origin: readonly [number, number];
10
+ }
11
+ /** The baked field: per cell depth (m), cos and sin of the crest phase, the travel direction (radians from +x towards +z). */
12
+ export interface ShoreField {
13
+ readonly size: number;
14
+ readonly cell: number;
15
+ readonly origin: readonly [number, number];
16
+ /** RGBA per cell: depth, cos φ, sin φ, direction. */
17
+ readonly data: Float32Array;
18
+ /** Arrival times (s), for tests and tools. */
19
+ readonly arrival: Float32Array;
20
+ /**
21
+ * Per cell, the height above the level of the lowest pass between it and the sea (ADR-0216, priority-flood filling):
22
+ * the swash reaches a cell only when it clears this, so a hollow behind a dune stays dry. The depth's negative on
23
+ * land, unless something higher stands between.
24
+ */
25
+ readonly spill: Float32Array;
26
+ }
27
+ /**
28
+ * Bakes the shore field of `seabed` under a sea at `level` for crests `period` seconds apart.
29
+ *
30
+ * @example
31
+ * const field = bakeShore({ heights, size: 256, cell: 1, origin: [-128, -198] }, 0, 8);
32
+ */
33
+ export declare function bakeShore(seabed: Seabed, level: number, period: number): ShoreField;
34
+ /** IEEE half-float bits of `v` (round to nearest even), for an rgba16float upload. */
35
+ export declare function toHalf(v: number): number;
package/dist/shore.js ADDED
@@ -0,0 +1,189 @@
1
+ // The shore field (docs/research/water-realism.md steps 1–2, ADR-0213): what the surf needs to know about the seabed,
2
+ // baked once from its heights on the CPU.
3
+ //
4
+ // depth the water's depth over each cell (the sea's level less the seabed; negative on land)
5
+ // phase when a wave crest that left deep water reaches the cell: the arrival time τ of the eikonal |∇τ| = 1/c with the
6
+ // shallow-water wave speed c = √(g·h), from the deepest water; stored as cos and sin of 2π·τ/T so it filters. Its
7
+ // isochrones are the crests: they bend with the bathymetry and arrive parallel to the beach, as real swell does
8
+ // dir the direction the crests travel (∇τ), as an angle
9
+ //
10
+ // Solved by fast sweeping (Zhao 2005): Godunov upwind updates in four diagonal sweep orders, twice.
11
+ const G = 9.81;
12
+ /** The shallowest depth used for the wave speed: the swash still has a speed where the sand is dry. */
13
+ const MIN_DEPTH = 0.15;
14
+ /**
15
+ * The lowest pass from the sea to every cell (Barnes, Lehman & Mulla 2014, priority-flood): from the open sea's cells
16
+ * (`open`: the deepest water, the crests' sources), each cell's spill height is the max of its own ground and the lowest
17
+ * spill of the cell it was reached from. A pond below the level behind a dune spills at the dune's crest: it is not sea.
18
+ */
19
+ function spillHeights(n, ground, open) {
20
+ const spill = new Float32Array(n * n).fill(Infinity);
21
+ // A binary heap of (height, cell).
22
+ const hh = [];
23
+ const hc = [];
24
+ const push = (h, c) => {
25
+ let i = hh.length;
26
+ hh.push(h);
27
+ hc.push(c);
28
+ while (i > 0) {
29
+ const p = (i - 1) >> 1;
30
+ if (hh[p] <= hh[i])
31
+ break;
32
+ [hh[p], hh[i]] = [hh[i], hh[p]];
33
+ [hc[p], hc[i]] = [hc[i], hc[p]];
34
+ i = p;
35
+ }
36
+ };
37
+ const pop = () => {
38
+ const top = hc[0];
39
+ const lh = hh.pop();
40
+ const lc = hc.pop();
41
+ if (hh.length) {
42
+ hh[0] = lh;
43
+ hc[0] = lc;
44
+ let i = 0;
45
+ for (;;) {
46
+ const a = 2 * i + 1;
47
+ const b = a + 1;
48
+ let m = i;
49
+ if (a < hh.length && hh[a] < hh[m])
50
+ m = a;
51
+ if (b < hh.length && hh[b] < hh[m])
52
+ m = b;
53
+ if (m === i)
54
+ break;
55
+ [hh[m], hh[i]] = [hh[i], hh[m]];
56
+ [hc[m], hc[i]] = [hc[i], hc[m]];
57
+ i = m;
58
+ }
59
+ }
60
+ return top;
61
+ };
62
+ for (let i = 0; i < n * n; i++) {
63
+ if (open(i)) {
64
+ spill[i] = ground[i];
65
+ push(ground[i], i);
66
+ }
67
+ }
68
+ while (hh.length) {
69
+ const c = pop();
70
+ const x = c % n;
71
+ const z = (c - x) / n;
72
+ const s = spill[c];
73
+ for (const [dx, dz] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
74
+ const nx = x + dx;
75
+ const nz = z + dz;
76
+ if (nx < 0 || nz < 0 || nx >= n || nz >= n)
77
+ continue;
78
+ const k = nz * n + nx;
79
+ const h = Math.max(ground[k], s);
80
+ if (h < spill[k]) {
81
+ spill[k] = h;
82
+ push(h, k);
83
+ }
84
+ }
85
+ }
86
+ return spill;
87
+ }
88
+ /**
89
+ * Bakes the shore field of `seabed` under a sea at `level` for crests `period` seconds apart.
90
+ *
91
+ * @example
92
+ * const field = bakeShore({ heights, size: 256, cell: 1, origin: [-128, -198] }, 0, 8);
93
+ */
94
+ export function bakeShore(seabed, level, period) {
95
+ const { size: n, heights } = seabed;
96
+ if (heights.length !== n * n)
97
+ throw Object.assign(new Error(`WATER_SEABED: ${heights.length} heights for a ${n} × ${n} grid`), { code: 'WATER_SEABED', path: 'heights' });
98
+ if (!(seabed.cell > 0))
99
+ throw Object.assign(new Error(`WATER_SEABED: a cell of ${seabed.cell} m`), { code: 'WATER_SEABED', path: 'cell' });
100
+ if (!(period > 0))
101
+ throw Object.assign(new Error(`WATER_SURF: a period of ${period} s`), { code: 'WATER_SURF', path: 'period' });
102
+ const h = seabed.cell;
103
+ // Slowness (s/m) per cell.
104
+ const slow = new Float32Array(n * n);
105
+ let deepest = -Infinity;
106
+ for (let i = 0; i < n * n; i++) {
107
+ const d = level - heights[i];
108
+ slow[i] = 1 / Math.sqrt(G * Math.max(d, MIN_DEPTH));
109
+ deepest = Math.max(deepest, d);
110
+ }
111
+ // Sources: the deepest water (within 10% of the deepest depth): crests start there.
112
+ const t = new Float32Array(n * n).fill(Infinity);
113
+ for (let i = 0; i < n * n; i++)
114
+ if (level - heights[i] >= deepest * 0.9)
115
+ t[i] = 0;
116
+ const update = (x, z) => {
117
+ const i = z * n + x;
118
+ if (t[i] === 0)
119
+ return;
120
+ const a = Math.min(x > 0 ? t[i - 1] : Infinity, x < n - 1 ? t[i + 1] : Infinity);
121
+ const b = Math.min(z > 0 ? t[i - n] : Infinity, z < n - 1 ? t[i + n] : Infinity);
122
+ const f = slow[i] * h;
123
+ let u;
124
+ if (Math.abs(a - b) >= f)
125
+ u = Math.min(a, b) + f;
126
+ else
127
+ u = (a + b + Math.sqrt(2 * f * f - (a - b) * (a - b))) / 2;
128
+ if (u < t[i])
129
+ t[i] = u;
130
+ };
131
+ for (let pass = 0; pass < 2; pass++) {
132
+ for (let z = 0; z < n; z++)
133
+ for (let x = 0; x < n; x++)
134
+ update(x, z);
135
+ for (let z = 0; z < n; z++)
136
+ for (let x = n - 1; x >= 0; x--)
137
+ update(x, z);
138
+ for (let z = n - 1; z >= 0; z--)
139
+ for (let x = 0; x < n; x++)
140
+ update(x, z);
141
+ for (let z = n - 1; z >= 0; z--)
142
+ for (let x = n - 1; x >= 0; x--)
143
+ update(x, z);
144
+ }
145
+ const data = new Float32Array(n * n * 4);
146
+ for (let z = 0; z < n; z++) {
147
+ for (let x = 0; x < n; x++) {
148
+ const i = z * n + x;
149
+ const tau = Number.isFinite(t[i]) ? t[i] : 0;
150
+ const ph = (2 * Math.PI * tau) / period;
151
+ // The travel direction: the arrival time's gradient (central differences, one-sided at the edges).
152
+ const tx = (t[z * n + Math.min(n - 1, x + 1)] - t[z * n + Math.max(0, x - 1)]) || 0;
153
+ const tz = (t[Math.min(n - 1, z + 1) * n + x] - t[Math.max(0, z - 1) * n + x]) || 0;
154
+ data.set([level - heights[i], Math.cos(ph), Math.sin(ph), Math.atan2(tz, tx)], i * 4);
155
+ }
156
+ }
157
+ // The lowest pass to the sea over every cell (heights above the level).
158
+ const ground = new Float32Array(n * n);
159
+ for (let i = 0; i < n * n; i++)
160
+ ground[i] = heights[i] - level;
161
+ return { size: n, cell: h, origin: [seabed.origin[0], seabed.origin[1]], data, arrival: t, spill: spillHeights(n, ground, (i) => t[i] === 0) };
162
+ }
163
+ /** IEEE half-float bits of `v` (round to nearest even), for an rgba16float upload. */
164
+ export function toHalf(v) {
165
+ const f = new Float32Array([v]);
166
+ const x = new Uint32Array(f.buffer)[0];
167
+ const sign = (x >>> 16) & 0x8000;
168
+ const exp = (x >>> 23) & 0xff;
169
+ let mant = x & 0x7fffff;
170
+ if (exp === 0xff)
171
+ return sign | 0x7c00 | (mant ? 0x200 : 0);
172
+ let e = exp - 127 + 15;
173
+ if (e >= 0x1f)
174
+ return sign | 0x7c00;
175
+ if (e <= 0) {
176
+ if (e < -10)
177
+ return sign;
178
+ mant = (mant | 0x800000) >> (1 - e);
179
+ return sign | ((mant + 0x1000) >> 13);
180
+ }
181
+ const half = sign | (e << 10) | (mant >> 13);
182
+ // Round to nearest even on the dropped bits.
183
+ const rest = mant & 0x1fff;
184
+ if (rest > 0x1000 || (rest === 0x1000 && (half & 1)))
185
+ return half + 1;
186
+ e = 0;
187
+ return half;
188
+ }
189
+ //# sourceMappingURL=shore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shore.js","sourceRoot":"","sources":["../src/shore.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  /** Linked from shaders/water/waves.wesl. */
2
2
  export declare const waterWaves: {
3
- readonly hash: "851c72a24f5654c5";
4
- readonly wgsl: "override N: u32 = 128u;\noverride HALF: u32 = 64u;\nconst PI = 3.14159265358979;\nconst G = 9.81;\nstruct WaveParams {\nt: vec4f,\nv: vec4u,\n}\n@group(0) @binding(0) var<storage, read> h0: array<vec4f>;\n@group(0) @binding(1) var<storage, read_write> a: array<vec2f>;\n@group(0) @binding(2) var<storage, read_write> b: array<vec2f>;\n@group(0) @binding(3) var<uniform> wp: WaveParams;\n@group(0) @binding(4) var displacement: texture_storage_2d_array<rgba16float, write>;\n@group(0) @binding(5) var slopes: texture_storage_2d_array<rgba16float, write>;\nfn cmul(x: vec2f, y: vec2f) -> vec2f {\nreturn vec2f(x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x);\n}\nfn ci(x: vec2f) -> vec2f {\nreturn vec2f(-x.y, x.x);\n}\n@compute @workgroup_size(8, 8) fn evolve(@builtin(global_invocation_id) id: vec3u) {\nif (id.x >= N || id.y >= N) {\nreturn;\n}\nlet i = id.y * N + id.x;\nlet size = wp.t.y;\nlet kx = 2.0 * PI * (f32(id.x) - f32(HALF)) / size;\nlet kz = 2.0 * PI * (f32(id.y) - f32(HALF)) / size;\nlet kl = sqrt(kx * kx + kz * kz);\nlet inv = select(0.0, 1.0 / kl, kl > 1e-6);\nlet w = sqrt(G * kl);\nlet e = vec2f(cos(w * wp.t.x), sin(w * wp.t.x));\nlet s = h0[i];\nlet h = cmul(s.xy, e) + cmul(s.zw, vec2f(e.x, -e.y));\nlet dx = -ci(h) * (kx * inv);\nlet dz = -ci(h) * (kz * inv);\nlet sx = ci(h) * kx;\nlet sz = ci(h) * kz;\nlet dxx = h * (kx * kx * inv);\nlet dzz = h * (kz * kz * inv);\nlet dxz = h * (kx * kz * inv);\nlet nn = N * N;\na[i] = h + ci(dx);\na[nn + i] = dz + ci(sx);\na[2u * nn + i] = sz + ci(dxx);\na[3u * nn + i] = dzz + ci(dxz);\n}\nvar<workgroup> line: array<vec2f, 2u * N>;\n@compute @workgroup_size(HALF) fn fft(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) t: u32) {\nlet field = wg.y * N * N;\nlet l = wg.x;\nlet rows = wp.v.x == 0u;\nlet i0 = select(t * N + l, l * N + t, rows) + field;\nlet i1 = select((t + HALF) * N + l, l * N + t + HALF, rows) + field;\nif (rows) {\nline[t] = a[i0];\nline[t + HALF] = a[i1];\n} else {\nline[t] = b[i0];\nline[t + HALF] = b[i1];\n}\nworkgroupBarrier();\nvar src = 0u;\nfor (var q = 1u; q < N; q <<= 1u) {\nlet k = t & (q - 1u);\nlet x = line[src * N + t];\nlet y = line[src * N + t + HALF];\nlet ang = PI * f32(k) / f32(q);\nlet yw = cmul(y, vec2f(cos(ang), sin(ang)));\nlet j = ((t - k) << 1u) + k;\nline[(1u - src) * N + j] = x + yw;\nline[(1u - src) * N + j + q] = x - yw;\nsrc = 1u - src;\nworkgroupBarrier();\n}\nif (rows) {\nb[i0] = line[src * N + t];\nb[i1] = line[src * N + t + HALF];\n} else {\na[i0] = line[src * N + t];\na[i1] = line[src * N + t + HALF];\n}\n}\n@compute @workgroup_size(8, 8) fn pack(@builtin(global_invocation_id) id: vec3u) {\nif (id.x >= N || id.y >= N) {\nreturn;\n}\nlet i = id.y * N + id.x;\nlet nn = N * N;\nlet sign = select(1.0, -1.0, ((id.x + id.y) & 1u) == 1u);\nlet f0 = a[i] * sign;\nlet f1 = a[nn + i] * sign;\nlet f2 = a[2u * nn + i] * sign;\nlet f3 = a[3u * nn + i] * sign;\nlet lambda = wp.t.z;\nlet h = f0.x;\nlet dx = f0.y * lambda;\nlet dz = f1.x * lambda;\nlet jx = 1.0 + lambda * f2.y;\nlet jz = 1.0 + lambda * f3.x;\nlet jxz = lambda * f3.y;\nlet det = jx * jz - jxz * jxz;\nlet layer = i32(wp.v.y);\ntextureStore(displacement, vec2i(id.xy), layer, vec4f(dx, h, dz, det));\ntextureStore(slopes, vec2i(id.xy), layer, vec4f(f1.y, f2.x, det, 0.0));\n}";
3
+ readonly hash: "206e70360bd13637";
4
+ readonly wgsl: "override N: u32 = 128u;\noverride HALF: u32 = 64u;\nconst PI = 3.14159265358979;\nconst G = 9.81;\nstruct WaveParams {\nt: vec4f,\nv: vec4u,\n}\n@group(0) @binding(0) var<storage, read> h0: array<vec4f>;\n@group(0) @binding(1) var<storage, read_write> a: array<vec2f>;\n@group(0) @binding(2) var<storage, read_write> b: array<vec2f>;\n@group(0) @binding(3) var<uniform> wp: WaveParams;\n@group(0) @binding(4) var displacement: texture_storage_2d_array<rgba16float, write>;\n@group(0) @binding(5) var slopes: texture_storage_2d_array<rgba16float, write>;\n@group(0) @binding(6) var<storage, read_write> foam: array<f32>;\nconst FOAM_LIFE = 2.2;\nfn cmul(x: vec2f, y: vec2f) -> vec2f {\nreturn vec2f(x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x);\n}\nfn ci(x: vec2f) -> vec2f {\nreturn vec2f(-x.y, x.x);\n}\n@compute @workgroup_size(8, 8) fn evolve(@builtin(global_invocation_id) id: vec3u) {\nif (id.x >= N || id.y >= N) {\nreturn;\n}\nlet i = id.y * N + id.x;\nlet size = wp.t.y;\nlet kx = 2.0 * PI * (f32(id.x) - f32(HALF)) / size;\nlet kz = 2.0 * PI * (f32(id.y) - f32(HALF)) / size;\nlet kl = sqrt(kx * kx + kz * kz);\nlet inv = select(0.0, 1.0 / kl, kl > 1e-6);\nlet w = sqrt(G * kl);\nlet e = vec2f(cos(w * wp.t.x), sin(w * wp.t.x));\nlet s = h0[i];\nlet h = cmul(s.xy, e) + cmul(s.zw, vec2f(e.x, -e.y));\nlet dx = -ci(h) * (kx * inv);\nlet dz = -ci(h) * (kz * inv);\nlet sx = ci(h) * kx;\nlet sz = ci(h) * kz;\nlet dxx = h * (kx * kx * inv);\nlet dzz = h * (kz * kz * inv);\nlet dxz = h * (kx * kz * inv);\nlet nn = N * N;\na[i] = h + ci(dx);\na[nn + i] = dz + ci(sx);\na[2u * nn + i] = sz + ci(dxx);\na[3u * nn + i] = dzz + ci(dxz);\n}\nvar<workgroup> line: array<vec2f, 2u * N>;\n@compute @workgroup_size(HALF) fn fft(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) t: u32) {\nlet field = wg.y * N * N;\nlet l = wg.x;\nlet rows = wp.v.x == 0u;\nlet i0 = select(t * N + l, l * N + t, rows) + field;\nlet i1 = select((t + HALF) * N + l, l * N + t + HALF, rows) + field;\nif (rows) {\nline[t] = a[i0];\nline[t + HALF] = a[i1];\n} else {\nline[t] = b[i0];\nline[t + HALF] = b[i1];\n}\nworkgroupBarrier();\nvar src = 0u;\nfor (var q = 1u; q < N; q <<= 1u) {\nlet k = t & (q - 1u);\nlet x = line[src * N + t];\nlet y = line[src * N + t + HALF];\nlet ang = PI * f32(k) / f32(q);\nlet yw = cmul(y, vec2f(cos(ang), sin(ang)));\nlet j = ((t - k) << 1u) + k;\nline[(1u - src) * N + j] = x + yw;\nline[(1u - src) * N + j + q] = x - yw;\nsrc = 1u - src;\nworkgroupBarrier();\n}\nif (rows) {\nb[i0] = line[src * N + t];\nb[i1] = line[src * N + t + HALF];\n} else {\na[i0] = line[src * N + t];\na[i1] = line[src * N + t + HALF];\n}\n}\n@compute @workgroup_size(8, 8) fn pack(@builtin(global_invocation_id) id: vec3u) {\nif (id.x >= N || id.y >= N) {\nreturn;\n}\nlet i = id.y * N + id.x;\nlet nn = N * N;\nlet sign = select(1.0, -1.0, ((id.x + id.y) & 1u) == 1u);\nlet f0 = a[i] * sign;\nlet f1 = a[nn + i] * sign;\nlet f2 = a[2u * nn + i] * sign;\nlet f3 = a[3u * nn + i] * sign;\nlet lambda = wp.t.z;\nlet h = f0.x;\nlet dx = f0.y * lambda;\nlet dz = f1.x * lambda;\nlet jx = 1.0 + lambda * f2.y;\nlet jz = 1.0 + lambda * f3.x;\nlet jxz = lambda * f3.y;\nlet det = jx * jz - jxz * jxz;\nlet layer = i32(wp.v.y);\nlet fi = wp.v.y * nn + i;\nlet made = saturate((bitcast<f32>(wp.v.w) - det) * 2.0);\nlet kept = foam[fi] * exp(-bitcast<f32>(wp.v.z) / FOAM_LIFE);\nlet fo = max(made, kept);\nfoam[fi] = fo;\ntextureStore(displacement, vec2i(id.xy), layer, vec4f(dx, h, dz, det));\ntextureStore(slopes, vec2i(id.xy), layer, vec4f(f1.y, f2.x, det, fo));\n}";
5
5
  };
6
6
  /** Linked from shaders/water/surface.wesl. */
7
7
  export declare const waterSurface: {
8
- readonly hash: "b15f2cb4cff18a4e";
9
- readonly wgsl: "const PI = 3.14159265;\nconst EARTH_RADIUS = 6371000.0;\nconst MAX_CASCADES = 4u;\nstruct SurfaceParams {\ncascade: array<vec4f, MAX_CASCADES>,\ngrid: vec4f,\nsea: vec4f,\nbody: vec4f,\nsss: vec4f,\nsize: vec4f,\noptics: vec4f,\nshore: vec4f,\nmirror: vec4f,\n}\n@group(0) @binding(0) var<uniform> camera: Camera;\n@group(0) @binding(1) var<uniform> lighting: Lighting;\n@group(0) @binding(2) var<uniform> sp: SurfaceParams;\n@group(0) @binding(3) var displacement: texture_2d_array<f32>;\n@group(0) @binding(4) var slopes: texture_2d_array<f32>;\n@group(0) @binding(5) var wrap: sampler;\n@group(0) @binding(6) var env_cube: texture_cube<f32>;\n@group(0) @binding(7) var under: texture_2d<f32>;\n@group(0) @binding(8) var under_depth: texture_2d<f32>;\n@group(0) @binding(9) var clamp_smp: sampler;\n@group(0) @binding(10) var scene_depth: texture_depth_2d;\n@group(0) @binding(11) var depth_out: texture_storage_2d<r32float, write>;\nstruct VsOut {\n@builtin(position) clip: vec4f,\n@location(0) p: vec3f,\n@location(1) rest: vec2f,\n@location(2) height: f32,\n}\nfn ring_radius(i: f32) -> f32 {\nlet k = sp.grid.y;\nreturn sp.grid.x * (pow(1.0 + k, i) - 1.0) / k;\n}\n@vertex fn vs(@builtin(vertex_index) vi: u32) -> VsOut {\nlet segs = u32(sp.grid.w);\nlet quad = vi / 6u;\nlet corner = vi % 6u;\nlet dr = select(select(0u, 1u, corner == 1u || corner == 2u || corner == 4u), 0u, corner == 0u || corner == 3u);\nlet ds = select(0u, 1u, corner == 2u || corner == 4u || corner == 5u);\nlet ring = f32(quad / segs + dr);\nlet seg = f32(quad % segs + ds);\nlet r = ring_radius(ring);\nlet a = 2.0 * PI * seg / f32(segs);\nlet rest = vec2f(cos(a), sin(a)) * r;\nlet spacing = max(ring_radius(ring + 1.0) - r, 2.0 * PI * r / f32(segs));\nvar d = vec3f(0.0);\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet texel = 1.0 / (cc.z * sp.size.x);\nlet w = 1.0 - smoothstep(1.0, 4.0, spacing / texel);\nif (w <= 0.0) {\ncontinue;\n}\nd += textureSampleLevel(displacement, wrap, cc.xy + rest * cc.z, c, 0.0).xyz * w;\n}\nlet drop = dot(rest, rest) / (2.0 * EARTH_RADIUS);\nlet p = vec3f(rest.x + d.x, sp.sea.x + d.y - drop, rest.y + d.z);\nvar o: VsOut;\no.clip = camera.view_proj * vec4f(p, 1.0);\no.p = p;\no.rest = rest;\no.height = d.y;\nreturn o;\n}\nfn sh_irradiance(n: vec3f) -> vec3f {\nreturn max(lighting.sh[0].rgb * 0.282095 + lighting.sh[1].rgb * (0.488603 * n.y) + lighting.sh[2].rgb * (0.488603 * n.z) + lighting.sh[3].rgb * (0.488603 * n.x) + lighting.sh[4].rgb * (1.092548 * n.x * n.y) + lighting.sh[5].rgb * (1.092548 * n.y * n.z) + lighting.sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0)) + lighting.sh[7].rgb * (1.092548 * n.x * n.z) + lighting.sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y)), vec3f(0.0));\n}\n@compute @workgroup_size(8, 8) fn copy_depth(@builtin(global_invocation_id) id: vec3u) {\nlet size = textureDimensions(scene_depth);\nif (id.x >= size.x || id.y >= size.y) {\nreturn;\n}\ntextureStore(depth_out, vec2i(id.xy), vec4f(textureLoad(scene_depth, vec2i(id.xy), 0), 0.0, 0.0, 0.0));\n}\nfn thickness(dist: f32, ds: f32, dop: f32) -> f32 {\nreturn select(1.0e9, dist * max(ds / dop - 1.0, 0.0), dop > 0.0);\n}\nfn mirror_scene(p: vec3f, r: vec3f, size: vec2f) -> vec4f {\nlet len = sp.mirror.x;\nlet steps = u32(sp.mirror.y);\nif (len <= 0.0 || steps == 0u || sp.shore.y < 0.5 || r.y <= 0.0) {\nreturn vec4f(0.0);\n}\nfor (var i = 1u; i <= steps; i++) {\nlet f = f32(i) / f32(steps);\nlet q = p + r * (len * f * f);\nlet c = camera.view_proj * vec4f(q, 1.0);\nif (c.w <= 0.0) {\nbreak;\n}\nlet ndc = c.xyz / c.w;\nlet uv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nbreak;\n}\nlet d = textureLoad(under_depth, vec2i(uv * size), 0).x;\nif (d > ndc.z && d > 0.0) {\nlet gap = c.w * (1.0 - ndc.z / d);\nif (gap < sp.mirror.z + 0.03 * c.w) {\nlet edge = min(min(uv.x, 1.0 - uv.x), min(uv.y, 1.0 - uv.y));\nlet fade = saturate(edge * 12.0) * (1.0 - f * f);\nreturn vec4f(textureSampleLevel(under, clamp_smp, uv, 0.0).rgb * sp.shore.z, fade);\n}\n}\n}\nreturn vec4f(0.0);\n}\n@fragment fn fs(in: VsOut) -> @location(0) vec4f {\nlet frag = in.clip;\nvar sx = 0.0;\nvar sz = 0.0;\nvar lost = 0.0;\nvar fold = 0.0;\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet uv = cc.xy + in.rest * cc.z;\nlet texels = length(fwidth(uv)) * sp.size.x;\nlet w = 1.0 - smoothstep(0.5, 2.0, texels);\nlet s = textureSampleLevel(slopes, wrap, uv, c, 0.0);\nsx += s.x * w;\nsz += s.y * w;\nlost += cc.w * (1.0 - w);\nfold = max(fold, saturate((sp.sea.z - s.z) * 2.0) * w);\n}\nlet n = normalize(vec3f(-sx, 1.0, -sz));\nlet v = normalize(-in.p);\nlet l = camera.light_dir.xyz;\nlet nv = max(dot(n, v), 1e-3);\nlet nl = max(dot(n, l), 0.0);\nlet a2 = max(sp.body.w * sp.body.w + 2.0 * lost, 1e-4);\nlet f0 = 0.02;\nlet fresnel = f0 + (1.0 - f0) * pow(1.0 - nv, 5.0);\nvar r = reflect(-v, n);\nr.y = abs(r.y);\nlet lod = clamp(sqrt(sqrt(a2)) * (lighting.env.x - 1.0), 0.0, lighting.env.x - 1.0);\nvar sky = textureSampleLevel(env_cube, wrap, r, lod).rgb * lighting.view.rgb;\nlet seen = mirror_scene(in.p, reflect(-v, n), vec2f(textureDimensions(under)));\nsky = mix(sky, seen.rgb, seen.a);\nif (sp.mirror.w > 0.5) {\nreturn vec4f(select(vec3f(seen.a * 0.2, 0.0, 0.0) + seen.rgb * seen.a, vec3f(0.3, 0.0, 0.0), seen.a <= 0.0) * lighting.env.z, 1.0);\n}\nlet h = normalize(v + l);\nlet nh = max(dot(n, h), 0.0);\nlet dd = nh * nh * (a2 - 1.0) + 1.0;\nlet ggx = a2 / (PI * dd * dd);\nlet vis = 0.5 / (nl * sqrt(nv * nv * (1.0 - a2) + a2) + nv * sqrt(nl * nl * (1.0 - a2) + a2) + 1e-5);\nlet fh = f0 + (1.0 - f0) * pow(1.0 - max(dot(v, h), 0.0), 5.0);\nlet sun = lighting.sun_color.rgb;\nlet spec = sun * (ggx * vis * fh * nl);\nlet up = sh_irradiance(vec3f(0.0, 1.0, 0.0));\nlet body = sp.body.rgb * (up + sun * (max(l.y, 0.0) / PI) * 0.5);\nlet behind = pow(saturate(dot(-v, l)), 8.0);\nlet crest = saturate(in.height * 0.5) * saturate(1.0 - n.y * 0.9 + 0.2);\nlet sss = sp.sss.rgb * sun * (behind * crest * sp.sss.w);\nvar below = body;\nvar shore_foam = 0.0;\nif (sp.shore.y > 0.5) {\nlet px = vec2i(frag.xy);\nlet size = vec2f(textureDimensions(under));\nlet dist = length(in.p);\nlet t0 = thickness(dist, frag.z, textureLoad(under_depth, px, 0).x);\nlet bend = n.xz * sp.optics.w * min(t0, 1.0);\nvar uv = (frag.xy + bend) / size;\nvar t = thickness(dist, frag.z, textureLoad(under_depth, vec2i(uv * size), 0).x);\nif (textureLoad(under_depth, vec2i(uv * size), 0).x > frag.z) {\nuv = frag.xy / size;\nt = t0;\n}\nlet behind_light = textureSampleLevel(under, clamp_smp, uv, 0.0).rgb * sp.shore.z;\nlet trans = exp(-sp.optics.rgb * t);\nbelow = behind_light * trans + body * (vec3f(1.0) - trans);\nlet ripple = textureSampleLevel(displacement, wrap, sp.cascade[u32(sp.sea.y) - 1u].xy + in.rest * sp.cascade[u32(sp.sea.y) - 1u].z, u32(sp.sea.y) - 1u, 0.0).y;\nshore_foam = saturate((1.0 - t0 / sp.shore.x) * 1.4 + ripple * 2.0) * saturate(t0 * 8.0);\n}\nlet water = sky * fresnel + (below + sss) * (1.0 - fresnel) + spec;\nlet foam_light = (sh_irradiance(n) + sun * (nl / PI)) * 0.85;\nlet foam = saturate(fold * sp.sea.w + shore_foam);\nlet lit = mix(water, foam_light, foam);\nlet fogged = apply_fog(lit, in.p, lighting.fog, lighting.fog2, lighting.fog_air.rgb, sh_irradiance(normalize(in.p)), sun, l);\nreturn vec4f(fogged * lighting.env.z, 1.0);\n}\nstruct Camera {\nview_proj: mat4x4f,\nfrustum: array<vec4f, 6>,\nlight_dir: vec4f,\norigin: vec4f,\n}\nstruct Lighting {\nsun_color: vec4f,\nenv: vec4f,\nsh: array<vec4f, 9>,\nscreen: vec4f,\nview: vec4f,\nao_reproject: mat4x4f,\nao: vec4f,\nfog: vec4f,\nfog2: vec4f,\nfog_air: vec4f,\ngi_grid: vec4f,\ngi_counts: vec4f,\nssr: vec4f,\ngi_cascades: array<vec4f, 3>,\n}\nfn apply_fog(color: vec3f, p: vec3f, fog: vec4f, fog2: vec4f, air: vec3f, ambient: vec3f, sun: vec3f, sun_dir: vec3f) -> vec3f {\nlet dist = length(p);\nif (dist <= 0.0 || (fog.x <= 0.0 && fog2.x <= 0.0)) {\nreturn color;\n}\nlet dir = p / dist;\nlet t = exp(-(air * (fog2.x * dist) + vec3f(height_fog_depth(fog, dir, dist))));\nreturn color * t + fog_inscatter(fog2, dir, ambient, sun, sun_dir) * (vec3f(1.0) - t);\n}\nfn height_fog_depth(fog: vec4f, dir: vec3f, dist: f32) -> f32 {\nif (fog.x <= 0.0) {\nreturn 0.0;\n}\nlet falloff = max(fog.y, 1e-5);\nlet len = max(dist - fog.w, 0.0);\nlet h0 = fog.z + dir.y * min(dist, fog.w);\nlet k = falloff * dir.y * len;\nlet shape = select((1.0 - exp(-k)) / k, 1.0 - 0.5 * k, abs(k) < 1e-3);\nreturn fog.x * exp(-falloff * h0) * len * shape;\n}\nfn fog_inscatter(fog2: vec4f, dir: vec3f, ambient: vec3f, sun: vec3f, sun_dir: vec3f) -> vec3f {\nlet g = fog2.z;\nlet c = dot(dir, sun_dir);\nlet phase = (1.0 - g * g) / (FOUR_PI * pow(max(1.0 + g * g - 2.0 * g * c, 1e-4), 1.5));\nreturn ambient + sun * (phase * fog2.y);\n}\nconst FOUR_PI = 12.5663706;";
8
+ readonly hash: "0d0f44afc28096f2";
9
+ readonly wgsl: "const PI = 3.14159265;\nconst EARTH_RADIUS = 6371000.0;\nconst MAX_CASCADES = 4u;\nstruct SurfaceParams {\ncascade: array<vec4f, MAX_CASCADES>,\ngrid: vec4f,\nsea: vec4f,\nbody: vec4f,\nsss: vec4f,\nsize: vec4f,\noptics: vec4f,\nshore: vec4f,\nmirror: vec4f,\nsurf0: vec4f,\nsurf1: vec4f,\nsurf2: vec4f,\nsurf3: vec4f,\n}\n@group(0) @binding(0) var<uniform> camera: Camera;\n@group(0) @binding(1) var<uniform> lighting: Lighting;\n@group(0) @binding(2) var<uniform> sp: SurfaceParams;\n@group(0) @binding(3) var displacement: texture_2d_array<f32>;\n@group(0) @binding(4) var slopes: texture_2d_array<f32>;\n@group(0) @binding(5) var wrap: sampler;\n@group(0) @binding(6) var env_cube: texture_cube<f32>;\n@group(0) @binding(7) var under: texture_2d<f32>;\n@group(0) @binding(8) var under_depth: texture_2d<f32>;\n@group(0) @binding(9) var clamp_smp: sampler;\n@group(0) @binding(10) var scene_depth: texture_depth_2d;\n@group(0) @binding(11) var depth_out: texture_storage_2d<r32float, write>;\nstruct WaterMotion {\nview_proj: mat4x4f,\nprev_view_proj: mat4x4f,\neye_move: vec4f,\n}\n@group(0) @binding(12) var prev_displacement: texture_2d_array<f32>;\n@group(0) @binding(13) var<uniform> wm: WaterMotion;\n@group(0) @binding(14) var shore_map: texture_2d<f32>;\n@group(0) @binding(15) var spill_map: texture_2d<f32>;\n@group(0) @binding(16) var cloud_pano: texture_2d<f32>;\nstruct Surf {\nd: vec3f,\nslope: vec2f,\nbroken: f32,\nfoam: f32,\nfade: f32,\nground: f32,\nreach: f32,\ncover: f32,\nwet: f32,\nfilm: f32,\nfront: f32,\nface: f32,\namp: f32,\nworld: vec2f,\ndir: vec2f,\nphase: f32,\nonset: f32,\nfresh: f32,\nfront_h: f32,\nwet_h1: f32,\nwet_h2: f32,\nrun: f32,\n}\nfn pcg(v: u32) -> u32 {\nlet state = v * 747796405u + 2891336453u;\nlet word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\nreturn (word >> 22u) ^ word;\n}\nfn hash1(n: f32) -> f32 {\nreturn f32(pcg(bitcast<u32>(i32(n)))) / 4294967296.0;\n}\nfn hash2(p: vec2f) -> f32 {\nlet q = vec2<i32>(p);\nreturn f32(pcg(bitcast<u32>(q.x) ^ pcg(bitcast<u32>(q.y) + 0x9e3779b9u))) / 4294967296.0;\n}\nfn vnoise(p: vec2f) -> f32 {\nlet i = floor(p);\nlet f = fract(p);\nlet u = f * f * (3.0 - 2.0 * f);\nreturn mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x), mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);\n}\nfn worley(p: vec2f) -> vec2f {\nlet i = floor(p);\nlet f = fract(p);\nvar d1 = 8.0;\nvar d2 = 8.0;\nfor (var y = -1; y <= 1; y++) {\nfor (var x = -1; x <= 1; x++) {\nlet o = vec2f(f32(x), f32(y));\nlet c = i + o;\nlet j = vec2f(hash2(c), hash2(c + vec2f(1013.0, 7919.0)));\nlet d = length(o + j - f) / (0.55 + 0.9 * hash2(c + vec2f(4241.0, 557.0)));\nif (d < d1) {\nd2 = d1;\nd1 = d;\n} else if (d < d2) {\nd2 = d;\n}\n}\n}\nreturn vec2f(d1, d2);\n}\nfn foam_net(p: vec2f, amount: f32, fw: f32) -> vec2f {\nlet a = saturate(amount);\nlet q = p + vec2f(vnoise(p * 2.0), vnoise(p * 2.0 + vec2f(5.2, 1.3))) * 0.36;\nlet big = worley(q * 3.2);\nvar small = vec2f(0.5, 1.0);\nif (sp.size.w > 1.5) {\nsmall = worley(q * 10.0 + vec2f(3.7, 1.9));\n}\nlet r = mix(0.82, 0.12, pow(a, 1.1));\nlet eb = fw * 3.2;\nlet es = fw * 10.0;\nlet mean = saturate(1.0 - 2.6 * r * r);\nlet hb = mix(smoothstep(r - 0.05 - eb, r + 0.05 + eb, big.x), mean, smoothstep(0.15, 0.6, eb));\nlet rs = r * 0.8 + 0.1;\nlet hs = mix(smoothstep(rs - 0.06 - es, rs + 0.06 + es, small.x), saturate(1.0 - 2.6 * rs * rs), smoothstep(0.15, 0.6, es));\nlet net = hb * mix(1.0, hs, 0.55 * (1.0 - a));\nlet cover = net * smoothstep(0.0, 0.06, a);\nlet lumps = mix(0.8, 1.04, saturate(1.0 - small.x * 0.9)) * mix(0.9, 1.0, saturate(1.0 - big.x * 0.8));\nlet light = mix(lumps, 0.92, smoothstep(0.15, 0.6, es));\nreturn vec2f(cover, light);\n}\nstruct Train {\nth: f32,\nu: f32,\nnw: f32,\namp: f32,\nshoaled: f32,\nperiod: f32,\nblend: f32,\n}\nconst TRAINS = array<vec4f, 3>(vec4f(1.0, 1.0, 0.0, 0.0), vec4f(1.37, 0.45, 0.0, 0.31), vec4f(0.79, 0.3, 0.0, 0.67));\nfn wobble(n: f32, i: u32, world: vec2f) -> f32 {\nlet o = vec2f(n * 0.61 + f32(i) * 7.0, n * 0.37 + f32(i) * 3.0);\nreturn ((vnoise(world / 45.0 + o) - 0.5) * 1.2 + (vnoise(world / 14.0 + o * 1.7) - 0.5) * 0.35) * sp.surf3.y;\n}\nfn wave_amp(n: f32, world: vec2f) -> f32 {\nlet group = 1.0 + 0.3 * sin(2.0 * PI * n / 6.3 + 1.3) + 0.25 * (hash1(n) - 0.5);\nlet section = 0.6 + 0.8 * vnoise(world / 28.0 + vec2f(n * 7.13, n * 3.71));\nreturn mix(1.0, group * section, sp.surf3.y);\n}\nfn swash_front(u: f32, run: f32) -> f32 {\nlet rise = 0.18;\nlet up = 1.0 - (1.0 - saturate(u / rise)) * (1.0 - saturate(u / rise));\nlet down = pow(saturate(1.0 - (u - rise) / 0.57), 1.6);\nreturn run * select(down, up, u < rise);\n}\nfn swash_high(u: f32, run: f32, window: f32) -> f32 {\nlet rise = 0.18;\nlet u0 = u - window;\nif (u0 >= rise) {\nreturn swash_front(u0, run);\n}\nif (u >= rise) {\nreturn run;\n}\nreturn max(swash_front(u, run), select(0.0, swash_front(u0 + 1.0, run), u0 < 0.0));\n}\nfn swash_state(e: f32, front_h: f32, wet_h1: f32, wet_h2: f32, run: f32) -> vec4f {\nlet cover = saturate((front_h - e) / 0.03);\nlet r1 = saturate((wet_h1 - e) / 0.06 + 0.4);\nlet r2 = saturate((wet_h2 - e) / 0.12 + 0.4);\nlet damp = 0.3 * saturate(1.0 - e / max(run, 1e-3));\nlet wet = max(0.75 * (0.55 * r1 + 0.45 * r2), damp) * saturate((run - e) / 0.08 + 0.5);\nlet film = 0.02 + 0.04 * cover * saturate((front_h - e) / 0.15);\nlet front = cover * (1.0 - saturate((front_h - e) / 0.015)) * saturate(front_h * 6.0);\nreturn vec4f(cover, wet, film, front);\n}\nfn surf_at(rest: vec2f, time: f32) -> Surf {\nvar s: Surf;\ns.fade = 1.0;\ns.amp = 1.0;\nif (sp.surf0.w < 0.5) {\nreturn s;\n}\nlet uv = sp.surf0.xy + rest * sp.surf0.z;\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nreturn s;\n}\nlet f = textureSampleLevel(shore_map, clamp_smp, uv, 0.0);\nlet sp2 = textureSampleLevel(spill_map, clamp_smp, uv, 0.0);\nlet depth = f.x;\nlet period = sp.surf1.y;\ns.world = uv / sp.surf0.z;\ns.fade = smoothstep(0.2, sp.surf2.y, depth);\nlet hs = max(depth, 0.15);\nlet start = sp.surf2.z;\nlet shoal = pow(start / hs, 0.25) * smoothstep(start, start * 0.6, depth);\nlet dir = vec2f(cos(f.w), sin(f.w));\ns.dir = dir;\nvar tr: array<Train, 3>;\nlet trains = select(1u, 3u, sp.size.w > 0.5);\nfor (var i = 0u; i < trains; i++) {\nlet ti = TRAINS[i];\nlet pi = period * ti.x;\nvar th: f32;\nvar u: f32;\nvar nw: f32;\nif (i == 0u) {\nth = 2.0 * PI * fract(time / period) - atan2(f.z, f.y);\nu = fract(th / (2.0 * PI));\nnw = round((time - sp2.y) / period + 0.5 - fract(u + 0.5));\n} else {\nlet cyc = (time - sp2.y) / pi + ti.w;\nu = fract(cyc);\nth = 2.0 * PI * u;\nnw = round(cyc + 0.5 - fract(u + 0.5));\n}\nlet ff = fract(u + 0.5);\nlet blend = smoothstep(0.82, 1.0, ff);\nvar amp = wave_amp(nw + f32(i) * 101.0, s.world);\nvar wob = wobble(nw, i, s.world);\nif (blend > 0.0) {\namp = mix(amp, wave_amp(nw + 1.0 + f32(i) * 101.0, s.world), blend);\nwob = mix(wob, wobble(nw + 1.0, i, s.world), blend);\n}\nth -= wob;\ntr[i].th = th;\ntr[i].u = fract(th / (2.0 * PI));\ntr[i].nw = nw;\ntr[i].amp = amp;\ntr[i].shoaled = sp.surf1.x * ti.y * amp * shoal;\ntr[i].blend = blend;\ntr[i].period = pi;\n}\nlet gnoise = mix(vnoise(s.world / 35.0 + vec2f(tr[0].nw * 2.3, 0.0)), vnoise(s.world / 35.0 + vec2f((tr[0].nw + 1.0) * 2.3, 0.0)), tr[0].blend);\nlet gamma = sp.surf1.z * (0.85 + 0.3 * gnoise * sp.surf3.y + 0.15 * (1.0 - sp.surf3.y));\nlet hb = gamma * hs;\nvar eta = 0.0;\nvar lean = vec2f(0.0);\nvar slope = 0.0;\nvar face_k = 0.0;\nvar foam = 0.0;\ns.broken = 0.0;\ns.onset = 0.0;\nfor (var i = 0u; i < trains; i++) {\nvar others = 0.0;\nfor (var j = 0u; j < trains; j++) {\nif (j != i) {\nothers += 0.4 * tr[j].shoaled * (0.5 + 0.5 * cos(tr[j].th));\n}\n}\nlet ratio = (tr[i].shoaled + others) / max(hb, 1e-3);\nlet broken = saturate((ratio - 1.0) * 3.0);\nlet share = tr[i].shoaled / max(tr[i].shoaled + others, 1e-4);\nlet H = min(tr[i].shoaled, hb * share + tr[i].shoaled * (1.0 - broken)) * smoothstep(-0.05, 0.25, depth);\nlet th = tr[i].th;\nlet u = tr[i].u;\nlet wn = mix(vnoise(s.world / 22.0 + vec2f(tr[i].nw * 5.1, f32(i) * 9.0)), vnoise(s.world / 22.0 + vec2f((tr[i].nw + 1.0) * 5.1, f32(i) * 9.0)), tr[i].blend);\nlet w = 0.12 * mix(1.0, 0.6 + 0.8 * wn, sp.surf3.y);\nlet stokes = 0.5 * (cos(th) + 0.3 * cos(2.0 * th));\nlet dstokes = 0.5 * (-sin(th) - 0.6 * sin(2.0 * th));\nlet bore = select(1.0 - (u - w) / (1.0 - w), u / w, u < w) - 0.5;\nlet dw = min(0.6 * w, 1.2 / (tr[i].period * sqrt(9.81 * hs)));\nlet rise = saturate(0.5 + u / dw) * saturate(0.5 - (u - w) / dw) + saturate(0.5 + (u - 1.0) / dw);\nlet dbore = (-1.0 / (1.0 - w) + (1.0 / w + 1.0 / (1.0 - w)) * rise) / (2.0 * PI);\nvar e = H * mix(stokes, bore, broken);\nlet de = H * mix(dstokes, dbore, broken);\nlet q = sp.surf1.w * (1.0 - 0.6 * broken);\nvar l = dir * (q * H * 0.5 * sin(th));\nlet onset = broken * (1.0 - broken) * 4.0 * saturate((ratio - 0.9) * 10.0);\nlet top = smoothstep(0.1, 0.5, e / max(H, 1e-3));\nl += dir * (sp.surf3.z * H * 0.55 * onset * top);\ne *= 1.0 + 0.25 * onset * top;\nlet k = 2.0 * PI / (tr[i].period * sqrt(9.81 * hs));\neta += e;\nlean += l;\nslope += -k * de;\nface_k += smoothstep(-0.2, 0.45, e / max(H, 1e-3)) * saturate(H * 1.2) * (1.0 - 0.65 * broken + 0.5 * onset) * TRAINS[i].y;\nlet fo = broken * smoothstep(0.0, w, u) * (exp(-(u - w) * 9.0) + 0.15 * exp(-(u - w) * 3.0)) * saturate(H * 3.0) * TRAINS[i].y;\nfoam = 1.0 - (1.0 - foam) * (1.0 - fo);\ns.broken = max(s.broken, broken);\ns.onset = max(s.onset, onset * TRAINS[i].y);\n}\ns.slope = slope * dir;\ns.d = vec3f(lean.x, eta, lean.y);\ns.face = saturate(-slope * 1.5) * saturate(face_k);\ns.foam = foam;\nlet u = tr[0].u;\ns.phase = u;\ns.amp = tr[0].amp;\ns.ground = -depth;\ns.reach = max(sp2.x, s.ground);\nif (s.ground > -0.05) {\nlet e = s.reach;\nlet rn = mix(vnoise(s.world / 9.0 + vec2f(tr[0].nw * 3.3, 1.0)), vnoise(s.world / 9.0 + vec2f((tr[0].nw + 1.0) * 3.3, 1.0)), tr[0].blend);\nlet run = sp.surf1.x * s.amp * 0.8 * mix(1.0, 0.7 + 0.6 * rn, sp.surf3.y);\ns.run = run;\ns.front_h = swash_front(u, run);\ns.wet_h1 = swash_high(u, run, 1.5 / sp.surf1.y);\ns.wet_h2 = swash_high(u, run, 4.0 / sp.surf1.y);\nlet w = swash_state(e, s.front_h, s.wet_h1, s.wet_h2, run);\ns.cover = w.x;\ns.wet = w.y;\ns.film = w.z;\ns.front = w.w;\n}\nreturn s;\n}\nfn on_beach(d: vec3f, s: Surf, spacing: f32) -> vec3f {\nif (s.ground <= 0.0) {\nreturn d;\n}\nlet reach = sp.surf1.x * 0.8 * 2.0 + 0.05;\nlet y = select(s.ground - 1.0, s.ground + s.film + 0.03 + 0.03 * spacing, s.reach < reach);\nlet k = smoothstep(0.0, 0.04, s.ground);\nreturn vec3f(d.x * (1.0 - k), mix(d.y, y, k), d.z * (1.0 - k));\n}\n@group(0) @binding(17) var surf_a: texture_2d<f32>;\n@group(0) @binding(18) var surf_b: texture_2d<f32>;\n@group(0) @binding(19) var surf_c: texture_2d<f32>;\n@group(0) @binding(20) var surf_d: texture_2d<f32>;\n@group(0) @binding(21) var surf_a_prev: texture_2d<f32>;\n@group(0) @binding(22) var surf_d_prev: texture_2d<f32>;\n@group(0) @binding(23) var out_a: texture_storage_2d<rgba16float, write>;\n@group(0) @binding(24) var out_b: texture_storage_2d<rgba16float, write>;\n@group(0) @binding(25) var out_c: texture_storage_2d<rgba16float, write>;\n@group(0) @binding(26) var out_d: texture_storage_2d<rgba16float, write>;\n@compute @workgroup_size(8, 8) fn surf_field(@builtin(global_invocation_id) id: vec3u) {\nlet n = textureDimensions(out_a);\nif (any(id.xy >= n)) {\nreturn;\n}\nlet uv = (vec2f(id.xy) + 0.5) / vec2f(n);\nlet rest = (uv - sp.surf0.xy) / sp.surf0.z;\nlet s = surf_at(rest, sp.surf3.x);\ntextureStore(out_a, vec2i(id.xy), vec4f(s.d, s.broken));\ntextureStore(out_b, vec2i(id.xy), vec4f(s.slope, s.face, s.onset));\ntextureStore(out_c, vec2i(id.xy), vec4f(s.foam, s.front_h, s.wet_h1, s.wet_h2));\nlet dt = clamp(sp.surf3.x - sp.surf3.w, 0.0, 0.1);\nlet v = surf_flow(s);\nlet extent = 1.0 / sp.surf0.z;\nlet back = uv - v * dt / extent;\nlet texel = 1.0 / vec2f(n);\nlet f0 = textureSampleLevel(surf_d_prev, clamp_smp, back, 0.0).w;\nlet fs = textureSampleLevel(surf_d_prev, clamp_smp, back + vec2f(texel.x, 0.0), 0.0).w + textureSampleLevel(surf_d_prev, clamp_smp, back - vec2f(texel.x, 0.0), 0.0).w + textureSampleLevel(surf_d_prev, clamp_smp, back + vec2f(0.0, texel.y), 0.0).w + textureSampleLevel(surf_d_prev, clamp_smp, back - vec2f(0.0, texel.y), 0.0).w;\nlet carried = mix(f0, fs * 0.25, 0.12 * saturate(dt * 60.0));\nlet life = mix(3.0, 1.2, saturate(carried));\nvar f = carried * exp(-dt / life) * select(1.0, 0.0, s.ground > 0.0 && s.cover < 0.01 && s.wet < 0.05) * select(0.0, 1.0, dt > 0.0);\nf = max(f, saturate(s.foam * 0.9 + s.front * 0.8));\ntextureStore(out_d, vec2i(id.xy), vec4f(s.run, s.phase, s.amp, f));\n}\nfn smooth_sample(t: texture_2d<f32>, uv: vec2f) -> vec4f {\nlet size = vec2f(textureDimensions(t));\nlet p = uv * size - 0.5;\nlet i = floor(p);\nlet f = p - i;\nlet w0 = (1.0 - f) * (1.0 - f) * (1.0 - f) / 6.0;\nlet w1 = (4.0 - 6.0 * f * f + 3.0 * f * f * f) / 6.0;\nlet w2 = (1.0 + 3.0 * f + 3.0 * f * f - 3.0 * f * f * f) / 6.0;\nlet w3 = f * f * f / 6.0;\nlet g0 = w0 + w1;\nlet g1 = w2 + w3;\nlet h0 = (i - 0.5 + w1 / g0) / size;\nlet h1 = (i + 1.5 + w3 / g1) / size;\nlet a = textureSampleLevel(t, clamp_smp, vec2f(h0.x, h0.y), 0.0);\nlet b = textureSampleLevel(t, clamp_smp, vec2f(h1.x, h0.y), 0.0);\nlet c = textureSampleLevel(t, clamp_smp, vec2f(h0.x, h1.y), 0.0);\nlet d = textureSampleLevel(t, clamp_smp, vec2f(h1.x, h1.y), 0.0);\nreturn mix(mix(a, b, g1.x), mix(c, d, g1.x), g1.y);\n}\nfn surf_tex(rest: vec2f, prev: bool) -> Surf {\nvar s: Surf;\ns.fade = 1.0;\ns.amp = 1.0;\nif (sp.surf0.w < 0.5) {\nreturn s;\n}\nlet uv = sp.surf0.xy + rest * sp.surf0.z;\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nreturn s;\n}\nlet f = textureSampleLevel(shore_map, clamp_smp, uv, 0.0);\nlet depth = f.x;\ns.fade = smoothstep(0.2, sp.surf2.y, depth);\ns.dir = vec2f(cos(f.w), sin(f.w));\ns.world = uv / sp.surf0.z;\ns.ground = -depth;\ns.reach = max(textureSampleLevel(spill_map, clamp_smp, uv, 0.0).x, s.ground);\nlet a = select(textureSampleLevel(surf_a, clamp_smp, uv, 0.0), textureSampleLevel(surf_a_prev, clamp_smp, uv, 0.0), prev);\ns.d = a.xyz;\ns.broken = a.w;\nlet b = textureSampleLevel(surf_b, clamp_smp, uv, 0.0);\ns.slope = b.xy;\ns.face = b.z;\ns.onset = b.w;\nlet c = textureSampleLevel(surf_c, clamp_smp, uv, 0.0);\nlet dd = textureSampleLevel(surf_d, clamp_smp, uv, 0.0);\nif (s.ground > -0.05) {\nlet e = max(smooth_sample(spill_map, uv).x, -smooth_sample(shore_map, uv).x);\nlet w = swash_state(e, c.y, c.z, c.w, dd.x);\ns.cover = w.x;\ns.wet = w.y;\ns.film = w.z;\ns.front = w.w;\n}\ns.front_h = c.y;\ns.run = dd.x;\ns.phase = dd.y;\ns.amp = dd.z;\ns.fresh = c.x;\ns.foam = max(c.x, dd.w);\nreturn s;\n}\nfn surf_disp(rest: vec2f, spacing: f32, prev: bool) -> vec3f {\nif (sp.surf0.w < 0.5) {\nreturn vec3f(0.0);\n}\nlet uv = sp.surf0.xy + rest * sp.surf0.z;\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nreturn vec3f(0.0);\n}\nlet r = 0.35 * spacing * sp.surf0.z;\nvar d = vec3f(0.0);\nfor (var k = 0u; k < 5u; k++) {\nlet o = array<vec2f, 5>(vec2f(0.0), vec2f(r, 0.0), vec2f(-r, 0.0), vec2f(0.0, r), vec2f(0.0, -r))[k];\nlet w = select(0.125, 0.5, k == 0u);\nlet t = select(textureSampleLevel(surf_a, clamp_smp, uv + o, 0.0), textureSampleLevel(surf_a_prev, clamp_smp, uv + o, 0.0), prev);\nd += t.xyz * w;\n}\nreturn d;\n}\nfn surf_flow(s: Surf) -> vec2f {\nlet hs = max(-s.ground, 0.15);\nif (s.ground > -0.05) {\nreturn s.dir * select(-0.9, 1.6, s.phase < 0.18) * s.cover;\n}\nreturn s.dir * clamp(sqrt(9.81 * hs) * max(s.d.y, 0.0) / hs, 0.0, 3.0);\n}\nconst SPRAY_SIDE = 192u;\nconst SPRAY_SPACING = 0.75;\nstruct SprayOut {\n@builtin(position) clip: vec4f,\n@location(0) q: vec4f,\n@location(1) p: vec3f,\n}\n@vertex fn vs_spray(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> SprayOut {\nvar o: SprayOut;\no.clip = vec4f(0.0, 0.0, -1.0, 1.0);\nif (sp.surf0.w < 0.5 || sp.size.y <= 0.0) {\nreturn o;\n}\nlet extent = 1.0 / sp.surf0.z;\nlet eye = sp.surf0.xy * extent;\nlet gx = f32(ii % SPRAY_SIDE) - f32(SPRAY_SIDE / 2u);\nlet gz = f32(ii / SPRAY_SIDE) - f32(SPRAY_SIDE / 2u);\nlet cell = floor(eye / SPRAY_SPACING) + vec2f(gx, gz);\nlet j = vec2f(hash2(cell), hash2(cell + vec2f(91.0, 37.0)));\nlet rest = (cell + j) * SPRAY_SPACING - eye;\nvar s = surf_tex(rest, false);\nlet tuv = sp.surf0.xy + rest * sp.surf0.z;\ns.phase = textureLoad(surf_d, vec2i(clamp(tuv, vec2f(0.0), vec2f(0.9999)) * vec2f(textureDimensions(surf_d))), 0).y;\nlet age = (s.phase - 0.12) * sp.surf1.y;\nlet life = 0.6 + 0.6 * hash2(cell + vec2f(13.0, 71.0));\nif (s.broken < 0.05 || age < 0.0 || age > life || s.ground > 0.3) {\nreturn o;\n}\nlet H = sp.surf1.x * s.amp;\nlet strength = (0.05 * s.broken + s.onset) * saturate(H * 1.2) * (0.4 + 1.2 * hash2(cell + vec2f(5.0, 3.0)));\nlet v0 = (2.0 + 3.0 * hash2(cell + vec2f(2.0, 9.0))) * saturate(H);\nlet k = 1.8;\nlet rise = v0 / k * (1.0 - exp(-k * age)) - 4.0 * age * age * 0.5;\nlet fwd = s.dir * (1.2 * (1.0 - exp(-1.5 * age)));\nlet base = s.d.y + 0.15;\nlet centre = vec3f(rest.x + fwd.x, sp.sea.x + base + max(rise, 0.0), rest.y + fwd.y);\nlet t = age / life;\nlet size = (0.12 + 0.5 * t) * (0.7 + 0.6 * hash2(cell + vec2f(17.0, 23.0))) * (0.6 + 0.6 * saturate(H));\nlet corner = array<vec2f, 6>(vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0))[vi];\nlet fwdv = normalize(centre);\nlet right = normalize(cross(vec3f(0.0, 1.0, 0.0), fwdv));\nlet up = cross(fwdv, right);\nlet pos = centre + (right * corner.x + up * corner.y) * size;\no.clip = camera.view_proj * vec4f(pos, 1.0);\nlet alpha = strength * smoothstep(0.0, 0.1, t) * (1.0 - smoothstep(0.4, 1.0, t)) * sp.size.y * 0.32;\no.q = vec4f(corner, alpha, length(centre));\no.p = centre;\nreturn o;\n}\n@fragment fn fs_spray(in: SprayOut) -> @location(0) vec4f {\nlet r2 = dot(in.q.xy, in.q.xy);\nif (r2 > 1.0) {\ndiscard;\n}\nlet wisp = vnoise(in.q.xy * 1.8 + in.p.xz * 3.1) * 0.6 + vnoise(in.q.xy * 4.3 + in.p.xz * 1.7) * 0.4;\nvar a = in.q.z * (1.0 - r2) * (1.0 - r2) * smoothstep(0.38, 0.62, wisp) * 1.6;\nlet d = textureLoad(under_depth, vec2i(in.clip.xy), 0).x;\nif (d > 0.0) {\nlet gap = in.q.w * (in.clip.z / d - 1.0);\na *= saturate(gap / 0.5);\n}\nlet v = normalize(in.p);\nlet l = camera.light_dir.xyz;\nlet g = 0.75;\nlet cosv = dot(v, l);\nlet hg = (1.0 - g * g) / (4.0 * PI * pow(1.0 + g * g - 2.0 * g * cosv, 1.5));\nlet light = sh_irradiance(vec3f(0.0, 1.0, 0.0)) * 0.6 + lighting.sun_color.rgb * (hg * 3.0 + 0.05);\nlet fogged = apply_fog(light, in.p, lighting.fog, lighting.fog2, lighting.fog_air.rgb, sh_irradiance(v), lighting.sun_color.rgb, l);\nreturn vec4f(fogged * lighting.env.z * a, a);\n}\nstruct VsOut {\n@builtin(position) clip: vec4f,\n@location(0) p: vec3f,\n@location(1) rest: vec2f,\n@location(2) height: f32,\n}\nfn ring_radius(i: f32) -> f32 {\nlet k = sp.grid.y;\nreturn sp.grid.x * (pow(1.0 + k, i) - 1.0) / k;\n}\nfn grid_point(vi: u32) -> vec3f {\nlet segs = u32(sp.grid.w);\nlet ring = f32(vi / (segs + 1u));\nlet seg = f32(vi % (segs + 1u));\nlet r = ring_radius(ring);\nlet a = 2.0 * PI * seg / f32(segs);\nreturn vec3f(vec2f(cos(a), sin(a)) * r, max(ring_radius(ring + 1.0) - r, 2.0 * PI * r / f32(segs)));\n}\nfn calm(c: u32, fade: f32) -> f32 {\nreturn mix(1.0, fade, saturate(1.0 / (sp.cascade[c].z * 150.0)));\n}\nfn displaced(field: texture_2d_array<f32>, rest: vec2f, spacing: f32, fade: f32) -> vec3f {\nvar d = vec3f(0.0);\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet texel = 1.0 / (cc.z * sp.size.x);\nlet w = (1.0 - smoothstep(1.0, 4.0, spacing / texel)) * calm(c, fade);\nif (w <= 0.0) {\ncontinue;\n}\nd += textureSampleLevel(field, wrap, cc.xy + rest * cc.z, c, 0.0).xyz * w;\n}\nreturn d;\n}\nstruct MotionOut {\n@builtin(position) clip: vec4f,\n@location(0) now: vec4f,\n@location(1) then: vec4f,\n}\n@vertex fn vs_motion(@builtin(vertex_index) vi: u32) -> MotionOut {\nlet g = grid_point(vi);\nlet rest = g.xy;\nlet drop = dot(rest, rest) / (2.0 * EARTH_RADIUS);\nlet sn = surf_tex(rest, false);\nlet sq = surf_tex(rest, wm.eye_move.w > 0.5);\nlet dn = on_beach(displaced(displacement, rest, g.z, sn.fade) + surf_disp(rest, g.z, false), sn, g.z);\nlet dp = select(dn, on_beach(displaced(prev_displacement, rest, g.z, sq.fade) + surf_disp(rest, g.z, true), sq, g.z), wm.eye_move.w > 0.5);\nlet p = vec3f(rest.x + dn.x, sp.sea.x + dn.y - drop, rest.y + dn.z);\nlet q = vec3f(rest.x + dp.x, sp.sea.x + dp.y - drop, rest.y + dp.z) + wm.eye_move.xyz;\nvar o: MotionOut;\no.now = wm.view_proj * vec4f(p, 1.0);\no.clip = camera.view_proj * vec4f(p, 1.0);\no.then = wm.prev_view_proj * vec4f(q, 1.0);\nreturn o;\n}\n@fragment fn fs_motion(in: MotionOut) -> @location(0) vec4f {\nlet now = in.now.xy / in.now.w;\nlet then = in.then.xy / max(in.then.w, 1e-6);\nreturn vec4f((now - then) * vec2f(0.5, -0.5), in.then.w, 1.0);\n}\n@vertex fn vs(@builtin(vertex_index) vi: u32) -> VsOut {\nlet segs = u32(sp.grid.w);\nlet ring = f32(vi / (segs + 1u));\nlet seg = f32(vi % (segs + 1u));\nlet r = ring_radius(ring);\nlet a = 2.0 * PI * seg / f32(segs);\nlet rest = vec2f(cos(a), sin(a)) * r;\nlet spacing = max(ring_radius(ring + 1.0) - r, 2.0 * PI * r / f32(segs));\nlet sf = surf_tex(rest, false);\nvar d = vec3f(0.0);\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet texel = 1.0 / (cc.z * sp.size.x);\nlet w = (1.0 - smoothstep(1.0, 4.0, spacing / texel)) * calm(c, sf.fade);\nif (w <= 0.0) {\ncontinue;\n}\nd += textureSampleLevel(displacement, wrap, cc.xy + rest * cc.z, c, 0.0).xyz * w;\n}\nd = on_beach(d + surf_disp(rest, spacing, false), sf, spacing);\nlet drop = dot(rest, rest) / (2.0 * EARTH_RADIUS);\nlet p = vec3f(rest.x + d.x, sp.sea.x + d.y - drop, rest.y + d.z);\nvar o: VsOut;\no.clip = camera.view_proj * vec4f(p, 1.0);\no.p = p;\no.rest = rest;\no.height = d.y;\nreturn o;\n}\nfn sh_irradiance(n: vec3f) -> vec3f {\nreturn max(lighting.sh[0].rgb * 0.282095 + lighting.sh[1].rgb * (0.488603 * n.y) + lighting.sh[2].rgb * (0.488603 * n.z) + lighting.sh[3].rgb * (0.488603 * n.x) + lighting.sh[4].rgb * (1.092548 * n.x * n.y) + lighting.sh[5].rgb * (1.092548 * n.y * n.z) + lighting.sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0)) + lighting.sh[7].rgb * (1.092548 * n.x * n.z) + lighting.sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y)), vec3f(0.0));\n}\n@compute @workgroup_size(8, 8) fn copy_depth(@builtin(global_invocation_id) id: vec3u) {\nlet size = textureDimensions(scene_depth);\nif (id.x >= size.x || id.y >= size.y) {\nreturn;\n}\ntextureStore(depth_out, vec2i(id.xy), vec4f(textureLoad(scene_depth, vec2i(id.xy), 0), 0.0, 0.0, 0.0));\n}\nfn thickness(dist: f32, ds: f32, dop: f32) -> f32 {\nreturn select(1.0e9, dist * max(ds / dop - 1.0, 0.0), dop > 0.0);\n}\nfn mirror_probe(p: vec3f, r: vec3f, t: f32, size: vec2f) -> vec4f {\nlet c = camera.view_proj * vec4f(p + r * t, 1.0);\nif (c.w <= 0.0) {\nreturn vec4f(0.0);\n}\nlet ndc = c.xyz / c.w;\nlet uv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nreturn vec4f(0.0);\n}\nlet d = textureLoad(under_depth, vec2i(uv * size), 0).x;\nlet gap = select(-1.0e9, c.w * (1.0 - ndc.z / d), d > 0.0);\nreturn vec4f(uv, gap, 1.0);\n}\nfn mirror_scene(p: vec3f, r: vec3f, size: vec2f, jitter: f32) -> vec4f {\nlet len = sp.mirror.x;\nlet steps = u32(sp.mirror.y);\nif (len <= 0.0 || steps == 0u || sp.shore.y < 0.5 || r.y <= 0.0) {\nreturn vec4f(0.0);\n}\nvar prev = 0.0;\nvar was_behind = false;\nfor (var i = 1u; i <= steps; i++) {\nlet f = (f32(i) - jitter) / f32(steps);\nlet t = len * f * f;\nlet s = mirror_probe(p, r, t, size);\nif (s.w < 0.5) {\nbreak;\n}\nlet behind = s.z > 0.0;\nif (behind && !was_behind) {\nvar a = prev;\nvar b = t;\nvar hit = s;\nfor (var k = 0u; k < 6u; k++) {\nlet m = 0.5 * (a + b);\nlet sm = mirror_probe(p, r, m, size);\nif (sm.w > 0.5 && sm.z > 0.0) {\nb = m;\nhit = sm;\n} else {\na = m;\n}\n}\nif (hit.z < sp.mirror.z + 0.03 * b) {\nlet uv = hit.xy;\nlet edge = min(min(uv.x, 1.0 - uv.x), min(uv.y, 1.0 - uv.y));\nlet fade = saturate(edge * 12.0) * (1.0 - smoothstep(0.7, 1.0, b / len));\nreturn vec4f(textureSampleLevel(under, clamp_smp, uv, 0.0).rgb * sp.shore.z, fade);\n}\n}\nwas_behind = behind;\nprev = t;\n}\nreturn vec4f(0.0);\n}\nfn ign(px: vec2f) -> f32 {\nlet frame = floor(camera.light_dir.w * 60.0) % 64.0;\nlet q = px + 5.588238 * frame;\nreturn fract(52.9829189 * fract(dot(q, vec2f(0.06711056, 0.00583715))));\n}\n@fragment fn fs(in: VsOut) -> @location(0) vec4f {\nlet frag = in.clip;\nvar sx = 0.0;\nvar sz = 0.0;\nvar lost = 0.0;\nvar fold = 0.0;\nlet sf = surf_tex(in.rest, false);\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet uv = cc.xy + in.rest * cc.z;\nlet texels = length(fwidth(uv)) * sp.size.x;\nlet resolved = 1.0 - smoothstep(0.5, 2.0, texels);\nlet w = resolved * calm(c, sf.fade);\nlet s = textureSampleLevel(slopes, wrap, uv, c, 0.0);\nsx += s.x * w;\nsz += s.y * w;\nlost += cc.w * (1.0 - resolved) * calm(c, sf.fade);\nfold = max(fold, max(saturate((sp.sea.z - s.z) * 2.0), s.w) * w);\n}\nlet fine = u32(sp.sea.y) - 1u;\nfor (var k = 0u; k < 2u; k++) {\nlet scale = select(8.3, 3.1, k == 0u);\nlet amp = select(0.5, 0.7, k == 0u) * sp.shore.w;\nlet ang = select(1.24, 0.65, k == 0u);\nlet rot = mat2x2f(cos(ang), sin(ang), -sin(ang), cos(ang));\nlet q = rot * (sp.cascade[fine].xy + in.rest * sp.cascade[fine].z) * scale + vec2f(0.37, 0.71) * f32(k + 1u);\nlet resolved = 1.0 - smoothstep(0.5, 2.0, length(fwidth(q)) * sp.size.x);\nlet t = textureSampleLevel(slopes, wrap, q, fine, 0.0).xy * amp;\nlet back = transpose(rot) * t;\nsx += back.x * resolved;\nsz += back.y * resolved;\nlost += sp.cascade[fine].w * amp * amp * (1.0 - resolved);\n}\nsx += sf.slope.x;\nsz += sf.slope.y;\nfold *= sf.fade;\nlet n = normalize(vec3f(-sx, 1.0, -sz));\nlet v = normalize(-in.p);\nlet l = camera.light_dir.xyz;\nlet nv = max(dot(n, v), 1e-3);\nlet nl = max(dot(n, l), 0.0);\nlet a2 = max(sp.body.w * sp.body.w + 2.0 * lost, 1e-4);\nlet f0 = 0.02;\nlet fresnel = f0 + (1.0 - f0) * pow(1.0 - nv, 5.0);\nvar r = reflect(-v, n);\nr.y = abs(r.y);\nlet lod = clamp(sqrt(sqrt(a2)) * (lighting.env.x - 1.0), 0.0, lighting.env.x - 1.0);\nvar sky = textureSampleLevel(env_cube, wrap, r, lod).rgb * lighting.view.rgb;\nif (sp.size.z > 0.5) {\nlet pn = vec2f(textureDimensions(cloud_pano));\nlet pv = clamp(sqrt(max(r.y, 0.0)), 0.5 / pn.y, 1.0 - 0.5 / pn.y);\nlet cl = textureSampleLevel(cloud_pano, wrap, vec2f(fract(atan2(r.z, r.x) / (2.0 * PI)), pv), 0.0);\nsky = sky * cl.a + cl.rgb;\n}\nlet seen = mirror_scene(in.p, reflect(-v, n), vec2f(textureDimensions(under)), ign(frag.xy));\nsky = mix(sky, seen.rgb, seen.a);\nif (sp.mirror.w > 0.5) {\nreturn vec4f(select(vec3f(seen.a * 0.2, 0.0, 0.0) + seen.rgb * seen.a, vec3f(0.3, 0.0, 0.0), seen.a <= 0.0) * lighting.env.z, 1.0);\n}\nlet h = normalize(v + l);\nlet nh = max(dot(n, h), 0.0);\nlet dd = nh * nh * (a2 - 1.0) + 1.0;\nlet ggx = a2 / (PI * dd * dd);\nlet vis = 0.5 / (nl * sqrt(nv * nv * (1.0 - a2) + a2) + nv * sqrt(nl * nl * (1.0 - a2) + a2) + 1e-5);\nlet fh = f0 + (1.0 - f0) * pow(1.0 - max(dot(v, h), 0.0), 5.0);\nlet sun = lighting.sun_color.rgb;\nlet spec = sun * (ggx * vis * fh * nl);\nlet up = sh_irradiance(vec3f(0.0, 1.0, 0.0));\nlet body = sp.body.rgb * (up + sun * (max(l.y, 0.0) / PI) * 0.5);\nlet behind = pow(saturate(dot(-v, l)), 8.0);\nlet crest = saturate(in.height * 0.5) * saturate(1.0 - n.y * 0.9 + 0.2);\nvar sss = sp.sss.rgb * sun * (behind * crest * sp.sss.w);\nlet wide = 0.35 + 0.65 * pow(saturate(dot(-v, l) * 0.5 + 0.5), 3.0);\nlet thin = sf.face * (0.6 + 0.4 * saturate(1.0 - abs(n.y) * 1.2));\nsss += sp.sss.rgb * (up * 4.5 + sun * (0.8 * wide)) * (thin * sp.sss.w * 2.5);\nvar below = body;\nvar shore_foam = 0.0;\nif (sp.shore.y > 0.5) {\nlet px = vec2i(frag.xy);\nlet size = vec2f(textureDimensions(under));\nlet dist = length(in.p);\nlet t0 = thickness(dist, frag.z, textureLoad(under_depth, px, 0).x);\nlet bend = n.xz * sp.optics.w * min(t0, 1.0);\nvar uv = (frag.xy + bend) / size;\nif (any(uv < vec2f(0.0)) || any(uv >= vec2f(1.0))) {\nuv = frag.xy / size;\n}\nvar t = thickness(dist, frag.z, textureLoad(under_depth, vec2i(uv * size), 0).x);\nif (textureLoad(under_depth, vec2i(uv * size), 0).x > frag.z) {\nuv = frag.xy / size;\nt = t0;\n}\nvar behind_light = textureSampleLevel(under, clamp_smp, uv, 0.0).rgb * sp.shore.z;\nlet down = max(t * v.y, 0.0);\nlet sun_in = sqrt(max(1.0 - (1.0 - l.y * l.y) / 1.7689, 0.04));\nlet above = (in.p - v * t).xz + vec2f(l.x, l.z) / sun_in * down * (1.0 / 1.33);\nvar lap = 0.0;\nlet cn = u32(sp.sea.y);\nlet c0 = select(0u, cn - 2u, cn >= 2u);\nfor (var c = c0; c < select(c0, select(1u, cn - 1u, cn >= 2u), sp.size.w > 0.5); c++) {\nlet cc = sp.cascade[c];\nlet e = 2.0 / (cc.z * sp.size.x);\nlet q = cc.xy + above * cc.z;\nlet ex = vec2f(cc.z * e, 0.0);\nlet ez = vec2f(0.0, cc.z * e);\nlet dsx = textureSampleLevel(slopes, wrap, q + ex, c, 0.0).x - textureSampleLevel(slopes, wrap, q - ex, c, 0.0).x;\nlet dsz = textureSampleLevel(slopes, wrap, q + ez, c, 0.0).y - textureSampleLevel(slopes, wrap, q - ez, c, 0.0).y;\nlap += (dsx + dsz) / (2.0 * e);\n}\nlet focus = 1.0 / max(abs(1.0 - down * 0.248 * lap), 0.25);\nlet caustic = mix(1.0, focus, saturate(down * 3.0) * exp(-down / 4.0) * saturate(l.y * 4.0));\nbehind_light *= mix(1.0, caustic, 0.65);\nlet trans = exp(-sp.optics.rgb * (t + down / sun_in));\nbelow = behind_light * trans + body * (vec3f(1.0) - exp(-sp.optics.rgb * t));\nlet vertical = t0 * max(v.y, 0.02);\nshore_foam = saturate(1.0 - vertical / sp.shore.x) * saturate(vertical * 40.0);\n}\nlet sigma = sqrt(lost);\nlet mean_f = f0 + (1.0 - f0) * pow(1.0 - nv, 5.0 * exp(-2.69 * sigma)) / (1.0 + 22.7 * pow(sigma, 1.5));\nlet fr = min(fresnel, mean_f);\nlet water = sky * fr + (below + sss) * (1.0 - fr) + spec;\nlet foam_light = (sh_irradiance(n) + sun * (nl / PI)) * 0.85;\nvar fp = (sp.cascade[0].xy + in.rest * sp.cascade[0].z) / sp.cascade[0].z;\nif (sf.foam > 0.001 || sf.front > 0.001) {\nfp = sf.world;\n}\nlet flow = surf_flow(sf);\nlet cyc = 1.0;\nlet ph0 = fract(sp.surf3.x / cyc);\nlet ph1 = fract(sp.surf3.x / cyc + 0.5);\nlet fp0 = fp - flow * (ph0 * cyc) + vec2f(13.7, 5.1) * floor(sp.surf3.x / cyc);\nlet fp1 = fp - flow * (ph1 * cyc) + vec2f(7.3, 11.9) * floor(sp.surf3.x / cyc + 0.5);\nlet wflow = abs(2.0 * ph0 - 1.0);\nlet beach = smoothstep(0.0, 0.04, sf.ground);\nvar cover = saturate(fold * sp.sea.w + shore_foam * (1.0 - beach) + sf.foam + sf.front * 0.9);\nif (cover > 0.002) {\nlet spots = vnoise(fp * 0.22) * 0.65 + vnoise(fp * 0.61 + vec2f(9.0, 4.0)) * 0.35;\ncover *= mix(smoothstep(0.35, 0.75, spots), 1.0, smoothstep(0.35, 0.8, cover));\nlet clumps = vnoise(fp * 0.35 + vec2f(3.0, 7.0)) * 0.6 + vnoise(fp * 1.1 + vec2f(11.0, 2.0)) * 0.4;\ncover *= mix(1.0, 0.45 + 0.75 * clumps, smoothstep(0.3, 0.7, cover));\n}\nlet fw = length(fwidth(fp0));\nvar foam = 0.0;\nvar bubbles = 1.0;\nif (cover > 0.002) {\nvar net = foam_net(fp0, cover, fw);\nif (dot(flow, flow) > 1e-4) {\nnet = mix(net, foam_net(fp1, cover, fw), wflow);\n}\nfoam = net.x;\nbubbles = net.y;\n}\nlet aerated = saturate(sf.fresh * 1.6) * (1.0 - beach);\nlet milky = mix(sp.sss.rgb * 2.5, vec3f(0.85, 0.95, 0.95), 0.55) * (sh_irradiance(n) + sun * (nl / PI));\nvar foam_lit = foam_light;\nif (foam > 0.01 && sp.surf0.w > 0.5) {\nlet fuv = sp.surf0.xy + in.rest * sp.surf0.z;\nlet ft = 1.0 / vec2f(textureDimensions(surf_d));\nlet gx = textureSampleLevel(surf_d, clamp_smp, fuv + vec2f(ft.x, 0.0), 0.0).w - textureSampleLevel(surf_d, clamp_smp, fuv - vec2f(ft.x, 0.0), 0.0).w;\nlet gz = textureSampleLevel(surf_d, clamp_smp, fuv + vec2f(0.0, ft.y), 0.0).w - textureSampleLevel(surf_d, clamp_smp, fuv - vec2f(0.0, ft.y), 0.0).w;\nlet per_m = 0.1 / (2.0 * ft.x / sp.surf0.z);\nlet nf = normalize(n + vec3f(-gx, 0.0, -gz) * per_m * 0.5);\nfoam_lit = (sh_irradiance(nf) + sun * (max(dot(nf, l), 0.0) / PI)) * 0.85;\n}\nvar lit = mix(mix(water, milky, aerated * 0.65), foam_lit * bubbles, foam);\nif (sf.ground > 0.0 && sp.shore.y > 0.5) {\nlet sand = textureLoad(under, vec2i(frag.xy), 0).rgb * sp.shore.z;\nlet wet_rough = 0.12;\nlet sheen = textureSampleLevel(env_cube, wrap, r, clamp(sqrt(wet_rough) * (lighting.env.x - 1.0), 0.0, lighting.env.x - 1.0)).rgb * lighting.view.rgb;\nlet wet_sand = sand * mix(1.0, 0.5, sf.wet) + sheen * fresnel * sf.wet * 0.8;\nlit = mix(wet_sand, lit, max(sf.cover, foam));\n}\nlet fogged = apply_fog(lit, in.p, lighting.fog, lighting.fog2, lighting.fog_air.rgb, sh_irradiance(normalize(in.p)), sun, l);\nreturn vec4f(fogged * lighting.env.z, 1.0);\n}\nstruct Camera {\nview_proj: mat4x4f,\nfrustum: array<vec4f, 6>,\nlight_dir: vec4f,\norigin: vec4f,\n}\nstruct Lighting {\nsun_color: vec4f,\nenv: vec4f,\nsh: array<vec4f, 9>,\nscreen: vec4f,\nview: vec4f,\nao_reproject: mat4x4f,\nao: vec4f,\nfog: vec4f,\nfog2: vec4f,\nfog_air: vec4f,\ngi_grid: vec4f,\ngi_counts: vec4f,\nssr: vec4f,\ngi_cascades: array<vec4f, 3>,\n}\nfn apply_fog(color: vec3f, p: vec3f, fog: vec4f, fog2: vec4f, air: vec3f, ambient: vec3f, sun: vec3f, sun_dir: vec3f) -> vec3f {\nlet dist = length(p);\nif (dist <= 0.0 || (fog.x <= 0.0 && fog2.x <= 0.0)) {\nreturn color;\n}\nlet dir = p / dist;\nlet t = exp(-(air * (fog2.x * dist) + vec3f(height_fog_depth(fog, dir, dist))));\nreturn color * t + fog_inscatter(fog2, dir, ambient, sun, sun_dir) * (vec3f(1.0) - t);\n}\nfn height_fog_depth(fog: vec4f, dir: vec3f, dist: f32) -> f32 {\nif (fog.x <= 0.0) {\nreturn 0.0;\n}\nlet falloff = max(fog.y, 1e-5);\nlet len = max(dist - fog.w, 0.0);\nlet h0 = fog.z + dir.y * min(dist, fog.w);\nlet k = falloff * dir.y * len;\nlet shape = select((1.0 - exp(-k)) / k, 1.0 - 0.5 * k, abs(k) < 1e-3);\nreturn fog.x * exp(-falloff * h0) * len * shape;\n}\nfn fog_inscatter(fog2: vec4f, dir: vec3f, ambient: vec3f, sun: vec3f, sun_dir: vec3f) -> vec3f {\nlet g = fog2.z;\nlet c = dot(dir, sun_dir);\nlet phase = (1.0 - g * g) / (FOUR_PI * pow(max(1.0 + g * g - 2.0 * g * c, 1e-4), 1.5));\nreturn ambient + sun * (phase * fog2.y);\n}\nconst FOUR_PI = 12.5663706;";
10
10
  };
@@ -2,12 +2,12 @@
2
2
  // Regenerate with `node tools/shaders-gen.mjs`; CI runs it with --check.
3
3
  /** Linked from shaders/water/waves.wesl. */
4
4
  export const waterWaves = {
5
- hash: '851c72a24f5654c5',
6
- wgsl: "override N: u32 = 128u;\noverride HALF: u32 = 64u;\nconst PI = 3.14159265358979;\nconst G = 9.81;\nstruct WaveParams {\nt: vec4f,\nv: vec4u,\n}\n@group(0) @binding(0) var<storage, read> h0: array<vec4f>;\n@group(0) @binding(1) var<storage, read_write> a: array<vec2f>;\n@group(0) @binding(2) var<storage, read_write> b: array<vec2f>;\n@group(0) @binding(3) var<uniform> wp: WaveParams;\n@group(0) @binding(4) var displacement: texture_storage_2d_array<rgba16float, write>;\n@group(0) @binding(5) var slopes: texture_storage_2d_array<rgba16float, write>;\nfn cmul(x: vec2f, y: vec2f) -> vec2f {\nreturn vec2f(x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x);\n}\nfn ci(x: vec2f) -> vec2f {\nreturn vec2f(-x.y, x.x);\n}\n@compute @workgroup_size(8, 8) fn evolve(@builtin(global_invocation_id) id: vec3u) {\nif (id.x >= N || id.y >= N) {\nreturn;\n}\nlet i = id.y * N + id.x;\nlet size = wp.t.y;\nlet kx = 2.0 * PI * (f32(id.x) - f32(HALF)) / size;\nlet kz = 2.0 * PI * (f32(id.y) - f32(HALF)) / size;\nlet kl = sqrt(kx * kx + kz * kz);\nlet inv = select(0.0, 1.0 / kl, kl > 1e-6);\nlet w = sqrt(G * kl);\nlet e = vec2f(cos(w * wp.t.x), sin(w * wp.t.x));\nlet s = h0[i];\nlet h = cmul(s.xy, e) + cmul(s.zw, vec2f(e.x, -e.y));\nlet dx = -ci(h) * (kx * inv);\nlet dz = -ci(h) * (kz * inv);\nlet sx = ci(h) * kx;\nlet sz = ci(h) * kz;\nlet dxx = h * (kx * kx * inv);\nlet dzz = h * (kz * kz * inv);\nlet dxz = h * (kx * kz * inv);\nlet nn = N * N;\na[i] = h + ci(dx);\na[nn + i] = dz + ci(sx);\na[2u * nn + i] = sz + ci(dxx);\na[3u * nn + i] = dzz + ci(dxz);\n}\nvar<workgroup> line: array<vec2f, 2u * N>;\n@compute @workgroup_size(HALF) fn fft(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) t: u32) {\nlet field = wg.y * N * N;\nlet l = wg.x;\nlet rows = wp.v.x == 0u;\nlet i0 = select(t * N + l, l * N + t, rows) + field;\nlet i1 = select((t + HALF) * N + l, l * N + t + HALF, rows) + field;\nif (rows) {\nline[t] = a[i0];\nline[t + HALF] = a[i1];\n} else {\nline[t] = b[i0];\nline[t + HALF] = b[i1];\n}\nworkgroupBarrier();\nvar src = 0u;\nfor (var q = 1u; q < N; q <<= 1u) {\nlet k = t & (q - 1u);\nlet x = line[src * N + t];\nlet y = line[src * N + t + HALF];\nlet ang = PI * f32(k) / f32(q);\nlet yw = cmul(y, vec2f(cos(ang), sin(ang)));\nlet j = ((t - k) << 1u) + k;\nline[(1u - src) * N + j] = x + yw;\nline[(1u - src) * N + j + q] = x - yw;\nsrc = 1u - src;\nworkgroupBarrier();\n}\nif (rows) {\nb[i0] = line[src * N + t];\nb[i1] = line[src * N + t + HALF];\n} else {\na[i0] = line[src * N + t];\na[i1] = line[src * N + t + HALF];\n}\n}\n@compute @workgroup_size(8, 8) fn pack(@builtin(global_invocation_id) id: vec3u) {\nif (id.x >= N || id.y >= N) {\nreturn;\n}\nlet i = id.y * N + id.x;\nlet nn = N * N;\nlet sign = select(1.0, -1.0, ((id.x + id.y) & 1u) == 1u);\nlet f0 = a[i] * sign;\nlet f1 = a[nn + i] * sign;\nlet f2 = a[2u * nn + i] * sign;\nlet f3 = a[3u * nn + i] * sign;\nlet lambda = wp.t.z;\nlet h = f0.x;\nlet dx = f0.y * lambda;\nlet dz = f1.x * lambda;\nlet jx = 1.0 + lambda * f2.y;\nlet jz = 1.0 + lambda * f3.x;\nlet jxz = lambda * f3.y;\nlet det = jx * jz - jxz * jxz;\nlet layer = i32(wp.v.y);\ntextureStore(displacement, vec2i(id.xy), layer, vec4f(dx, h, dz, det));\ntextureStore(slopes, vec2i(id.xy), layer, vec4f(f1.y, f2.x, det, 0.0));\n}",
5
+ hash: '206e70360bd13637',
6
+ wgsl: "override N: u32 = 128u;\noverride HALF: u32 = 64u;\nconst PI = 3.14159265358979;\nconst G = 9.81;\nstruct WaveParams {\nt: vec4f,\nv: vec4u,\n}\n@group(0) @binding(0) var<storage, read> h0: array<vec4f>;\n@group(0) @binding(1) var<storage, read_write> a: array<vec2f>;\n@group(0) @binding(2) var<storage, read_write> b: array<vec2f>;\n@group(0) @binding(3) var<uniform> wp: WaveParams;\n@group(0) @binding(4) var displacement: texture_storage_2d_array<rgba16float, write>;\n@group(0) @binding(5) var slopes: texture_storage_2d_array<rgba16float, write>;\n@group(0) @binding(6) var<storage, read_write> foam: array<f32>;\nconst FOAM_LIFE = 2.2;\nfn cmul(x: vec2f, y: vec2f) -> vec2f {\nreturn vec2f(x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x);\n}\nfn ci(x: vec2f) -> vec2f {\nreturn vec2f(-x.y, x.x);\n}\n@compute @workgroup_size(8, 8) fn evolve(@builtin(global_invocation_id) id: vec3u) {\nif (id.x >= N || id.y >= N) {\nreturn;\n}\nlet i = id.y * N + id.x;\nlet size = wp.t.y;\nlet kx = 2.0 * PI * (f32(id.x) - f32(HALF)) / size;\nlet kz = 2.0 * PI * (f32(id.y) - f32(HALF)) / size;\nlet kl = sqrt(kx * kx + kz * kz);\nlet inv = select(0.0, 1.0 / kl, kl > 1e-6);\nlet w = sqrt(G * kl);\nlet e = vec2f(cos(w * wp.t.x), sin(w * wp.t.x));\nlet s = h0[i];\nlet h = cmul(s.xy, e) + cmul(s.zw, vec2f(e.x, -e.y));\nlet dx = -ci(h) * (kx * inv);\nlet dz = -ci(h) * (kz * inv);\nlet sx = ci(h) * kx;\nlet sz = ci(h) * kz;\nlet dxx = h * (kx * kx * inv);\nlet dzz = h * (kz * kz * inv);\nlet dxz = h * (kx * kz * inv);\nlet nn = N * N;\na[i] = h + ci(dx);\na[nn + i] = dz + ci(sx);\na[2u * nn + i] = sz + ci(dxx);\na[3u * nn + i] = dzz + ci(dxz);\n}\nvar<workgroup> line: array<vec2f, 2u * N>;\n@compute @workgroup_size(HALF) fn fft(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) t: u32) {\nlet field = wg.y * N * N;\nlet l = wg.x;\nlet rows = wp.v.x == 0u;\nlet i0 = select(t * N + l, l * N + t, rows) + field;\nlet i1 = select((t + HALF) * N + l, l * N + t + HALF, rows) + field;\nif (rows) {\nline[t] = a[i0];\nline[t + HALF] = a[i1];\n} else {\nline[t] = b[i0];\nline[t + HALF] = b[i1];\n}\nworkgroupBarrier();\nvar src = 0u;\nfor (var q = 1u; q < N; q <<= 1u) {\nlet k = t & (q - 1u);\nlet x = line[src * N + t];\nlet y = line[src * N + t + HALF];\nlet ang = PI * f32(k) / f32(q);\nlet yw = cmul(y, vec2f(cos(ang), sin(ang)));\nlet j = ((t - k) << 1u) + k;\nline[(1u - src) * N + j] = x + yw;\nline[(1u - src) * N + j + q] = x - yw;\nsrc = 1u - src;\nworkgroupBarrier();\n}\nif (rows) {\nb[i0] = line[src * N + t];\nb[i1] = line[src * N + t + HALF];\n} else {\na[i0] = line[src * N + t];\na[i1] = line[src * N + t + HALF];\n}\n}\n@compute @workgroup_size(8, 8) fn pack(@builtin(global_invocation_id) id: vec3u) {\nif (id.x >= N || id.y >= N) {\nreturn;\n}\nlet i = id.y * N + id.x;\nlet nn = N * N;\nlet sign = select(1.0, -1.0, ((id.x + id.y) & 1u) == 1u);\nlet f0 = a[i] * sign;\nlet f1 = a[nn + i] * sign;\nlet f2 = a[2u * nn + i] * sign;\nlet f3 = a[3u * nn + i] * sign;\nlet lambda = wp.t.z;\nlet h = f0.x;\nlet dx = f0.y * lambda;\nlet dz = f1.x * lambda;\nlet jx = 1.0 + lambda * f2.y;\nlet jz = 1.0 + lambda * f3.x;\nlet jxz = lambda * f3.y;\nlet det = jx * jz - jxz * jxz;\nlet layer = i32(wp.v.y);\nlet fi = wp.v.y * nn + i;\nlet made = saturate((bitcast<f32>(wp.v.w) - det) * 2.0);\nlet kept = foam[fi] * exp(-bitcast<f32>(wp.v.z) / FOAM_LIFE);\nlet fo = max(made, kept);\nfoam[fi] = fo;\ntextureStore(displacement, vec2i(id.xy), layer, vec4f(dx, h, dz, det));\ntextureStore(slopes, vec2i(id.xy), layer, vec4f(f1.y, f2.x, det, fo));\n}",
7
7
  };
8
8
  /** Linked from shaders/water/surface.wesl. */
9
9
  export const waterSurface = {
10
- hash: 'b15f2cb4cff18a4e',
11
- wgsl: "const PI = 3.14159265;\nconst EARTH_RADIUS = 6371000.0;\nconst MAX_CASCADES = 4u;\nstruct SurfaceParams {\ncascade: array<vec4f, MAX_CASCADES>,\ngrid: vec4f,\nsea: vec4f,\nbody: vec4f,\nsss: vec4f,\nsize: vec4f,\noptics: vec4f,\nshore: vec4f,\nmirror: vec4f,\n}\n@group(0) @binding(0) var<uniform> camera: Camera;\n@group(0) @binding(1) var<uniform> lighting: Lighting;\n@group(0) @binding(2) var<uniform> sp: SurfaceParams;\n@group(0) @binding(3) var displacement: texture_2d_array<f32>;\n@group(0) @binding(4) var slopes: texture_2d_array<f32>;\n@group(0) @binding(5) var wrap: sampler;\n@group(0) @binding(6) var env_cube: texture_cube<f32>;\n@group(0) @binding(7) var under: texture_2d<f32>;\n@group(0) @binding(8) var under_depth: texture_2d<f32>;\n@group(0) @binding(9) var clamp_smp: sampler;\n@group(0) @binding(10) var scene_depth: texture_depth_2d;\n@group(0) @binding(11) var depth_out: texture_storage_2d<r32float, write>;\nstruct VsOut {\n@builtin(position) clip: vec4f,\n@location(0) p: vec3f,\n@location(1) rest: vec2f,\n@location(2) height: f32,\n}\nfn ring_radius(i: f32) -> f32 {\nlet k = sp.grid.y;\nreturn sp.grid.x * (pow(1.0 + k, i) - 1.0) / k;\n}\n@vertex fn vs(@builtin(vertex_index) vi: u32) -> VsOut {\nlet segs = u32(sp.grid.w);\nlet quad = vi / 6u;\nlet corner = vi % 6u;\nlet dr = select(select(0u, 1u, corner == 1u || corner == 2u || corner == 4u), 0u, corner == 0u || corner == 3u);\nlet ds = select(0u, 1u, corner == 2u || corner == 4u || corner == 5u);\nlet ring = f32(quad / segs + dr);\nlet seg = f32(quad % segs + ds);\nlet r = ring_radius(ring);\nlet a = 2.0 * PI * seg / f32(segs);\nlet rest = vec2f(cos(a), sin(a)) * r;\nlet spacing = max(ring_radius(ring + 1.0) - r, 2.0 * PI * r / f32(segs));\nvar d = vec3f(0.0);\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet texel = 1.0 / (cc.z * sp.size.x);\nlet w = 1.0 - smoothstep(1.0, 4.0, spacing / texel);\nif (w <= 0.0) {\ncontinue;\n}\nd += textureSampleLevel(displacement, wrap, cc.xy + rest * cc.z, c, 0.0).xyz * w;\n}\nlet drop = dot(rest, rest) / (2.0 * EARTH_RADIUS);\nlet p = vec3f(rest.x + d.x, sp.sea.x + d.y - drop, rest.y + d.z);\nvar o: VsOut;\no.clip = camera.view_proj * vec4f(p, 1.0);\no.p = p;\no.rest = rest;\no.height = d.y;\nreturn o;\n}\nfn sh_irradiance(n: vec3f) -> vec3f {\nreturn max(lighting.sh[0].rgb * 0.282095 + lighting.sh[1].rgb * (0.488603 * n.y) + lighting.sh[2].rgb * (0.488603 * n.z) + lighting.sh[3].rgb * (0.488603 * n.x) + lighting.sh[4].rgb * (1.092548 * n.x * n.y) + lighting.sh[5].rgb * (1.092548 * n.y * n.z) + lighting.sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0)) + lighting.sh[7].rgb * (1.092548 * n.x * n.z) + lighting.sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y)), vec3f(0.0));\n}\n@compute @workgroup_size(8, 8) fn copy_depth(@builtin(global_invocation_id) id: vec3u) {\nlet size = textureDimensions(scene_depth);\nif (id.x >= size.x || id.y >= size.y) {\nreturn;\n}\ntextureStore(depth_out, vec2i(id.xy), vec4f(textureLoad(scene_depth, vec2i(id.xy), 0), 0.0, 0.0, 0.0));\n}\nfn thickness(dist: f32, ds: f32, dop: f32) -> f32 {\nreturn select(1.0e9, dist * max(ds / dop - 1.0, 0.0), dop > 0.0);\n}\nfn mirror_scene(p: vec3f, r: vec3f, size: vec2f) -> vec4f {\nlet len = sp.mirror.x;\nlet steps = u32(sp.mirror.y);\nif (len <= 0.0 || steps == 0u || sp.shore.y < 0.5 || r.y <= 0.0) {\nreturn vec4f(0.0);\n}\nfor (var i = 1u; i <= steps; i++) {\nlet f = f32(i) / f32(steps);\nlet q = p + r * (len * f * f);\nlet c = camera.view_proj * vec4f(q, 1.0);\nif (c.w <= 0.0) {\nbreak;\n}\nlet ndc = c.xyz / c.w;\nlet uv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nbreak;\n}\nlet d = textureLoad(under_depth, vec2i(uv * size), 0).x;\nif (d > ndc.z && d > 0.0) {\nlet gap = c.w * (1.0 - ndc.z / d);\nif (gap < sp.mirror.z + 0.03 * c.w) {\nlet edge = min(min(uv.x, 1.0 - uv.x), min(uv.y, 1.0 - uv.y));\nlet fade = saturate(edge * 12.0) * (1.0 - f * f);\nreturn vec4f(textureSampleLevel(under, clamp_smp, uv, 0.0).rgb * sp.shore.z, fade);\n}\n}\n}\nreturn vec4f(0.0);\n}\n@fragment fn fs(in: VsOut) -> @location(0) vec4f {\nlet frag = in.clip;\nvar sx = 0.0;\nvar sz = 0.0;\nvar lost = 0.0;\nvar fold = 0.0;\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet uv = cc.xy + in.rest * cc.z;\nlet texels = length(fwidth(uv)) * sp.size.x;\nlet w = 1.0 - smoothstep(0.5, 2.0, texels);\nlet s = textureSampleLevel(slopes, wrap, uv, c, 0.0);\nsx += s.x * w;\nsz += s.y * w;\nlost += cc.w * (1.0 - w);\nfold = max(fold, saturate((sp.sea.z - s.z) * 2.0) * w);\n}\nlet n = normalize(vec3f(-sx, 1.0, -sz));\nlet v = normalize(-in.p);\nlet l = camera.light_dir.xyz;\nlet nv = max(dot(n, v), 1e-3);\nlet nl = max(dot(n, l), 0.0);\nlet a2 = max(sp.body.w * sp.body.w + 2.0 * lost, 1e-4);\nlet f0 = 0.02;\nlet fresnel = f0 + (1.0 - f0) * pow(1.0 - nv, 5.0);\nvar r = reflect(-v, n);\nr.y = abs(r.y);\nlet lod = clamp(sqrt(sqrt(a2)) * (lighting.env.x - 1.0), 0.0, lighting.env.x - 1.0);\nvar sky = textureSampleLevel(env_cube, wrap, r, lod).rgb * lighting.view.rgb;\nlet seen = mirror_scene(in.p, reflect(-v, n), vec2f(textureDimensions(under)));\nsky = mix(sky, seen.rgb, seen.a);\nif (sp.mirror.w > 0.5) {\nreturn vec4f(select(vec3f(seen.a * 0.2, 0.0, 0.0) + seen.rgb * seen.a, vec3f(0.3, 0.0, 0.0), seen.a <= 0.0) * lighting.env.z, 1.0);\n}\nlet h = normalize(v + l);\nlet nh = max(dot(n, h), 0.0);\nlet dd = nh * nh * (a2 - 1.0) + 1.0;\nlet ggx = a2 / (PI * dd * dd);\nlet vis = 0.5 / (nl * sqrt(nv * nv * (1.0 - a2) + a2) + nv * sqrt(nl * nl * (1.0 - a2) + a2) + 1e-5);\nlet fh = f0 + (1.0 - f0) * pow(1.0 - max(dot(v, h), 0.0), 5.0);\nlet sun = lighting.sun_color.rgb;\nlet spec = sun * (ggx * vis * fh * nl);\nlet up = sh_irradiance(vec3f(0.0, 1.0, 0.0));\nlet body = sp.body.rgb * (up + sun * (max(l.y, 0.0) / PI) * 0.5);\nlet behind = pow(saturate(dot(-v, l)), 8.0);\nlet crest = saturate(in.height * 0.5) * saturate(1.0 - n.y * 0.9 + 0.2);\nlet sss = sp.sss.rgb * sun * (behind * crest * sp.sss.w);\nvar below = body;\nvar shore_foam = 0.0;\nif (sp.shore.y > 0.5) {\nlet px = vec2i(frag.xy);\nlet size = vec2f(textureDimensions(under));\nlet dist = length(in.p);\nlet t0 = thickness(dist, frag.z, textureLoad(under_depth, px, 0).x);\nlet bend = n.xz * sp.optics.w * min(t0, 1.0);\nvar uv = (frag.xy + bend) / size;\nvar t = thickness(dist, frag.z, textureLoad(under_depth, vec2i(uv * size), 0).x);\nif (textureLoad(under_depth, vec2i(uv * size), 0).x > frag.z) {\nuv = frag.xy / size;\nt = t0;\n}\nlet behind_light = textureSampleLevel(under, clamp_smp, uv, 0.0).rgb * sp.shore.z;\nlet trans = exp(-sp.optics.rgb * t);\nbelow = behind_light * trans + body * (vec3f(1.0) - trans);\nlet ripple = textureSampleLevel(displacement, wrap, sp.cascade[u32(sp.sea.y) - 1u].xy + in.rest * sp.cascade[u32(sp.sea.y) - 1u].z, u32(sp.sea.y) - 1u, 0.0).y;\nshore_foam = saturate((1.0 - t0 / sp.shore.x) * 1.4 + ripple * 2.0) * saturate(t0 * 8.0);\n}\nlet water = sky * fresnel + (below + sss) * (1.0 - fresnel) + spec;\nlet foam_light = (sh_irradiance(n) + sun * (nl / PI)) * 0.85;\nlet foam = saturate(fold * sp.sea.w + shore_foam);\nlet lit = mix(water, foam_light, foam);\nlet fogged = apply_fog(lit, in.p, lighting.fog, lighting.fog2, lighting.fog_air.rgb, sh_irradiance(normalize(in.p)), sun, l);\nreturn vec4f(fogged * lighting.env.z, 1.0);\n}\nstruct Camera {\nview_proj: mat4x4f,\nfrustum: array<vec4f, 6>,\nlight_dir: vec4f,\norigin: vec4f,\n}\nstruct Lighting {\nsun_color: vec4f,\nenv: vec4f,\nsh: array<vec4f, 9>,\nscreen: vec4f,\nview: vec4f,\nao_reproject: mat4x4f,\nao: vec4f,\nfog: vec4f,\nfog2: vec4f,\nfog_air: vec4f,\ngi_grid: vec4f,\ngi_counts: vec4f,\nssr: vec4f,\ngi_cascades: array<vec4f, 3>,\n}\nfn apply_fog(color: vec3f, p: vec3f, fog: vec4f, fog2: vec4f, air: vec3f, ambient: vec3f, sun: vec3f, sun_dir: vec3f) -> vec3f {\nlet dist = length(p);\nif (dist <= 0.0 || (fog.x <= 0.0 && fog2.x <= 0.0)) {\nreturn color;\n}\nlet dir = p / dist;\nlet t = exp(-(air * (fog2.x * dist) + vec3f(height_fog_depth(fog, dir, dist))));\nreturn color * t + fog_inscatter(fog2, dir, ambient, sun, sun_dir) * (vec3f(1.0) - t);\n}\nfn height_fog_depth(fog: vec4f, dir: vec3f, dist: f32) -> f32 {\nif (fog.x <= 0.0) {\nreturn 0.0;\n}\nlet falloff = max(fog.y, 1e-5);\nlet len = max(dist - fog.w, 0.0);\nlet h0 = fog.z + dir.y * min(dist, fog.w);\nlet k = falloff * dir.y * len;\nlet shape = select((1.0 - exp(-k)) / k, 1.0 - 0.5 * k, abs(k) < 1e-3);\nreturn fog.x * exp(-falloff * h0) * len * shape;\n}\nfn fog_inscatter(fog2: vec4f, dir: vec3f, ambient: vec3f, sun: vec3f, sun_dir: vec3f) -> vec3f {\nlet g = fog2.z;\nlet c = dot(dir, sun_dir);\nlet phase = (1.0 - g * g) / (FOUR_PI * pow(max(1.0 + g * g - 2.0 * g * c, 1e-4), 1.5));\nreturn ambient + sun * (phase * fog2.y);\n}\nconst FOUR_PI = 12.5663706;",
10
+ hash: '0d0f44afc28096f2',
11
+ wgsl: "const PI = 3.14159265;\nconst EARTH_RADIUS = 6371000.0;\nconst MAX_CASCADES = 4u;\nstruct SurfaceParams {\ncascade: array<vec4f, MAX_CASCADES>,\ngrid: vec4f,\nsea: vec4f,\nbody: vec4f,\nsss: vec4f,\nsize: vec4f,\noptics: vec4f,\nshore: vec4f,\nmirror: vec4f,\nsurf0: vec4f,\nsurf1: vec4f,\nsurf2: vec4f,\nsurf3: vec4f,\n}\n@group(0) @binding(0) var<uniform> camera: Camera;\n@group(0) @binding(1) var<uniform> lighting: Lighting;\n@group(0) @binding(2) var<uniform> sp: SurfaceParams;\n@group(0) @binding(3) var displacement: texture_2d_array<f32>;\n@group(0) @binding(4) var slopes: texture_2d_array<f32>;\n@group(0) @binding(5) var wrap: sampler;\n@group(0) @binding(6) var env_cube: texture_cube<f32>;\n@group(0) @binding(7) var under: texture_2d<f32>;\n@group(0) @binding(8) var under_depth: texture_2d<f32>;\n@group(0) @binding(9) var clamp_smp: sampler;\n@group(0) @binding(10) var scene_depth: texture_depth_2d;\n@group(0) @binding(11) var depth_out: texture_storage_2d<r32float, write>;\nstruct WaterMotion {\nview_proj: mat4x4f,\nprev_view_proj: mat4x4f,\neye_move: vec4f,\n}\n@group(0) @binding(12) var prev_displacement: texture_2d_array<f32>;\n@group(0) @binding(13) var<uniform> wm: WaterMotion;\n@group(0) @binding(14) var shore_map: texture_2d<f32>;\n@group(0) @binding(15) var spill_map: texture_2d<f32>;\n@group(0) @binding(16) var cloud_pano: texture_2d<f32>;\nstruct Surf {\nd: vec3f,\nslope: vec2f,\nbroken: f32,\nfoam: f32,\nfade: f32,\nground: f32,\nreach: f32,\ncover: f32,\nwet: f32,\nfilm: f32,\nfront: f32,\nface: f32,\namp: f32,\nworld: vec2f,\ndir: vec2f,\nphase: f32,\nonset: f32,\nfresh: f32,\nfront_h: f32,\nwet_h1: f32,\nwet_h2: f32,\nrun: f32,\n}\nfn pcg(v: u32) -> u32 {\nlet state = v * 747796405u + 2891336453u;\nlet word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\nreturn (word >> 22u) ^ word;\n}\nfn hash1(n: f32) -> f32 {\nreturn f32(pcg(bitcast<u32>(i32(n)))) / 4294967296.0;\n}\nfn hash2(p: vec2f) -> f32 {\nlet q = vec2<i32>(p);\nreturn f32(pcg(bitcast<u32>(q.x) ^ pcg(bitcast<u32>(q.y) + 0x9e3779b9u))) / 4294967296.0;\n}\nfn vnoise(p: vec2f) -> f32 {\nlet i = floor(p);\nlet f = fract(p);\nlet u = f * f * (3.0 - 2.0 * f);\nreturn mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x), mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);\n}\nfn worley(p: vec2f) -> vec2f {\nlet i = floor(p);\nlet f = fract(p);\nvar d1 = 8.0;\nvar d2 = 8.0;\nfor (var y = -1; y <= 1; y++) {\nfor (var x = -1; x <= 1; x++) {\nlet o = vec2f(f32(x), f32(y));\nlet c = i + o;\nlet j = vec2f(hash2(c), hash2(c + vec2f(1013.0, 7919.0)));\nlet d = length(o + j - f) / (0.55 + 0.9 * hash2(c + vec2f(4241.0, 557.0)));\nif (d < d1) {\nd2 = d1;\nd1 = d;\n} else if (d < d2) {\nd2 = d;\n}\n}\n}\nreturn vec2f(d1, d2);\n}\nfn foam_net(p: vec2f, amount: f32, fw: f32) -> vec2f {\nlet a = saturate(amount);\nlet q = p + vec2f(vnoise(p * 2.0), vnoise(p * 2.0 + vec2f(5.2, 1.3))) * 0.36;\nlet big = worley(q * 3.2);\nvar small = vec2f(0.5, 1.0);\nif (sp.size.w > 1.5) {\nsmall = worley(q * 10.0 + vec2f(3.7, 1.9));\n}\nlet r = mix(0.82, 0.12, pow(a, 1.1));\nlet eb = fw * 3.2;\nlet es = fw * 10.0;\nlet mean = saturate(1.0 - 2.6 * r * r);\nlet hb = mix(smoothstep(r - 0.05 - eb, r + 0.05 + eb, big.x), mean, smoothstep(0.15, 0.6, eb));\nlet rs = r * 0.8 + 0.1;\nlet hs = mix(smoothstep(rs - 0.06 - es, rs + 0.06 + es, small.x), saturate(1.0 - 2.6 * rs * rs), smoothstep(0.15, 0.6, es));\nlet net = hb * mix(1.0, hs, 0.55 * (1.0 - a));\nlet cover = net * smoothstep(0.0, 0.06, a);\nlet lumps = mix(0.8, 1.04, saturate(1.0 - small.x * 0.9)) * mix(0.9, 1.0, saturate(1.0 - big.x * 0.8));\nlet light = mix(lumps, 0.92, smoothstep(0.15, 0.6, es));\nreturn vec2f(cover, light);\n}\nstruct Train {\nth: f32,\nu: f32,\nnw: f32,\namp: f32,\nshoaled: f32,\nperiod: f32,\nblend: f32,\n}\nconst TRAINS = array<vec4f, 3>(vec4f(1.0, 1.0, 0.0, 0.0), vec4f(1.37, 0.45, 0.0, 0.31), vec4f(0.79, 0.3, 0.0, 0.67));\nfn wobble(n: f32, i: u32, world: vec2f) -> f32 {\nlet o = vec2f(n * 0.61 + f32(i) * 7.0, n * 0.37 + f32(i) * 3.0);\nreturn ((vnoise(world / 45.0 + o) - 0.5) * 1.2 + (vnoise(world / 14.0 + o * 1.7) - 0.5) * 0.35) * sp.surf3.y;\n}\nfn wave_amp(n: f32, world: vec2f) -> f32 {\nlet group = 1.0 + 0.3 * sin(2.0 * PI * n / 6.3 + 1.3) + 0.25 * (hash1(n) - 0.5);\nlet section = 0.6 + 0.8 * vnoise(world / 28.0 + vec2f(n * 7.13, n * 3.71));\nreturn mix(1.0, group * section, sp.surf3.y);\n}\nfn swash_front(u: f32, run: f32) -> f32 {\nlet rise = 0.18;\nlet up = 1.0 - (1.0 - saturate(u / rise)) * (1.0 - saturate(u / rise));\nlet down = pow(saturate(1.0 - (u - rise) / 0.57), 1.6);\nreturn run * select(down, up, u < rise);\n}\nfn swash_high(u: f32, run: f32, window: f32) -> f32 {\nlet rise = 0.18;\nlet u0 = u - window;\nif (u0 >= rise) {\nreturn swash_front(u0, run);\n}\nif (u >= rise) {\nreturn run;\n}\nreturn max(swash_front(u, run), select(0.0, swash_front(u0 + 1.0, run), u0 < 0.0));\n}\nfn swash_state(e: f32, front_h: f32, wet_h1: f32, wet_h2: f32, run: f32) -> vec4f {\nlet cover = saturate((front_h - e) / 0.03);\nlet r1 = saturate((wet_h1 - e) / 0.06 + 0.4);\nlet r2 = saturate((wet_h2 - e) / 0.12 + 0.4);\nlet damp = 0.3 * saturate(1.0 - e / max(run, 1e-3));\nlet wet = max(0.75 * (0.55 * r1 + 0.45 * r2), damp) * saturate((run - e) / 0.08 + 0.5);\nlet film = 0.02 + 0.04 * cover * saturate((front_h - e) / 0.15);\nlet front = cover * (1.0 - saturate((front_h - e) / 0.015)) * saturate(front_h * 6.0);\nreturn vec4f(cover, wet, film, front);\n}\nfn surf_at(rest: vec2f, time: f32) -> Surf {\nvar s: Surf;\ns.fade = 1.0;\ns.amp = 1.0;\nif (sp.surf0.w < 0.5) {\nreturn s;\n}\nlet uv = sp.surf0.xy + rest * sp.surf0.z;\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nreturn s;\n}\nlet f = textureSampleLevel(shore_map, clamp_smp, uv, 0.0);\nlet sp2 = textureSampleLevel(spill_map, clamp_smp, uv, 0.0);\nlet depth = f.x;\nlet period = sp.surf1.y;\ns.world = uv / sp.surf0.z;\ns.fade = smoothstep(0.2, sp.surf2.y, depth);\nlet hs = max(depth, 0.15);\nlet start = sp.surf2.z;\nlet shoal = pow(start / hs, 0.25) * smoothstep(start, start * 0.6, depth);\nlet dir = vec2f(cos(f.w), sin(f.w));\ns.dir = dir;\nvar tr: array<Train, 3>;\nlet trains = select(1u, 3u, sp.size.w > 0.5);\nfor (var i = 0u; i < trains; i++) {\nlet ti = TRAINS[i];\nlet pi = period * ti.x;\nvar th: f32;\nvar u: f32;\nvar nw: f32;\nif (i == 0u) {\nth = 2.0 * PI * fract(time / period) - atan2(f.z, f.y);\nu = fract(th / (2.0 * PI));\nnw = round((time - sp2.y) / period + 0.5 - fract(u + 0.5));\n} else {\nlet cyc = (time - sp2.y) / pi + ti.w;\nu = fract(cyc);\nth = 2.0 * PI * u;\nnw = round(cyc + 0.5 - fract(u + 0.5));\n}\nlet ff = fract(u + 0.5);\nlet blend = smoothstep(0.82, 1.0, ff);\nvar amp = wave_amp(nw + f32(i) * 101.0, s.world);\nvar wob = wobble(nw, i, s.world);\nif (blend > 0.0) {\namp = mix(amp, wave_amp(nw + 1.0 + f32(i) * 101.0, s.world), blend);\nwob = mix(wob, wobble(nw + 1.0, i, s.world), blend);\n}\nth -= wob;\ntr[i].th = th;\ntr[i].u = fract(th / (2.0 * PI));\ntr[i].nw = nw;\ntr[i].amp = amp;\ntr[i].shoaled = sp.surf1.x * ti.y * amp * shoal;\ntr[i].blend = blend;\ntr[i].period = pi;\n}\nlet gnoise = mix(vnoise(s.world / 35.0 + vec2f(tr[0].nw * 2.3, 0.0)), vnoise(s.world / 35.0 + vec2f((tr[0].nw + 1.0) * 2.3, 0.0)), tr[0].blend);\nlet gamma = sp.surf1.z * (0.85 + 0.3 * gnoise * sp.surf3.y + 0.15 * (1.0 - sp.surf3.y));\nlet hb = gamma * hs;\nvar eta = 0.0;\nvar lean = vec2f(0.0);\nvar slope = 0.0;\nvar face_k = 0.0;\nvar foam = 0.0;\ns.broken = 0.0;\ns.onset = 0.0;\nfor (var i = 0u; i < trains; i++) {\nvar others = 0.0;\nfor (var j = 0u; j < trains; j++) {\nif (j != i) {\nothers += 0.4 * tr[j].shoaled * (0.5 + 0.5 * cos(tr[j].th));\n}\n}\nlet ratio = (tr[i].shoaled + others) / max(hb, 1e-3);\nlet broken = saturate((ratio - 1.0) * 3.0);\nlet share = tr[i].shoaled / max(tr[i].shoaled + others, 1e-4);\nlet H = min(tr[i].shoaled, hb * share + tr[i].shoaled * (1.0 - broken)) * smoothstep(-0.05, 0.25, depth);\nlet th = tr[i].th;\nlet u = tr[i].u;\nlet wn = mix(vnoise(s.world / 22.0 + vec2f(tr[i].nw * 5.1, f32(i) * 9.0)), vnoise(s.world / 22.0 + vec2f((tr[i].nw + 1.0) * 5.1, f32(i) * 9.0)), tr[i].blend);\nlet w = 0.12 * mix(1.0, 0.6 + 0.8 * wn, sp.surf3.y);\nlet stokes = 0.5 * (cos(th) + 0.3 * cos(2.0 * th));\nlet dstokes = 0.5 * (-sin(th) - 0.6 * sin(2.0 * th));\nlet bore = select(1.0 - (u - w) / (1.0 - w), u / w, u < w) - 0.5;\nlet dw = min(0.6 * w, 1.2 / (tr[i].period * sqrt(9.81 * hs)));\nlet rise = saturate(0.5 + u / dw) * saturate(0.5 - (u - w) / dw) + saturate(0.5 + (u - 1.0) / dw);\nlet dbore = (-1.0 / (1.0 - w) + (1.0 / w + 1.0 / (1.0 - w)) * rise) / (2.0 * PI);\nvar e = H * mix(stokes, bore, broken);\nlet de = H * mix(dstokes, dbore, broken);\nlet q = sp.surf1.w * (1.0 - 0.6 * broken);\nvar l = dir * (q * H * 0.5 * sin(th));\nlet onset = broken * (1.0 - broken) * 4.0 * saturate((ratio - 0.9) * 10.0);\nlet top = smoothstep(0.1, 0.5, e / max(H, 1e-3));\nl += dir * (sp.surf3.z * H * 0.55 * onset * top);\ne *= 1.0 + 0.25 * onset * top;\nlet k = 2.0 * PI / (tr[i].period * sqrt(9.81 * hs));\neta += e;\nlean += l;\nslope += -k * de;\nface_k += smoothstep(-0.2, 0.45, e / max(H, 1e-3)) * saturate(H * 1.2) * (1.0 - 0.65 * broken + 0.5 * onset) * TRAINS[i].y;\nlet fo = broken * smoothstep(0.0, w, u) * (exp(-(u - w) * 9.0) + 0.15 * exp(-(u - w) * 3.0)) * saturate(H * 3.0) * TRAINS[i].y;\nfoam = 1.0 - (1.0 - foam) * (1.0 - fo);\ns.broken = max(s.broken, broken);\ns.onset = max(s.onset, onset * TRAINS[i].y);\n}\ns.slope = slope * dir;\ns.d = vec3f(lean.x, eta, lean.y);\ns.face = saturate(-slope * 1.5) * saturate(face_k);\ns.foam = foam;\nlet u = tr[0].u;\ns.phase = u;\ns.amp = tr[0].amp;\ns.ground = -depth;\ns.reach = max(sp2.x, s.ground);\nif (s.ground > -0.05) {\nlet e = s.reach;\nlet rn = mix(vnoise(s.world / 9.0 + vec2f(tr[0].nw * 3.3, 1.0)), vnoise(s.world / 9.0 + vec2f((tr[0].nw + 1.0) * 3.3, 1.0)), tr[0].blend);\nlet run = sp.surf1.x * s.amp * 0.8 * mix(1.0, 0.7 + 0.6 * rn, sp.surf3.y);\ns.run = run;\ns.front_h = swash_front(u, run);\ns.wet_h1 = swash_high(u, run, 1.5 / sp.surf1.y);\ns.wet_h2 = swash_high(u, run, 4.0 / sp.surf1.y);\nlet w = swash_state(e, s.front_h, s.wet_h1, s.wet_h2, run);\ns.cover = w.x;\ns.wet = w.y;\ns.film = w.z;\ns.front = w.w;\n}\nreturn s;\n}\nfn on_beach(d: vec3f, s: Surf, spacing: f32) -> vec3f {\nif (s.ground <= 0.0) {\nreturn d;\n}\nlet reach = sp.surf1.x * 0.8 * 2.0 + 0.05;\nlet y = select(s.ground - 1.0, s.ground + s.film + 0.03 + 0.03 * spacing, s.reach < reach);\nlet k = smoothstep(0.0, 0.04, s.ground);\nreturn vec3f(d.x * (1.0 - k), mix(d.y, y, k), d.z * (1.0 - k));\n}\n@group(0) @binding(17) var surf_a: texture_2d<f32>;\n@group(0) @binding(18) var surf_b: texture_2d<f32>;\n@group(0) @binding(19) var surf_c: texture_2d<f32>;\n@group(0) @binding(20) var surf_d: texture_2d<f32>;\n@group(0) @binding(21) var surf_a_prev: texture_2d<f32>;\n@group(0) @binding(22) var surf_d_prev: texture_2d<f32>;\n@group(0) @binding(23) var out_a: texture_storage_2d<rgba16float, write>;\n@group(0) @binding(24) var out_b: texture_storage_2d<rgba16float, write>;\n@group(0) @binding(25) var out_c: texture_storage_2d<rgba16float, write>;\n@group(0) @binding(26) var out_d: texture_storage_2d<rgba16float, write>;\n@compute @workgroup_size(8, 8) fn surf_field(@builtin(global_invocation_id) id: vec3u) {\nlet n = textureDimensions(out_a);\nif (any(id.xy >= n)) {\nreturn;\n}\nlet uv = (vec2f(id.xy) + 0.5) / vec2f(n);\nlet rest = (uv - sp.surf0.xy) / sp.surf0.z;\nlet s = surf_at(rest, sp.surf3.x);\ntextureStore(out_a, vec2i(id.xy), vec4f(s.d, s.broken));\ntextureStore(out_b, vec2i(id.xy), vec4f(s.slope, s.face, s.onset));\ntextureStore(out_c, vec2i(id.xy), vec4f(s.foam, s.front_h, s.wet_h1, s.wet_h2));\nlet dt = clamp(sp.surf3.x - sp.surf3.w, 0.0, 0.1);\nlet v = surf_flow(s);\nlet extent = 1.0 / sp.surf0.z;\nlet back = uv - v * dt / extent;\nlet texel = 1.0 / vec2f(n);\nlet f0 = textureSampleLevel(surf_d_prev, clamp_smp, back, 0.0).w;\nlet fs = textureSampleLevel(surf_d_prev, clamp_smp, back + vec2f(texel.x, 0.0), 0.0).w + textureSampleLevel(surf_d_prev, clamp_smp, back - vec2f(texel.x, 0.0), 0.0).w + textureSampleLevel(surf_d_prev, clamp_smp, back + vec2f(0.0, texel.y), 0.0).w + textureSampleLevel(surf_d_prev, clamp_smp, back - vec2f(0.0, texel.y), 0.0).w;\nlet carried = mix(f0, fs * 0.25, 0.12 * saturate(dt * 60.0));\nlet life = mix(3.0, 1.2, saturate(carried));\nvar f = carried * exp(-dt / life) * select(1.0, 0.0, s.ground > 0.0 && s.cover < 0.01 && s.wet < 0.05) * select(0.0, 1.0, dt > 0.0);\nf = max(f, saturate(s.foam * 0.9 + s.front * 0.8));\ntextureStore(out_d, vec2i(id.xy), vec4f(s.run, s.phase, s.amp, f));\n}\nfn smooth_sample(t: texture_2d<f32>, uv: vec2f) -> vec4f {\nlet size = vec2f(textureDimensions(t));\nlet p = uv * size - 0.5;\nlet i = floor(p);\nlet f = p - i;\nlet w0 = (1.0 - f) * (1.0 - f) * (1.0 - f) / 6.0;\nlet w1 = (4.0 - 6.0 * f * f + 3.0 * f * f * f) / 6.0;\nlet w2 = (1.0 + 3.0 * f + 3.0 * f * f - 3.0 * f * f * f) / 6.0;\nlet w3 = f * f * f / 6.0;\nlet g0 = w0 + w1;\nlet g1 = w2 + w3;\nlet h0 = (i - 0.5 + w1 / g0) / size;\nlet h1 = (i + 1.5 + w3 / g1) / size;\nlet a = textureSampleLevel(t, clamp_smp, vec2f(h0.x, h0.y), 0.0);\nlet b = textureSampleLevel(t, clamp_smp, vec2f(h1.x, h0.y), 0.0);\nlet c = textureSampleLevel(t, clamp_smp, vec2f(h0.x, h1.y), 0.0);\nlet d = textureSampleLevel(t, clamp_smp, vec2f(h1.x, h1.y), 0.0);\nreturn mix(mix(a, b, g1.x), mix(c, d, g1.x), g1.y);\n}\nfn surf_tex(rest: vec2f, prev: bool) -> Surf {\nvar s: Surf;\ns.fade = 1.0;\ns.amp = 1.0;\nif (sp.surf0.w < 0.5) {\nreturn s;\n}\nlet uv = sp.surf0.xy + rest * sp.surf0.z;\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nreturn s;\n}\nlet f = textureSampleLevel(shore_map, clamp_smp, uv, 0.0);\nlet depth = f.x;\ns.fade = smoothstep(0.2, sp.surf2.y, depth);\ns.dir = vec2f(cos(f.w), sin(f.w));\ns.world = uv / sp.surf0.z;\ns.ground = -depth;\ns.reach = max(textureSampleLevel(spill_map, clamp_smp, uv, 0.0).x, s.ground);\nlet a = select(textureSampleLevel(surf_a, clamp_smp, uv, 0.0), textureSampleLevel(surf_a_prev, clamp_smp, uv, 0.0), prev);\ns.d = a.xyz;\ns.broken = a.w;\nlet b = textureSampleLevel(surf_b, clamp_smp, uv, 0.0);\ns.slope = b.xy;\ns.face = b.z;\ns.onset = b.w;\nlet c = textureSampleLevel(surf_c, clamp_smp, uv, 0.0);\nlet dd = textureSampleLevel(surf_d, clamp_smp, uv, 0.0);\nif (s.ground > -0.05) {\nlet e = max(smooth_sample(spill_map, uv).x, -smooth_sample(shore_map, uv).x);\nlet w = swash_state(e, c.y, c.z, c.w, dd.x);\ns.cover = w.x;\ns.wet = w.y;\ns.film = w.z;\ns.front = w.w;\n}\ns.front_h = c.y;\ns.run = dd.x;\ns.phase = dd.y;\ns.amp = dd.z;\ns.fresh = c.x;\ns.foam = max(c.x, dd.w);\nreturn s;\n}\nfn surf_disp(rest: vec2f, spacing: f32, prev: bool) -> vec3f {\nif (sp.surf0.w < 0.5) {\nreturn vec3f(0.0);\n}\nlet uv = sp.surf0.xy + rest * sp.surf0.z;\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nreturn vec3f(0.0);\n}\nlet r = 0.35 * spacing * sp.surf0.z;\nvar d = vec3f(0.0);\nfor (var k = 0u; k < 5u; k++) {\nlet o = array<vec2f, 5>(vec2f(0.0), vec2f(r, 0.0), vec2f(-r, 0.0), vec2f(0.0, r), vec2f(0.0, -r))[k];\nlet w = select(0.125, 0.5, k == 0u);\nlet t = select(textureSampleLevel(surf_a, clamp_smp, uv + o, 0.0), textureSampleLevel(surf_a_prev, clamp_smp, uv + o, 0.0), prev);\nd += t.xyz * w;\n}\nreturn d;\n}\nfn surf_flow(s: Surf) -> vec2f {\nlet hs = max(-s.ground, 0.15);\nif (s.ground > -0.05) {\nreturn s.dir * select(-0.9, 1.6, s.phase < 0.18) * s.cover;\n}\nreturn s.dir * clamp(sqrt(9.81 * hs) * max(s.d.y, 0.0) / hs, 0.0, 3.0);\n}\nconst SPRAY_SIDE = 192u;\nconst SPRAY_SPACING = 0.75;\nstruct SprayOut {\n@builtin(position) clip: vec4f,\n@location(0) q: vec4f,\n@location(1) p: vec3f,\n}\n@vertex fn vs_spray(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> SprayOut {\nvar o: SprayOut;\no.clip = vec4f(0.0, 0.0, -1.0, 1.0);\nif (sp.surf0.w < 0.5 || sp.size.y <= 0.0) {\nreturn o;\n}\nlet extent = 1.0 / sp.surf0.z;\nlet eye = sp.surf0.xy * extent;\nlet gx = f32(ii % SPRAY_SIDE) - f32(SPRAY_SIDE / 2u);\nlet gz = f32(ii / SPRAY_SIDE) - f32(SPRAY_SIDE / 2u);\nlet cell = floor(eye / SPRAY_SPACING) + vec2f(gx, gz);\nlet j = vec2f(hash2(cell), hash2(cell + vec2f(91.0, 37.0)));\nlet rest = (cell + j) * SPRAY_SPACING - eye;\nvar s = surf_tex(rest, false);\nlet tuv = sp.surf0.xy + rest * sp.surf0.z;\ns.phase = textureLoad(surf_d, vec2i(clamp(tuv, vec2f(0.0), vec2f(0.9999)) * vec2f(textureDimensions(surf_d))), 0).y;\nlet age = (s.phase - 0.12) * sp.surf1.y;\nlet life = 0.6 + 0.6 * hash2(cell + vec2f(13.0, 71.0));\nif (s.broken < 0.05 || age < 0.0 || age > life || s.ground > 0.3) {\nreturn o;\n}\nlet H = sp.surf1.x * s.amp;\nlet strength = (0.05 * s.broken + s.onset) * saturate(H * 1.2) * (0.4 + 1.2 * hash2(cell + vec2f(5.0, 3.0)));\nlet v0 = (2.0 + 3.0 * hash2(cell + vec2f(2.0, 9.0))) * saturate(H);\nlet k = 1.8;\nlet rise = v0 / k * (1.0 - exp(-k * age)) - 4.0 * age * age * 0.5;\nlet fwd = s.dir * (1.2 * (1.0 - exp(-1.5 * age)));\nlet base = s.d.y + 0.15;\nlet centre = vec3f(rest.x + fwd.x, sp.sea.x + base + max(rise, 0.0), rest.y + fwd.y);\nlet t = age / life;\nlet size = (0.12 + 0.5 * t) * (0.7 + 0.6 * hash2(cell + vec2f(17.0, 23.0))) * (0.6 + 0.6 * saturate(H));\nlet corner = array<vec2f, 6>(vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0))[vi];\nlet fwdv = normalize(centre);\nlet right = normalize(cross(vec3f(0.0, 1.0, 0.0), fwdv));\nlet up = cross(fwdv, right);\nlet pos = centre + (right * corner.x + up * corner.y) * size;\no.clip = camera.view_proj * vec4f(pos, 1.0);\nlet alpha = strength * smoothstep(0.0, 0.1, t) * (1.0 - smoothstep(0.4, 1.0, t)) * sp.size.y * 0.32;\no.q = vec4f(corner, alpha, length(centre));\no.p = centre;\nreturn o;\n}\n@fragment fn fs_spray(in: SprayOut) -> @location(0) vec4f {\nlet r2 = dot(in.q.xy, in.q.xy);\nif (r2 > 1.0) {\ndiscard;\n}\nlet wisp = vnoise(in.q.xy * 1.8 + in.p.xz * 3.1) * 0.6 + vnoise(in.q.xy * 4.3 + in.p.xz * 1.7) * 0.4;\nvar a = in.q.z * (1.0 - r2) * (1.0 - r2) * smoothstep(0.38, 0.62, wisp) * 1.6;\nlet d = textureLoad(under_depth, vec2i(in.clip.xy), 0).x;\nif (d > 0.0) {\nlet gap = in.q.w * (in.clip.z / d - 1.0);\na *= saturate(gap / 0.5);\n}\nlet v = normalize(in.p);\nlet l = camera.light_dir.xyz;\nlet g = 0.75;\nlet cosv = dot(v, l);\nlet hg = (1.0 - g * g) / (4.0 * PI * pow(1.0 + g * g - 2.0 * g * cosv, 1.5));\nlet light = sh_irradiance(vec3f(0.0, 1.0, 0.0)) * 0.6 + lighting.sun_color.rgb * (hg * 3.0 + 0.05);\nlet fogged = apply_fog(light, in.p, lighting.fog, lighting.fog2, lighting.fog_air.rgb, sh_irradiance(v), lighting.sun_color.rgb, l);\nreturn vec4f(fogged * lighting.env.z * a, a);\n}\nstruct VsOut {\n@builtin(position) clip: vec4f,\n@location(0) p: vec3f,\n@location(1) rest: vec2f,\n@location(2) height: f32,\n}\nfn ring_radius(i: f32) -> f32 {\nlet k = sp.grid.y;\nreturn sp.grid.x * (pow(1.0 + k, i) - 1.0) / k;\n}\nfn grid_point(vi: u32) -> vec3f {\nlet segs = u32(sp.grid.w);\nlet ring = f32(vi / (segs + 1u));\nlet seg = f32(vi % (segs + 1u));\nlet r = ring_radius(ring);\nlet a = 2.0 * PI * seg / f32(segs);\nreturn vec3f(vec2f(cos(a), sin(a)) * r, max(ring_radius(ring + 1.0) - r, 2.0 * PI * r / f32(segs)));\n}\nfn calm(c: u32, fade: f32) -> f32 {\nreturn mix(1.0, fade, saturate(1.0 / (sp.cascade[c].z * 150.0)));\n}\nfn displaced(field: texture_2d_array<f32>, rest: vec2f, spacing: f32, fade: f32) -> vec3f {\nvar d = vec3f(0.0);\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet texel = 1.0 / (cc.z * sp.size.x);\nlet w = (1.0 - smoothstep(1.0, 4.0, spacing / texel)) * calm(c, fade);\nif (w <= 0.0) {\ncontinue;\n}\nd += textureSampleLevel(field, wrap, cc.xy + rest * cc.z, c, 0.0).xyz * w;\n}\nreturn d;\n}\nstruct MotionOut {\n@builtin(position) clip: vec4f,\n@location(0) now: vec4f,\n@location(1) then: vec4f,\n}\n@vertex fn vs_motion(@builtin(vertex_index) vi: u32) -> MotionOut {\nlet g = grid_point(vi);\nlet rest = g.xy;\nlet drop = dot(rest, rest) / (2.0 * EARTH_RADIUS);\nlet sn = surf_tex(rest, false);\nlet sq = surf_tex(rest, wm.eye_move.w > 0.5);\nlet dn = on_beach(displaced(displacement, rest, g.z, sn.fade) + surf_disp(rest, g.z, false), sn, g.z);\nlet dp = select(dn, on_beach(displaced(prev_displacement, rest, g.z, sq.fade) + surf_disp(rest, g.z, true), sq, g.z), wm.eye_move.w > 0.5);\nlet p = vec3f(rest.x + dn.x, sp.sea.x + dn.y - drop, rest.y + dn.z);\nlet q = vec3f(rest.x + dp.x, sp.sea.x + dp.y - drop, rest.y + dp.z) + wm.eye_move.xyz;\nvar o: MotionOut;\no.now = wm.view_proj * vec4f(p, 1.0);\no.clip = camera.view_proj * vec4f(p, 1.0);\no.then = wm.prev_view_proj * vec4f(q, 1.0);\nreturn o;\n}\n@fragment fn fs_motion(in: MotionOut) -> @location(0) vec4f {\nlet now = in.now.xy / in.now.w;\nlet then = in.then.xy / max(in.then.w, 1e-6);\nreturn vec4f((now - then) * vec2f(0.5, -0.5), in.then.w, 1.0);\n}\n@vertex fn vs(@builtin(vertex_index) vi: u32) -> VsOut {\nlet segs = u32(sp.grid.w);\nlet ring = f32(vi / (segs + 1u));\nlet seg = f32(vi % (segs + 1u));\nlet r = ring_radius(ring);\nlet a = 2.0 * PI * seg / f32(segs);\nlet rest = vec2f(cos(a), sin(a)) * r;\nlet spacing = max(ring_radius(ring + 1.0) - r, 2.0 * PI * r / f32(segs));\nlet sf = surf_tex(rest, false);\nvar d = vec3f(0.0);\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet texel = 1.0 / (cc.z * sp.size.x);\nlet w = (1.0 - smoothstep(1.0, 4.0, spacing / texel)) * calm(c, sf.fade);\nif (w <= 0.0) {\ncontinue;\n}\nd += textureSampleLevel(displacement, wrap, cc.xy + rest * cc.z, c, 0.0).xyz * w;\n}\nd = on_beach(d + surf_disp(rest, spacing, false), sf, spacing);\nlet drop = dot(rest, rest) / (2.0 * EARTH_RADIUS);\nlet p = vec3f(rest.x + d.x, sp.sea.x + d.y - drop, rest.y + d.z);\nvar o: VsOut;\no.clip = camera.view_proj * vec4f(p, 1.0);\no.p = p;\no.rest = rest;\no.height = d.y;\nreturn o;\n}\nfn sh_irradiance(n: vec3f) -> vec3f {\nreturn max(lighting.sh[0].rgb * 0.282095 + lighting.sh[1].rgb * (0.488603 * n.y) + lighting.sh[2].rgb * (0.488603 * n.z) + lighting.sh[3].rgb * (0.488603 * n.x) + lighting.sh[4].rgb * (1.092548 * n.x * n.y) + lighting.sh[5].rgb * (1.092548 * n.y * n.z) + lighting.sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0)) + lighting.sh[7].rgb * (1.092548 * n.x * n.z) + lighting.sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y)), vec3f(0.0));\n}\n@compute @workgroup_size(8, 8) fn copy_depth(@builtin(global_invocation_id) id: vec3u) {\nlet size = textureDimensions(scene_depth);\nif (id.x >= size.x || id.y >= size.y) {\nreturn;\n}\ntextureStore(depth_out, vec2i(id.xy), vec4f(textureLoad(scene_depth, vec2i(id.xy), 0), 0.0, 0.0, 0.0));\n}\nfn thickness(dist: f32, ds: f32, dop: f32) -> f32 {\nreturn select(1.0e9, dist * max(ds / dop - 1.0, 0.0), dop > 0.0);\n}\nfn mirror_probe(p: vec3f, r: vec3f, t: f32, size: vec2f) -> vec4f {\nlet c = camera.view_proj * vec4f(p + r * t, 1.0);\nif (c.w <= 0.0) {\nreturn vec4f(0.0);\n}\nlet ndc = c.xyz / c.w;\nlet uv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\nif (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0))) {\nreturn vec4f(0.0);\n}\nlet d = textureLoad(under_depth, vec2i(uv * size), 0).x;\nlet gap = select(-1.0e9, c.w * (1.0 - ndc.z / d), d > 0.0);\nreturn vec4f(uv, gap, 1.0);\n}\nfn mirror_scene(p: vec3f, r: vec3f, size: vec2f, jitter: f32) -> vec4f {\nlet len = sp.mirror.x;\nlet steps = u32(sp.mirror.y);\nif (len <= 0.0 || steps == 0u || sp.shore.y < 0.5 || r.y <= 0.0) {\nreturn vec4f(0.0);\n}\nvar prev = 0.0;\nvar was_behind = false;\nfor (var i = 1u; i <= steps; i++) {\nlet f = (f32(i) - jitter) / f32(steps);\nlet t = len * f * f;\nlet s = mirror_probe(p, r, t, size);\nif (s.w < 0.5) {\nbreak;\n}\nlet behind = s.z > 0.0;\nif (behind && !was_behind) {\nvar a = prev;\nvar b = t;\nvar hit = s;\nfor (var k = 0u; k < 6u; k++) {\nlet m = 0.5 * (a + b);\nlet sm = mirror_probe(p, r, m, size);\nif (sm.w > 0.5 && sm.z > 0.0) {\nb = m;\nhit = sm;\n} else {\na = m;\n}\n}\nif (hit.z < sp.mirror.z + 0.03 * b) {\nlet uv = hit.xy;\nlet edge = min(min(uv.x, 1.0 - uv.x), min(uv.y, 1.0 - uv.y));\nlet fade = saturate(edge * 12.0) * (1.0 - smoothstep(0.7, 1.0, b / len));\nreturn vec4f(textureSampleLevel(under, clamp_smp, uv, 0.0).rgb * sp.shore.z, fade);\n}\n}\nwas_behind = behind;\nprev = t;\n}\nreturn vec4f(0.0);\n}\nfn ign(px: vec2f) -> f32 {\nlet frame = floor(camera.light_dir.w * 60.0) % 64.0;\nlet q = px + 5.588238 * frame;\nreturn fract(52.9829189 * fract(dot(q, vec2f(0.06711056, 0.00583715))));\n}\n@fragment fn fs(in: VsOut) -> @location(0) vec4f {\nlet frag = in.clip;\nvar sx = 0.0;\nvar sz = 0.0;\nvar lost = 0.0;\nvar fold = 0.0;\nlet sf = surf_tex(in.rest, false);\nfor (var c = 0u; c < u32(sp.sea.y); c++) {\nlet cc = sp.cascade[c];\nlet uv = cc.xy + in.rest * cc.z;\nlet texels = length(fwidth(uv)) * sp.size.x;\nlet resolved = 1.0 - smoothstep(0.5, 2.0, texels);\nlet w = resolved * calm(c, sf.fade);\nlet s = textureSampleLevel(slopes, wrap, uv, c, 0.0);\nsx += s.x * w;\nsz += s.y * w;\nlost += cc.w * (1.0 - resolved) * calm(c, sf.fade);\nfold = max(fold, max(saturate((sp.sea.z - s.z) * 2.0), s.w) * w);\n}\nlet fine = u32(sp.sea.y) - 1u;\nfor (var k = 0u; k < 2u; k++) {\nlet scale = select(8.3, 3.1, k == 0u);\nlet amp = select(0.5, 0.7, k == 0u) * sp.shore.w;\nlet ang = select(1.24, 0.65, k == 0u);\nlet rot = mat2x2f(cos(ang), sin(ang), -sin(ang), cos(ang));\nlet q = rot * (sp.cascade[fine].xy + in.rest * sp.cascade[fine].z) * scale + vec2f(0.37, 0.71) * f32(k + 1u);\nlet resolved = 1.0 - smoothstep(0.5, 2.0, length(fwidth(q)) * sp.size.x);\nlet t = textureSampleLevel(slopes, wrap, q, fine, 0.0).xy * amp;\nlet back = transpose(rot) * t;\nsx += back.x * resolved;\nsz += back.y * resolved;\nlost += sp.cascade[fine].w * amp * amp * (1.0 - resolved);\n}\nsx += sf.slope.x;\nsz += sf.slope.y;\nfold *= sf.fade;\nlet n = normalize(vec3f(-sx, 1.0, -sz));\nlet v = normalize(-in.p);\nlet l = camera.light_dir.xyz;\nlet nv = max(dot(n, v), 1e-3);\nlet nl = max(dot(n, l), 0.0);\nlet a2 = max(sp.body.w * sp.body.w + 2.0 * lost, 1e-4);\nlet f0 = 0.02;\nlet fresnel = f0 + (1.0 - f0) * pow(1.0 - nv, 5.0);\nvar r = reflect(-v, n);\nr.y = abs(r.y);\nlet lod = clamp(sqrt(sqrt(a2)) * (lighting.env.x - 1.0), 0.0, lighting.env.x - 1.0);\nvar sky = textureSampleLevel(env_cube, wrap, r, lod).rgb * lighting.view.rgb;\nif (sp.size.z > 0.5) {\nlet pn = vec2f(textureDimensions(cloud_pano));\nlet pv = clamp(sqrt(max(r.y, 0.0)), 0.5 / pn.y, 1.0 - 0.5 / pn.y);\nlet cl = textureSampleLevel(cloud_pano, wrap, vec2f(fract(atan2(r.z, r.x) / (2.0 * PI)), pv), 0.0);\nsky = sky * cl.a + cl.rgb;\n}\nlet seen = mirror_scene(in.p, reflect(-v, n), vec2f(textureDimensions(under)), ign(frag.xy));\nsky = mix(sky, seen.rgb, seen.a);\nif (sp.mirror.w > 0.5) {\nreturn vec4f(select(vec3f(seen.a * 0.2, 0.0, 0.0) + seen.rgb * seen.a, vec3f(0.3, 0.0, 0.0), seen.a <= 0.0) * lighting.env.z, 1.0);\n}\nlet h = normalize(v + l);\nlet nh = max(dot(n, h), 0.0);\nlet dd = nh * nh * (a2 - 1.0) + 1.0;\nlet ggx = a2 / (PI * dd * dd);\nlet vis = 0.5 / (nl * sqrt(nv * nv * (1.0 - a2) + a2) + nv * sqrt(nl * nl * (1.0 - a2) + a2) + 1e-5);\nlet fh = f0 + (1.0 - f0) * pow(1.0 - max(dot(v, h), 0.0), 5.0);\nlet sun = lighting.sun_color.rgb;\nlet spec = sun * (ggx * vis * fh * nl);\nlet up = sh_irradiance(vec3f(0.0, 1.0, 0.0));\nlet body = sp.body.rgb * (up + sun * (max(l.y, 0.0) / PI) * 0.5);\nlet behind = pow(saturate(dot(-v, l)), 8.0);\nlet crest = saturate(in.height * 0.5) * saturate(1.0 - n.y * 0.9 + 0.2);\nvar sss = sp.sss.rgb * sun * (behind * crest * sp.sss.w);\nlet wide = 0.35 + 0.65 * pow(saturate(dot(-v, l) * 0.5 + 0.5), 3.0);\nlet thin = sf.face * (0.6 + 0.4 * saturate(1.0 - abs(n.y) * 1.2));\nsss += sp.sss.rgb * (up * 4.5 + sun * (0.8 * wide)) * (thin * sp.sss.w * 2.5);\nvar below = body;\nvar shore_foam = 0.0;\nif (sp.shore.y > 0.5) {\nlet px = vec2i(frag.xy);\nlet size = vec2f(textureDimensions(under));\nlet dist = length(in.p);\nlet t0 = thickness(dist, frag.z, textureLoad(under_depth, px, 0).x);\nlet bend = n.xz * sp.optics.w * min(t0, 1.0);\nvar uv = (frag.xy + bend) / size;\nif (any(uv < vec2f(0.0)) || any(uv >= vec2f(1.0))) {\nuv = frag.xy / size;\n}\nvar t = thickness(dist, frag.z, textureLoad(under_depth, vec2i(uv * size), 0).x);\nif (textureLoad(under_depth, vec2i(uv * size), 0).x > frag.z) {\nuv = frag.xy / size;\nt = t0;\n}\nvar behind_light = textureSampleLevel(under, clamp_smp, uv, 0.0).rgb * sp.shore.z;\nlet down = max(t * v.y, 0.0);\nlet sun_in = sqrt(max(1.0 - (1.0 - l.y * l.y) / 1.7689, 0.04));\nlet above = (in.p - v * t).xz + vec2f(l.x, l.z) / sun_in * down * (1.0 / 1.33);\nvar lap = 0.0;\nlet cn = u32(sp.sea.y);\nlet c0 = select(0u, cn - 2u, cn >= 2u);\nfor (var c = c0; c < select(c0, select(1u, cn - 1u, cn >= 2u), sp.size.w > 0.5); c++) {\nlet cc = sp.cascade[c];\nlet e = 2.0 / (cc.z * sp.size.x);\nlet q = cc.xy + above * cc.z;\nlet ex = vec2f(cc.z * e, 0.0);\nlet ez = vec2f(0.0, cc.z * e);\nlet dsx = textureSampleLevel(slopes, wrap, q + ex, c, 0.0).x - textureSampleLevel(slopes, wrap, q - ex, c, 0.0).x;\nlet dsz = textureSampleLevel(slopes, wrap, q + ez, c, 0.0).y - textureSampleLevel(slopes, wrap, q - ez, c, 0.0).y;\nlap += (dsx + dsz) / (2.0 * e);\n}\nlet focus = 1.0 / max(abs(1.0 - down * 0.248 * lap), 0.25);\nlet caustic = mix(1.0, focus, saturate(down * 3.0) * exp(-down / 4.0) * saturate(l.y * 4.0));\nbehind_light *= mix(1.0, caustic, 0.65);\nlet trans = exp(-sp.optics.rgb * (t + down / sun_in));\nbelow = behind_light * trans + body * (vec3f(1.0) - exp(-sp.optics.rgb * t));\nlet vertical = t0 * max(v.y, 0.02);\nshore_foam = saturate(1.0 - vertical / sp.shore.x) * saturate(vertical * 40.0);\n}\nlet sigma = sqrt(lost);\nlet mean_f = f0 + (1.0 - f0) * pow(1.0 - nv, 5.0 * exp(-2.69 * sigma)) / (1.0 + 22.7 * pow(sigma, 1.5));\nlet fr = min(fresnel, mean_f);\nlet water = sky * fr + (below + sss) * (1.0 - fr) + spec;\nlet foam_light = (sh_irradiance(n) + sun * (nl / PI)) * 0.85;\nvar fp = (sp.cascade[0].xy + in.rest * sp.cascade[0].z) / sp.cascade[0].z;\nif (sf.foam > 0.001 || sf.front > 0.001) {\nfp = sf.world;\n}\nlet flow = surf_flow(sf);\nlet cyc = 1.0;\nlet ph0 = fract(sp.surf3.x / cyc);\nlet ph1 = fract(sp.surf3.x / cyc + 0.5);\nlet fp0 = fp - flow * (ph0 * cyc) + vec2f(13.7, 5.1) * floor(sp.surf3.x / cyc);\nlet fp1 = fp - flow * (ph1 * cyc) + vec2f(7.3, 11.9) * floor(sp.surf3.x / cyc + 0.5);\nlet wflow = abs(2.0 * ph0 - 1.0);\nlet beach = smoothstep(0.0, 0.04, sf.ground);\nvar cover = saturate(fold * sp.sea.w + shore_foam * (1.0 - beach) + sf.foam + sf.front * 0.9);\nif (cover > 0.002) {\nlet spots = vnoise(fp * 0.22) * 0.65 + vnoise(fp * 0.61 + vec2f(9.0, 4.0)) * 0.35;\ncover *= mix(smoothstep(0.35, 0.75, spots), 1.0, smoothstep(0.35, 0.8, cover));\nlet clumps = vnoise(fp * 0.35 + vec2f(3.0, 7.0)) * 0.6 + vnoise(fp * 1.1 + vec2f(11.0, 2.0)) * 0.4;\ncover *= mix(1.0, 0.45 + 0.75 * clumps, smoothstep(0.3, 0.7, cover));\n}\nlet fw = length(fwidth(fp0));\nvar foam = 0.0;\nvar bubbles = 1.0;\nif (cover > 0.002) {\nvar net = foam_net(fp0, cover, fw);\nif (dot(flow, flow) > 1e-4) {\nnet = mix(net, foam_net(fp1, cover, fw), wflow);\n}\nfoam = net.x;\nbubbles = net.y;\n}\nlet aerated = saturate(sf.fresh * 1.6) * (1.0 - beach);\nlet milky = mix(sp.sss.rgb * 2.5, vec3f(0.85, 0.95, 0.95), 0.55) * (sh_irradiance(n) + sun * (nl / PI));\nvar foam_lit = foam_light;\nif (foam > 0.01 && sp.surf0.w > 0.5) {\nlet fuv = sp.surf0.xy + in.rest * sp.surf0.z;\nlet ft = 1.0 / vec2f(textureDimensions(surf_d));\nlet gx = textureSampleLevel(surf_d, clamp_smp, fuv + vec2f(ft.x, 0.0), 0.0).w - textureSampleLevel(surf_d, clamp_smp, fuv - vec2f(ft.x, 0.0), 0.0).w;\nlet gz = textureSampleLevel(surf_d, clamp_smp, fuv + vec2f(0.0, ft.y), 0.0).w - textureSampleLevel(surf_d, clamp_smp, fuv - vec2f(0.0, ft.y), 0.0).w;\nlet per_m = 0.1 / (2.0 * ft.x / sp.surf0.z);\nlet nf = normalize(n + vec3f(-gx, 0.0, -gz) * per_m * 0.5);\nfoam_lit = (sh_irradiance(nf) + sun * (max(dot(nf, l), 0.0) / PI)) * 0.85;\n}\nvar lit = mix(mix(water, milky, aerated * 0.65), foam_lit * bubbles, foam);\nif (sf.ground > 0.0 && sp.shore.y > 0.5) {\nlet sand = textureLoad(under, vec2i(frag.xy), 0).rgb * sp.shore.z;\nlet wet_rough = 0.12;\nlet sheen = textureSampleLevel(env_cube, wrap, r, clamp(sqrt(wet_rough) * (lighting.env.x - 1.0), 0.0, lighting.env.x - 1.0)).rgb * lighting.view.rgb;\nlet wet_sand = sand * mix(1.0, 0.5, sf.wet) + sheen * fresnel * sf.wet * 0.8;\nlit = mix(wet_sand, lit, max(sf.cover, foam));\n}\nlet fogged = apply_fog(lit, in.p, lighting.fog, lighting.fog2, lighting.fog_air.rgb, sh_irradiance(normalize(in.p)), sun, l);\nreturn vec4f(fogged * lighting.env.z, 1.0);\n}\nstruct Camera {\nview_proj: mat4x4f,\nfrustum: array<vec4f, 6>,\nlight_dir: vec4f,\norigin: vec4f,\n}\nstruct Lighting {\nsun_color: vec4f,\nenv: vec4f,\nsh: array<vec4f, 9>,\nscreen: vec4f,\nview: vec4f,\nao_reproject: mat4x4f,\nao: vec4f,\nfog: vec4f,\nfog2: vec4f,\nfog_air: vec4f,\ngi_grid: vec4f,\ngi_counts: vec4f,\nssr: vec4f,\ngi_cascades: array<vec4f, 3>,\n}\nfn apply_fog(color: vec3f, p: vec3f, fog: vec4f, fog2: vec4f, air: vec3f, ambient: vec3f, sun: vec3f, sun_dir: vec3f) -> vec3f {\nlet dist = length(p);\nif (dist <= 0.0 || (fog.x <= 0.0 && fog2.x <= 0.0)) {\nreturn color;\n}\nlet dir = p / dist;\nlet t = exp(-(air * (fog2.x * dist) + vec3f(height_fog_depth(fog, dir, dist))));\nreturn color * t + fog_inscatter(fog2, dir, ambient, sun, sun_dir) * (vec3f(1.0) - t);\n}\nfn height_fog_depth(fog: vec4f, dir: vec3f, dist: f32) -> f32 {\nif (fog.x <= 0.0) {\nreturn 0.0;\n}\nlet falloff = max(fog.y, 1e-5);\nlet len = max(dist - fog.w, 0.0);\nlet h0 = fog.z + dir.y * min(dist, fog.w);\nlet k = falloff * dir.y * len;\nlet shape = select((1.0 - exp(-k)) / k, 1.0 - 0.5 * k, abs(k) < 1e-3);\nreturn fog.x * exp(-falloff * h0) * len * shape;\n}\nfn fog_inscatter(fog2: vec4f, dir: vec3f, ambient: vec3f, sun: vec3f, sun_dir: vec3f) -> vec3f {\nlet g = fog2.z;\nlet c = dot(dir, sun_dir);\nlet phase = (1.0 - g * g) / (FOUR_PI * pow(max(1.0 + g * g - 2.0 * g * c, 1e-4), 1.5));\nreturn ambient + sun * (phase * fog2.y);\n}\nconst FOUR_PI = 12.5663706;",
12
12
  };
13
13
  //# sourceMappingURL=water-shaders.gen.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"water-shaders.gen.js","sourceRoot":"","sources":["../src/water-shaders.gen.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,yEAAyE;AAEzE,4CAA4C;AAC5C,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,stGAAstG;CACptG,CAAC;AAEX,8CAA8C;AAC9C,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,ynRAAynR;CACvnR,CAAC"}
1
+ {"version":3,"file":"water-shaders.gen.js","sourceRoot":"","sources":["../src/water-shaders.gen.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,yEAAyE;AAEzE,4CAA4C;AAC5C,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,8+GAA8+G;CAC5+G,CAAC;AAEX,8CAA8C;AAC9C,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,myiCAAmyiC;CACjyiC,CAAC"}