@yali-engine/water 0.11.0 → 0.12.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 +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/shore.d.ts +35 -0
- package/dist/shore.js +189 -0
- package/dist/shore.js.map +1 -0
- package/dist/water-shaders.gen.d.ts +4 -4
- package/dist/water-shaders.gen.js +4 -4
- package/dist/water-shaders.gen.js.map +1 -1
- package/dist/water.d.ts +66 -0
- package/dist/water.js +391 -3
- package/dist/water.js.map +1 -1
- package/dist/waves.d.ts +2 -0
- package/dist/waves.js +14 -2
- package/dist/waves.js.map +1 -1
- package/package.json +2 -2
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 {
|
|
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,
|
|
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"}
|
package/dist/shore.d.ts
ADDED
|
@@ -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: "
|
|
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,
|
|
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: "
|
|
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: "d76b79846f4edbf0";
|
|
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,\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.72, 0.0, pow(a, 0.65));\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 = max(net, smoothstep(0.8, 1.0, a)) * smoothstep(0.0, 0.06, a);\nlet lumps = mix(0.66, 1.06, saturate(1.0 - small.x * 0.9)) * mix(0.84, 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}\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 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;\nlet clock = 2.0 * PI * fract(time / period);\nlet th = clock - atan2(f.z, f.y);\nlet u = fract(th / (2.0 * PI));\ns.phase = u;\nlet ff = fract(u + 0.5);\nlet nw = round((time - sp2.y) / period + 0.5 - ff);\ns.world = uv / sp.surf0.z;\ns.amp = mix(wave_amp(nw, s.world), wave_amp(nw + 1.0, s.world), smoothstep(0.82, 1.0, ff));\ns.fade = smoothstep(0.2, sp.surf2.y, depth);\nlet hs = max(depth, 0.15);\nlet start = sp.surf2.z;\nlet shoaled = sp.surf1.x * s.amp * pow(start / hs, 0.25) * smoothstep(start, start * 0.6, depth);\nlet hb = sp.surf1.z * hs;\nlet ratio = shoaled / max(hb, 1e-3);\ns.broken = saturate((ratio - 1.0) * 3.0);\nlet H = min(shoaled, hb) * smoothstep(-0.05, 0.25, depth);\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 w = 0.12;\nlet bore = select(1.0 - (u - w) / (1.0 - w), u / w, u < w) - 0.5;\nlet dbore = select(-1.0 / (1.0 - w), 1.0 / w, u < w) / (2.0 * PI);\nvar eta = H * mix(stokes, bore, s.broken);\nlet deta = H * mix(dstokes, dbore, s.broken);\nlet dir = vec2f(cos(f.w), sin(f.w));\ns.dir = dir;\nlet q = sp.surf1.w * (1.0 - 0.6 * s.broken);\nvar lean = dir * (q * H * 0.5 * sin(th));\nlet onset = s.broken * (1.0 - s.broken) * 4.0 * saturate((ratio - 0.9) * 10.0);\ns.onset = onset;\nlet top = smoothstep(0.1, 0.5, eta / max(H, 1e-3));\nlean += dir * (sp.surf3.z * H * 0.55 * onset * top);\neta *= 1.0 + 0.25 * onset * top;\nlet k = 2.0 * PI / (period * sqrt(9.81 * hs));\ns.slope = -k * deta * dir;\ns.d = vec3f(lean.x, eta, lean.y);\ns.face = saturate(-dot(s.slope, dir) * 1.5) * smoothstep(-0.2, 0.45, eta / max(H, 1e-3)) * saturate(H * 1.2) * (1.0 - 0.65 * s.broken + 0.5 * onset);\ns.foam = s.broken * smoothstep(0.0, w, u) * (exp(-(u - w) * 9.0) + 0.3 * exp(-(u - w) * 3.0)) * saturate(H * 3.0);\ns.ground = -depth;\ns.reach = max(sp2.x, s.ground);\nif (s.ground > -0.05) {\nlet e = s.reach;\nlet run = sp.surf1.x * s.amp * 0.8;\nlet front = run * pow(saturate(1.0 - u / 0.55), 1.6);\ns.cover = saturate((front - e) / 0.03);\nlet passed = 0.55 * (1.0 - pow(saturate(e / max(run, 1e-3)), 1.0 / 1.6));\nlet since = select(0.0, (u - passed) * sp.surf1.y, u > passed);\ns.wet = select(0.0, max(exp(-since / 4.0) * 0.75, 0.35 * saturate(1.0 - e / max(run, 1e-3))), e < run);\ns.film = 0.02 + 0.04 * s.cover * saturate((front - e) / 0.15);\ns.front = s.cover * (1.0 - saturate((front - e) / 0.015)) * saturate(front * 6.0);\n}\nreturn s;\n}\nfn on_beach(d: vec3f, s: Surf) -> 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, 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}\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;\nlet s = surf_at(rest, sp.surf3.x);\nlet age = (s.phase - 0.12) * sp.surf1.y;\nlet life = 1.2 + 0.8 * 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.2 * s.broken + 0.8 * s.onset) * saturate(H * 1.2) * (0.4 + 1.2 * hash2(cell + vec2f(5.0, 3.0)));\nlet v0 = (1.5 + 2.5 * 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.25 + 0.9 * 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.22;\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.25, 0.8, 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.6;\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.9 + lighting.sun_color.rgb * (hg * 2.0 + 0.08);\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 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);\nreturn vec3f(vec2f(cos(a), sin(a)) * r, max(ring_radius(ring + 1.0) - r, 2.0 * PI * r / f32(segs)));\n}\nfn displaced(field: texture_2d_array<f32>, rest: vec2f, spacing: 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);\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_at(rest, sp.surf3.x);\nlet sq = surf_at(rest, select(sp.surf3.x, sp.surf3.w, wm.eye_move.w > 0.5));\nlet dn = on_beach(displaced(displacement, rest, g.z) * sn.fade + sn.d, sn);\nlet dp = select(dn, on_beach(displaced(prev_displacement, rest, g.z) * sq.fade + sq.d, sq), 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 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 sf = surf_at(rest, sp.surf3.x);\nd = on_beach(d * sf.fade + sf.d, sf);\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;\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, max(saturate((sp.sea.z - s.z) * 2.0), s.w) * w);\n}\nlet sf = surf_at(in.rest, sp.surf3.x);\nsx = sx * sf.fade + sf.slope.x;\nsz = sz * sf.fade + 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) {\nlet c = sqrt(9.81 * max(1.0 - sf.ground, 0.3));\nfp = sf.world - sf.dir * (0.5 * c * sp.surf1.y * sf.phase);\n}\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));\n}\nlet fw = length(fwidth(fp));\nvar foam = 0.0;\nvar bubbles = 1.0;\nif (cover > 0.002) {\nlet net = foam_net(fp, cover, fw);\nfoam = net.x;\nbubbles = net.y;\n}\nvar lit = mix(water, foam_light * 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: '
|
|
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,
|
|
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: '
|
|
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: 'd76b79846f4edbf0',
|
|
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,\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.72, 0.0, pow(a, 0.65));\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 = max(net, smoothstep(0.8, 1.0, a)) * smoothstep(0.0, 0.06, a);\nlet lumps = mix(0.66, 1.06, saturate(1.0 - small.x * 0.9)) * mix(0.84, 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}\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 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;\nlet clock = 2.0 * PI * fract(time / period);\nlet th = clock - atan2(f.z, f.y);\nlet u = fract(th / (2.0 * PI));\ns.phase = u;\nlet ff = fract(u + 0.5);\nlet nw = round((time - sp2.y) / period + 0.5 - ff);\ns.world = uv / sp.surf0.z;\ns.amp = mix(wave_amp(nw, s.world), wave_amp(nw + 1.0, s.world), smoothstep(0.82, 1.0, ff));\ns.fade = smoothstep(0.2, sp.surf2.y, depth);\nlet hs = max(depth, 0.15);\nlet start = sp.surf2.z;\nlet shoaled = sp.surf1.x * s.amp * pow(start / hs, 0.25) * smoothstep(start, start * 0.6, depth);\nlet hb = sp.surf1.z * hs;\nlet ratio = shoaled / max(hb, 1e-3);\ns.broken = saturate((ratio - 1.0) * 3.0);\nlet H = min(shoaled, hb) * smoothstep(-0.05, 0.25, depth);\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 w = 0.12;\nlet bore = select(1.0 - (u - w) / (1.0 - w), u / w, u < w) - 0.5;\nlet dbore = select(-1.0 / (1.0 - w), 1.0 / w, u < w) / (2.0 * PI);\nvar eta = H * mix(stokes, bore, s.broken);\nlet deta = H * mix(dstokes, dbore, s.broken);\nlet dir = vec2f(cos(f.w), sin(f.w));\ns.dir = dir;\nlet q = sp.surf1.w * (1.0 - 0.6 * s.broken);\nvar lean = dir * (q * H * 0.5 * sin(th));\nlet onset = s.broken * (1.0 - s.broken) * 4.0 * saturate((ratio - 0.9) * 10.0);\ns.onset = onset;\nlet top = smoothstep(0.1, 0.5, eta / max(H, 1e-3));\nlean += dir * (sp.surf3.z * H * 0.55 * onset * top);\neta *= 1.0 + 0.25 * onset * top;\nlet k = 2.0 * PI / (period * sqrt(9.81 * hs));\ns.slope = -k * deta * dir;\ns.d = vec3f(lean.x, eta, lean.y);\ns.face = saturate(-dot(s.slope, dir) * 1.5) * smoothstep(-0.2, 0.45, eta / max(H, 1e-3)) * saturate(H * 1.2) * (1.0 - 0.65 * s.broken + 0.5 * onset);\ns.foam = s.broken * smoothstep(0.0, w, u) * (exp(-(u - w) * 9.0) + 0.3 * exp(-(u - w) * 3.0)) * saturate(H * 3.0);\ns.ground = -depth;\ns.reach = max(sp2.x, s.ground);\nif (s.ground > -0.05) {\nlet e = s.reach;\nlet run = sp.surf1.x * s.amp * 0.8;\nlet front = run * pow(saturate(1.0 - u / 0.55), 1.6);\ns.cover = saturate((front - e) / 0.03);\nlet passed = 0.55 * (1.0 - pow(saturate(e / max(run, 1e-3)), 1.0 / 1.6));\nlet since = select(0.0, (u - passed) * sp.surf1.y, u > passed);\ns.wet = select(0.0, max(exp(-since / 4.0) * 0.75, 0.35 * saturate(1.0 - e / max(run, 1e-3))), e < run);\ns.film = 0.02 + 0.04 * s.cover * saturate((front - e) / 0.15);\ns.front = s.cover * (1.0 - saturate((front - e) / 0.015)) * saturate(front * 6.0);\n}\nreturn s;\n}\nfn on_beach(d: vec3f, s: Surf) -> 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, 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}\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;\nlet s = surf_at(rest, sp.surf3.x);\nlet age = (s.phase - 0.12) * sp.surf1.y;\nlet life = 1.2 + 0.8 * 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.2 * s.broken + 0.8 * s.onset) * saturate(H * 1.2) * (0.4 + 1.2 * hash2(cell + vec2f(5.0, 3.0)));\nlet v0 = (1.5 + 2.5 * 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.25 + 0.9 * 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.22;\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.25, 0.8, 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.6;\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.9 + lighting.sun_color.rgb * (hg * 2.0 + 0.08);\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 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);\nreturn vec3f(vec2f(cos(a), sin(a)) * r, max(ring_radius(ring + 1.0) - r, 2.0 * PI * r / f32(segs)));\n}\nfn displaced(field: texture_2d_array<f32>, rest: vec2f, spacing: 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);\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_at(rest, sp.surf3.x);\nlet sq = surf_at(rest, select(sp.surf3.x, sp.surf3.w, wm.eye_move.w > 0.5));\nlet dn = on_beach(displaced(displacement, rest, g.z) * sn.fade + sn.d, sn);\nlet dp = select(dn, on_beach(displaced(prev_displacement, rest, g.z) * sq.fade + sq.d, sq), 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 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 sf = surf_at(rest, sp.surf3.x);\nd = on_beach(d * sf.fade + sf.d, sf);\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;\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, max(saturate((sp.sea.z - s.z) * 2.0), s.w) * w);\n}\nlet sf = surf_at(in.rest, sp.surf3.x);\nsx = sx * sf.fade + sf.slope.x;\nsz = sz * sf.fade + 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) {\nlet c = sqrt(9.81 * max(1.0 - sf.ground, 0.3));\nfp = sf.world - sf.dir * (0.5 * c * sp.surf1.y * sf.phase);\n}\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));\n}\nlet fw = length(fwidth(fp));\nvar foam = 0.0;\nvar bubbles = 1.0;\nif (cover > 0.002) {\nlet net = foam_net(fp, cover, fw);\nfoam = net.x;\nbubbles = net.y;\n}\nvar lit = mix(water, foam_light * 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,
|
|
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,youBAAyouB;CACvouB,CAAC"}
|
package/dist/water.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FrameGraph, PipelineCache, RenderFeature, RenderFeatureFactory, RenderFeatureFrame, SceneRenderer } from '@yali-engine/render';
|
|
2
|
+
import { type Seabed, type ShoreField } from './shore.ts';
|
|
2
3
|
import { WaterWaves, type WaterWavesOptions } from './waves.ts';
|
|
3
4
|
/** The ocean's knobs. */
|
|
4
5
|
export interface WaterOptions extends WaterWavesOptions {
|
|
@@ -22,10 +23,30 @@ export interface WaterOptions extends WaterWavesOptions {
|
|
|
22
23
|
readonly refraction?: number;
|
|
23
24
|
/** Water shallower than this foams at a shore, metres (default 0.12). */
|
|
24
25
|
readonly shoreFoam?: number;
|
|
26
|
+
/** The quality tier (ADR-0220, default 'high'): see `Water.quality`. */
|
|
27
|
+
readonly quality?: WaterQuality;
|
|
25
28
|
/** How far the mirror ray is marched for reflections of the scene, metres (default 40; 0: the sky only), and its steps (24). */
|
|
26
29
|
readonly reflections?: number;
|
|
27
30
|
readonly reflectionSteps?: number;
|
|
31
|
+
/**
|
|
32
|
+
* The surf on a shore (ADR-0213), once `setSeabed` gives the water its seabed: its height in deep water (m, default 0.9),
|
|
33
|
+
* period (s, 8), breaking ratio γ (0.78), how far crests lean before breaking (0.55), the depth it rises out of the sea
|
|
34
|
+
* at (7 m) and the depth over which the open sea's waves calm (4 m).
|
|
35
|
+
*/
|
|
36
|
+
readonly surf?: {
|
|
37
|
+
readonly height?: number;
|
|
38
|
+
readonly period?: number;
|
|
39
|
+
readonly breaking?: number;
|
|
40
|
+
readonly lean?: number;
|
|
41
|
+
readonly start?: number;
|
|
42
|
+
readonly calm?: number;
|
|
43
|
+
readonly variety?: number;
|
|
44
|
+
readonly lip?: number;
|
|
45
|
+
readonly spray?: number;
|
|
46
|
+
};
|
|
28
47
|
}
|
|
48
|
+
/** How much the water may cost (ADR-0220): the iGPU target is `medium` or `low` (≤ 2.5 ms, water research §1.1). */
|
|
49
|
+
export type WaterQuality = 'high' | 'medium' | 'low';
|
|
29
50
|
/**
|
|
30
51
|
* An ocean (ADR-0198): FFT waves (JONSWAP, Donelan–Banner) on a radial grid to the horizon, lit by the scene's sky, sun and
|
|
31
52
|
* fog. WebGPU with HDR only.
|
|
@@ -59,12 +80,57 @@ export declare class Water implements RenderFeature {
|
|
|
59
80
|
debugMirror: boolean;
|
|
60
81
|
/** Off: nothing is drawn or computed (live). */
|
|
61
82
|
enabled: boolean;
|
|
83
|
+
/** The waves' own motion for TAA (ADR-0210; live): off, TAA sees the surface as still (the camera's motion only). */
|
|
84
|
+
motion: boolean;
|
|
85
|
+
/** The surf's height in deep water, metres (ADR-0213; live). */
|
|
86
|
+
surfHeight: number;
|
|
87
|
+
/** The breaking ratio γ: a wave breaks when it is taller than γ × the depth (0.78, live). */
|
|
88
|
+
surfBreaking: number;
|
|
89
|
+
/** How far crests lean shoreward before they break (Gerstner's Q, 0–1, live). */
|
|
90
|
+
surfLean: number;
|
|
91
|
+
/** The depth (m) at which the surf rises out of the sea, and over which the open sea's waves calm (live). */
|
|
92
|
+
surfStart: number;
|
|
93
|
+
surfCalm: number;
|
|
94
|
+
/** The surf's period, seconds: set before `setSeabed` (the crests' phase is baked for it). */
|
|
95
|
+
surfPeriod: number;
|
|
96
|
+
/**
|
|
97
|
+
* How much waves differ (ADR-0218; live, 0–1): 1 = swell in sets, each crest's height varying along the shore so it breaks
|
|
98
|
+
* in sections; 0 = every wave alike.
|
|
99
|
+
*/
|
|
100
|
+
surfVariety: number;
|
|
101
|
+
/** How far a crest that starts to break throws its lip forward (ADR-0218; live, 0–1). */
|
|
102
|
+
surfLip: number;
|
|
103
|
+
/** Spray thrown up by breaking waves (ADR-0218; live, 0 = none, 1 = default). */
|
|
104
|
+
spray: number;
|
|
105
|
+
/**
|
|
106
|
+
* The quality tier (ADR-0220; live): `high` everything; `medium` the coarse foam pattern only, 16 reflection steps, half
|
|
107
|
+
* the spray; `low` no caustics, no scene reflections, no spray. Setting it sets those knobs (they stay live after).
|
|
108
|
+
*/
|
|
109
|
+
get quality(): WaterQuality;
|
|
110
|
+
set quality(q: WaterQuality);
|
|
62
111
|
readonly rings: number;
|
|
63
112
|
readonly segments: number;
|
|
64
113
|
constructor(device: GPUDevice, o: WaterOptions);
|
|
65
114
|
init(cache: PipelineCache, format: GPUTextureFormat): Promise<void>;
|
|
66
115
|
prepare(f: RenderFeatureFrame): void;
|
|
116
|
+
/**
|
|
117
|
+
* The seabed under the water (ADR-0213): bakes the shore field (`bakeShore`: depth, the crests' arrival phase for
|
|
118
|
+
* `surfPeriod`, their direction) and uploads it; the surf then rises, bends to the beach and breaks. Call again after
|
|
119
|
+
* changing `surfPeriod` or the seabed.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ocean.setSeabed({ heights, size: 256, cell: 1, origin: [-128, -198] });
|
|
123
|
+
*/
|
|
124
|
+
setSeabed(seabed: Seabed): ShoreField;
|
|
125
|
+
/** The baked shore field, or null before `setSeabed`. */
|
|
126
|
+
get shoreField(): ShoreField | null;
|
|
127
|
+
addMotionPasses(graph: FrameGraph, motion: string, depth: string): void;
|
|
67
128
|
addPasses(graph: FrameGraph, color: string, depth: string, scene: SceneRenderer): void;
|
|
129
|
+
/**
|
|
130
|
+
* Its agent tools (ADR-0204): `water.state` reads the sea, `water.set` turns its knobs, `water.sea` makes a new sea.
|
|
131
|
+
* Errors carry the argument's `path`.
|
|
132
|
+
*/
|
|
133
|
+
tools(): ReturnType<NonNullable<RenderFeature['tools']>>;
|
|
68
134
|
/** What the sea holds on the GPU (its waves; the surface has no buffers). */
|
|
69
135
|
get gpuBytes(): number;
|
|
70
136
|
destroy(): void;
|