@yali-engine/water 0.0.0-stage → 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 +4 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/shore.d.ts +35 -0
- package/dist/shore.js +189 -0
- package/dist/shore.js.map +1 -0
- package/dist/spectrum.d.ts +35 -0
- package/dist/spectrum.js +112 -0
- package/dist/spectrum.js.map +1 -0
- package/dist/water-shaders.gen.d.ts +10 -0
- package/dist/water-shaders.gen.js +13 -0
- package/dist/water-shaders.gen.js.map +1 -0
- package/dist/water.d.ts +137 -0
- package/dist/water.js +597 -0
- package/dist/water.js.map +1 -0
- package/dist/waves.d.ts +50 -0
- package/dist/waves.js +173 -0
- package/dist/waves.js.map +1 -0
- package/package.json +24 -3
- package/README.md +0 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { cascadeBands, donelanBanner, h0Table, jonswap, jonswapPeak, type SeaState, significantHeight } from './spectrum.ts';
|
|
2
|
+
export { bakeShore, type Seabed, type ShoreField } from './shore.ts';
|
|
3
|
+
export { water, Water, type WaterOptions, type WaterQuality } from './water.ts';
|
|
4
|
+
export { WaterWaves, type WaterWavesOptions } from './waves.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// `@yali-engine/water` (ADR-0198, docs/research/water.md): an ocean's FFT cascades and its surface, as a render feature.
|
|
2
|
+
export { cascadeBands, donelanBanner, h0Table, jonswap, jonswapPeak, significantHeight } from "./spectrum.js";
|
|
3
|
+
export { bakeShore } from "./shore.js";
|
|
4
|
+
export { water, Water } from "./water.js";
|
|
5
|
+
export { WaterWaves } from "./waves.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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,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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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** The sea a spectrum describes. */
|
|
2
|
+
export interface SeaState {
|
|
3
|
+
/** Wind speed 10 m above the water, m/s (default 10). */
|
|
4
|
+
readonly wind?: number;
|
|
5
|
+
/** The direction the wind blows towards, radians from +x towards +z (default 0). */
|
|
6
|
+
readonly direction?: number;
|
|
7
|
+
/** Fetch: how far the wind has blown over open water, metres (default 100 km). */
|
|
8
|
+
readonly fetch?: number;
|
|
9
|
+
/** JONSWAP's peak enhancement γ (default 3.3). */
|
|
10
|
+
readonly gamma?: number;
|
|
11
|
+
/** The generator's seed (default 1). */
|
|
12
|
+
readonly seed?: number;
|
|
13
|
+
}
|
|
14
|
+
/** JONSWAP's peak angular frequency (rad/s) and Phillips constant for a wind and fetch. */
|
|
15
|
+
export declare function jonswapPeak(wind: number, fetch: number): {
|
|
16
|
+
omega: number;
|
|
17
|
+
alpha: number;
|
|
18
|
+
};
|
|
19
|
+
/** JONSWAP's spectral density at angular frequency ω, m²·s. */
|
|
20
|
+
export declare function jonswap(omega: number, peak: {
|
|
21
|
+
omega: number;
|
|
22
|
+
alpha: number;
|
|
23
|
+
}, gamma?: number): number;
|
|
24
|
+
/** Donelan–Banner spreading at angle θ from the wind for ω / ωp; integrates to 1 over θ ∈ (−π, π]. */
|
|
25
|
+
export declare function donelanBanner(theta: number, ratio: number): number;
|
|
26
|
+
/** A cascade's band of wavenumber magnitudes, rad/m: [lo, hi). */
|
|
27
|
+
export declare function cascadeBands(sizes: readonly number[], n: number): [number, number][];
|
|
28
|
+
/**
|
|
29
|
+
* One cascade's h0 table, `n × n` texels of four floats: h0(k) and conj(h0(−k)) (what `evolve` reads), k on the grid
|
|
30
|
+
* centred on n / 2, zero outside the band. E|h0(k)|² is half the directional spectrum times the grid cell's area in
|
|
31
|
+
* wavenumber, so the heights' variance (h(k) holds h0(k) and h0(−k)) is the band's share of the sea's m0.
|
|
32
|
+
*/
|
|
33
|
+
export declare function h0Table(n: number, size: number, band: readonly [number, number], sea?: SeaState, cascade?: number): Float32Array<ArrayBuffer>;
|
|
34
|
+
/** The sea's significant wave height (4 √m0, m) from JONSWAP integrated over frequency: what the cascades together carry. */
|
|
35
|
+
export declare function significantHeight(sea?: SeaState): number;
|
package/dist/spectrum.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// The ocean's spectrum on the CPU (ADR-0198, docs/research/water.md §4): JONSWAP for a fetch-limited sea (Hasselmann et al.
|
|
2
|
+
// 1973) with Donelan–Banner directional spreading (Donelan, Hamilton and Hui 1985), in deep water (ω² = g k), sampled on
|
|
3
|
+
// each cascade's grid of wavenumbers, its band only (the cascades' bands are disjoint, so no wave is counted twice), and
|
|
4
|
+
// drawn as Tessendorf's h0: a complex Gaussian with the spectrum's variance, from a seeded generator (the same seed, the same
|
|
5
|
+
// sea on every machine).
|
|
6
|
+
const G = 9.81;
|
|
7
|
+
/** JONSWAP's peak angular frequency (rad/s) and Phillips constant for a wind and fetch. */
|
|
8
|
+
export function jonswapPeak(wind, fetch) {
|
|
9
|
+
const x = (G * fetch) / (wind * wind);
|
|
10
|
+
return { omega: 22 * (G / wind) * Math.pow(x, -1 / 3), alpha: 0.076 * Math.pow(x, -0.22) };
|
|
11
|
+
}
|
|
12
|
+
/** JONSWAP's spectral density at angular frequency ω, m²·s. */
|
|
13
|
+
export function jonswap(omega, peak, gamma = 3.3) {
|
|
14
|
+
if (omega <= 0)
|
|
15
|
+
return 0;
|
|
16
|
+
const wp = peak.omega;
|
|
17
|
+
const sigma = omega <= wp ? 0.07 : 0.09;
|
|
18
|
+
const r = Math.exp(-((omega - wp) ** 2) / (2 * sigma * sigma * wp * wp));
|
|
19
|
+
return ((peak.alpha * G * G) / omega ** 5) * Math.exp(-1.25 * (wp / omega) ** 4) * Math.pow(gamma, r);
|
|
20
|
+
}
|
|
21
|
+
/** Donelan–Banner spreading at angle θ from the wind for ω / ωp; integrates to 1 over θ ∈ (−π, π]. */
|
|
22
|
+
export function donelanBanner(theta, ratio) {
|
|
23
|
+
const beta = ratio < 0.95 ? 2.61 * ratio ** 1.3 : ratio < 1.6 ? 2.28 * ratio ** -1.3 : 10 ** (-0.4 + 0.8393 * Math.exp(-0.567 * Math.log(ratio * ratio)));
|
|
24
|
+
const c = Math.cosh(beta * theta);
|
|
25
|
+
return beta / (2 * Math.tanh(beta * Math.PI)) / (c * c);
|
|
26
|
+
}
|
|
27
|
+
/** A cascade's band of wavenumber magnitudes, rad/m: [lo, hi). */
|
|
28
|
+
export function cascadeBands(sizes, n) {
|
|
29
|
+
// Each cascade starts at 4 of its tile's fundamentals (its longest waves would tile visibly); the one before ends there.
|
|
30
|
+
const lo = sizes.map((l, i) => (i === 0 ? 0 : (4 * 2 * Math.PI) / l));
|
|
31
|
+
return sizes.map((l, i) => [lo[i], i + 1 < sizes.length ? lo[i + 1] : (Math.PI * n) / l * Math.SQRT2]);
|
|
32
|
+
}
|
|
33
|
+
/** A seeded uniform generator (mulberry32). */
|
|
34
|
+
function uniform(seed) {
|
|
35
|
+
let s = seed >>> 0;
|
|
36
|
+
return () => {
|
|
37
|
+
s = (s + 0x6d2b79f5) >>> 0;
|
|
38
|
+
let t = s;
|
|
39
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
40
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
41
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* One cascade's h0 table, `n × n` texels of four floats: h0(k) and conj(h0(−k)) (what `evolve` reads), k on the grid
|
|
46
|
+
* centred on n / 2, zero outside the band. E|h0(k)|² is half the directional spectrum times the grid cell's area in
|
|
47
|
+
* wavenumber, so the heights' variance (h(k) holds h0(k) and h0(−k)) is the band's share of the sea's m0.
|
|
48
|
+
*/
|
|
49
|
+
export function h0Table(n, size, band, sea = {}, cascade = 0) {
|
|
50
|
+
const wind = sea.wind ?? 10;
|
|
51
|
+
const dir = sea.direction ?? 0;
|
|
52
|
+
const peak = jonswapPeak(wind, sea.fetch ?? 100_000);
|
|
53
|
+
const gamma = sea.gamma ?? 3.3;
|
|
54
|
+
const dk = (2 * Math.PI) / size;
|
|
55
|
+
const rand = uniform(((sea.seed ?? 1) * 0x9e3779b1 + cascade * 0x85ebca6b) >>> 0);
|
|
56
|
+
// Two Gaussians per texel (Box–Muller), drawn for every texel in order: the table is the same for the same seed.
|
|
57
|
+
const xi = new Float32Array(n * n * 2);
|
|
58
|
+
for (let i = 0; i < n * n; i++) {
|
|
59
|
+
const u1 = Math.max(rand(), 1e-12);
|
|
60
|
+
const u2 = rand();
|
|
61
|
+
const r = Math.sqrt(-2 * Math.log(u1));
|
|
62
|
+
xi[i * 2] = r * Math.cos(2 * Math.PI * u2);
|
|
63
|
+
xi[i * 2 + 1] = r * Math.sin(2 * Math.PI * u2);
|
|
64
|
+
}
|
|
65
|
+
const amp = new Float32Array(n * n);
|
|
66
|
+
for (let z = 0; z < n; z++) {
|
|
67
|
+
for (let x = 0; x < n; x++) {
|
|
68
|
+
const kx = (x - n / 2) * dk;
|
|
69
|
+
const kz = (z - n / 2) * dk;
|
|
70
|
+
const k = Math.hypot(kx, kz);
|
|
71
|
+
// The Nyquist row and column (index 0: −n/2) have no mirror on the grid: their h(k) is not Hermitian, and two real
|
|
72
|
+
// fields packed in one complex FFT would leak into each other there. Left empty.
|
|
73
|
+
if (x === 0 || z === 0 || k < band[0] || k >= band[1] || k === 0)
|
|
74
|
+
continue;
|
|
75
|
+
const omega = Math.sqrt(G * k);
|
|
76
|
+
// S(k) = S(ω) D(θ) dω/dk / k (polar to Cartesian), dω/dk = g / (2ω) in deep water.
|
|
77
|
+
let theta = Math.atan2(kz, kx) - dir;
|
|
78
|
+
theta = Math.atan2(Math.sin(theta), Math.cos(theta));
|
|
79
|
+
const s = (jonswap(omega, peak, gamma) * donelanBanner(theta, omega / peak.omega) * (G / (2 * omega))) / k;
|
|
80
|
+
// h(k) = h0(k) + conj(h0(−k)) carries the variance of both: each half of S Δk².
|
|
81
|
+
amp[z * n + x] = Math.sqrt((s * dk * dk) / 2);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const out = new Float32Array(n * n * 4);
|
|
85
|
+
for (let z = 0; z < n; z++) {
|
|
86
|
+
for (let x = 0; x < n; x++) {
|
|
87
|
+
const i = z * n + x;
|
|
88
|
+
// −k on the grid: (n − x, n − z) mod n about the centre.
|
|
89
|
+
const j = ((n - z) % n) * n + ((n - x) % n);
|
|
90
|
+
const a = amp[i] / Math.SQRT2;
|
|
91
|
+
const b = amp[j] / Math.SQRT2;
|
|
92
|
+
out[i * 4] = a * xi[i * 2];
|
|
93
|
+
out[i * 4 + 1] = a * xi[i * 2 + 1];
|
|
94
|
+
out[i * 4 + 2] = b * xi[j * 2];
|
|
95
|
+
out[i * 4 + 3] = -b * xi[j * 2 + 1];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
/** The sea's significant wave height (4 √m0, m) from JONSWAP integrated over frequency: what the cascades together carry. */
|
|
101
|
+
export function significantHeight(sea = {}) {
|
|
102
|
+
const peak = jonswapPeak(sea.wind ?? 10, sea.fetch ?? 100_000);
|
|
103
|
+
let m0 = 0;
|
|
104
|
+
const steps = 20000;
|
|
105
|
+
const top = peak.omega * 20;
|
|
106
|
+
for (let i = 1; i <= steps; i++) {
|
|
107
|
+
const w = (i - 0.5) * (top / steps);
|
|
108
|
+
m0 += jonswap(w, peak, sea.gamma ?? 3.3) * (top / steps);
|
|
109
|
+
}
|
|
110
|
+
return 4 * Math.sqrt(m0);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=spectrum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spectrum.js","sourceRoot":"","sources":["../src/spectrum.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Linked from shaders/water/waves.wesl. */
|
|
2
|
+
export declare const waterWaves: {
|
|
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
|
+
};
|
|
6
|
+
/** Linked from shaders/water/surface.wesl. */
|
|
7
|
+
export declare const waterSurface: {
|
|
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
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// GENERATED by tools/shaders-gen.mjs from shaders/**/*.wesl. Do not edit.
|
|
2
|
+
// Regenerate with `node tools/shaders-gen.mjs`; CI runs it with --check.
|
|
3
|
+
/** Linked from shaders/water/waves.wesl. */
|
|
4
|
+
export const waterWaves = {
|
|
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
|
+
};
|
|
8
|
+
/** Linked from shaders/water/surface.wesl. */
|
|
9
|
+
export const waterSurface = {
|
|
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
|
+
};
|
|
13
|
+
//# sourceMappingURL=water-shaders.gen.js.map
|
|
@@ -0,0 +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,8+GAA8+G;CAC5+G,CAAC;AAEX,8CAA8C;AAC9C,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,youBAAyouB;CACvouB,CAAC"}
|