@driftengine/core 4.13.4 → 4.13.5
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/CHANGELOG.json +36 -0
- package/README.md +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/render/anisotropicLobe.d.ts +5 -0
- package/dist/render/anisotropicLobe.js +13 -0
- package/dist/render/atmosphere.d.ts +79 -1
- package/dist/render/atmosphere.js +66 -19
- package/dist/render/backend/webgl2/renderer.d.ts +34 -2
- package/dist/render/backend/webgl2/renderer.js +44 -12
- package/dist/render/backend/webgpu/boltPass.d.ts +1 -1
- package/dist/render/backend/webgpu/causticsPass.d.ts +1 -1
- package/dist/render/backend/webgpu/filmPass.d.ts +1 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +20 -3
- package/dist/render/backend/webgpu/linePass.d.ts +1 -1
- package/dist/render/backend/webgpu/mediumFields.d.ts +17 -0
- package/dist/render/backend/webgpu/mediumFields.js +74 -0
- package/dist/render/backend/webgpu/plumePass.d.ts +3 -3
- package/dist/render/backend/webgpu/postPass.d.ts +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +13 -3
- package/dist/render/backend/webgpu/renderer.js +86 -150
- package/dist/render/backend/webgpu/scatterPass.d.ts +1 -1
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/environmentMixing.d.ts +18 -0
- package/dist/render/environmentMixing.js +40 -0
- package/dist/render/fog.d.ts +43 -1
- package/dist/render/fog.js +87 -22
- package/dist/render/globalMedium.d.ts +112 -10
- package/dist/render/globalMedium.js +96 -1
- package/dist/render/globalMediumPass.d.ts +7 -3
- package/dist/render/globalMediumPass.js +22 -1
- package/dist/render/hairLobes.d.ts +5 -3
- package/dist/render/hairLobes.js +11 -7
- package/dist/render/lightBudget.d.ts +12 -0
- package/dist/render/lightBudget.js +24 -0
- package/dist/render/sceneTarget.d.ts +5 -3
- package/dist/render/sceneTarget.js +14 -8
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/film.js +4 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.d.ts +8 -1
- package/dist/render/shaders/flat/anisotropic.js +25 -3
- package/dist/render/shaders/flat/hair.js +31 -7
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +20 -1
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/preamble.js +7 -0
- package/dist/render/shaders/fog.d.ts +4 -4
- package/dist/render/shaders/fog.js +81 -32
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +29 -39
- package/dist/render/shaders/generated/arcane.wgsl.js +29 -39
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +26 -36
- package/dist/render/shaders/generated/bolt.wgsl.js +26 -36
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +25 -35
- package/dist/render/shaders/generated/caustics.wgsl.js +25 -35
- package/dist/render/shaders/generated/film.wgsl.d.ts +28 -38
- package/dist/render/shaders/generated/film.wgsl.js +28 -38
- package/dist/render/shaders/generated/fire.wgsl.d.ts +28 -38
- package/dist/render/shaders/generated/fire.wgsl.js +28 -38
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1466 -1546
- package/dist/render/shaders/generated/flat.wgsl.js +1489 -1569
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +98 -12
- package/dist/render/shaders/generated/globalMedium.wgsl.js +98 -12
- package/dist/render/shaders/generated/line.wgsl.d.ts +25 -35
- package/dist/render/shaders/generated/line.wgsl.js +25 -35
- package/dist/render/shaders/generated/particle.wgsl.d.ts +100 -140
- package/dist/render/shaders/generated/particle.wgsl.js +100 -140
- package/dist/render/shaders/generated/rush.wgsl.d.ts +20 -10
- package/dist/render/shaders/generated/rush.wgsl.js +20 -10
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +25 -35
- package/dist/render/shaders/generated/scatter.wgsl.js +25 -35
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +28 -38
- package/dist/render/shaders/generated/smoke.wgsl.js +28 -38
- package/dist/render/shaders/generated/water.wgsl.d.ts +29 -39
- package/dist/render/shaders/generated/water.wgsl.js +29 -39
- package/dist/render/shaders/globalMedium.js +114 -10
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +2 -1
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +10 -4
- package/dist/render/shaders/gpudriven/fog.wgsl.js +77 -19
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +12 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +3 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/line.js +4 -2
- package/dist/render/shaders/localExposure.d.ts +42 -1
- package/dist/render/shaders/localExposure.js +73 -6
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/particle.js +10 -6
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/scatter.js +4 -2
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +2 -1
- package/dist/render/surfaceModel.d.ts +23 -4
- package/dist/render/surfaceModel.js +12 -2
- package/package.json +6 -6
- package/src/index.ts +12 -2
- package/src/render/anisotropicLobe.ts +20 -0
- package/src/render/atmosphere.ts +153 -20
- package/src/render/backend/webgl2/renderer.ts +63 -13
- package/src/render/backend/webgpu/gpuDrivenPass.ts +20 -3
- package/src/render/backend/webgpu/mediumFields.ts +87 -0
- package/src/render/backend/webgpu/renderer.ts +109 -157
- package/src/render/environmentMixing.ts +48 -0
- package/src/render/fog.ts +125 -25
- package/src/render/globalMedium.ts +214 -11
- package/src/render/globalMediumPass.ts +29 -4
- package/src/render/hairLobes.ts +35 -4
- package/src/render/lightBudget.ts +32 -0
- package/src/render/sceneTarget.ts +15 -11
- package/src/render/shaders/film.ts +4 -1
- package/src/render/shaders/flat/anisotropic.ts +25 -3
- package/src/render/shaders/flat/hair.ts +31 -7
- package/src/render/shaders/flat/main.ts +20 -1
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/preamble.ts +7 -0
- package/src/render/shaders/fog.ts +81 -32
- package/src/render/shaders/generated/arcane.wgsl.ts +29 -39
- package/src/render/shaders/generated/bolt.wgsl.ts +26 -36
- package/src/render/shaders/generated/caustics.wgsl.ts +25 -35
- package/src/render/shaders/generated/film.wgsl.ts +28 -38
- package/src/render/shaders/generated/fire.wgsl.ts +28 -38
- package/src/render/shaders/generated/flat.wgsl.ts +1489 -1569
- package/src/render/shaders/generated/globalMedium.wgsl.ts +98 -12
- package/src/render/shaders/generated/line.wgsl.ts +25 -35
- package/src/render/shaders/generated/particle.wgsl.ts +100 -140
- package/src/render/shaders/generated/rush.wgsl.ts +20 -10
- package/src/render/shaders/generated/scatter.wgsl.ts +25 -35
- package/src/render/shaders/generated/smoke.wgsl.ts +28 -38
- package/src/render/shaders/generated/water.wgsl.ts +29 -39
- package/src/render/shaders/globalMedium.ts +114 -10
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +2 -1
- package/src/render/shaders/gpudriven/fog.wgsl.ts +78 -19
- package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +12 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +3 -1
- package/src/render/shaders/line.ts +4 -2
- package/src/render/shaders/localExposure.ts +115 -6
- package/src/render/shaders/particle.ts +10 -6
- package/src/render/shaders/scatter.ts +4 -2
- package/src/render/shaders/water.ts +2 -1
- package/src/render/surfaceModel.ts +41 -6
package/src/render/atmosphere.ts
CHANGED
|
@@ -12,6 +12,44 @@ export interface UnderwaterAtmosphere {
|
|
|
12
12
|
transitionDepth: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* A layer of haze: how thick it is where it is thickest, and how fast it thins above that.
|
|
17
|
+
*
|
|
18
|
+
* In natural units, as `fogDensity` is: a haze authored in base two, its surviving light
|
|
19
|
+
* `2^(−∫ρ)` and its density falling as `2^(−f·Δy)`, is the layer `density: ρ · (ln 2)²`,
|
|
20
|
+
* `heightFalloff: f · ln 2` here — the integral of base two's exponential carries one `ln 2` of its
|
|
21
|
+
* own, and the transmittance's base the other.
|
|
22
|
+
*/
|
|
23
|
+
export interface FogLayer {
|
|
24
|
+
/** Extinction per metre at `baseY`. */
|
|
25
|
+
density: number;
|
|
26
|
+
/** Reciprocal scale height, per metre: 0 is a layer of even density. */
|
|
27
|
+
heightFalloff: number;
|
|
28
|
+
/** The height it is thickest at. */
|
|
29
|
+
baseY: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The sun's light scattered toward the eye by the haze: a glow in the sun's direction, as dust and
|
|
34
|
+
* droplets throw forward what they catch.
|
|
35
|
+
*
|
|
36
|
+
* Added to what the haze returns rather than mixed with it, by as much of the ray past `start` as
|
|
37
|
+
* the haze hides: a mountain range seen into a low sun burns gold at its distance, and the
|
|
38
|
+
* figures a few metres off do not. **What it gives up** is the light's own shape: the glow is a
|
|
39
|
+
* lobe about one direction, `cos^exponent` of the angle from it, not light scattered along the
|
|
40
|
+
* ray through the shadows the sun's map knows of — the global medium is that one.
|
|
41
|
+
*/
|
|
42
|
+
export interface FogSunGlow {
|
|
43
|
+
/** The glow looking straight along `direction` through haze that hides everything. */
|
|
44
|
+
color: Vec3;
|
|
45
|
+
/** How tight the glow is about the sun: the cosine of the angle from it, to this power. */
|
|
46
|
+
exponent: number;
|
|
47
|
+
/** Metres past the haze's own start before the glow begins. Default 0. */
|
|
48
|
+
start?: number;
|
|
49
|
+
/** Surface → the light the glow surrounds. Absent, the environment's `directionalDir`. */
|
|
50
|
+
direction?: Vec3;
|
|
51
|
+
}
|
|
52
|
+
|
|
15
53
|
/** Atmosphere fields shared by every visible render pass. */
|
|
16
54
|
export interface Atmosphere {
|
|
17
55
|
fogColor: Vec3;
|
|
@@ -60,6 +98,31 @@ export interface Atmosphere {
|
|
|
60
98
|
* between the camera and the start, which a real medium has; that is the point of it.
|
|
61
99
|
*/
|
|
62
100
|
fogStart?: number;
|
|
101
|
+
/**
|
|
102
|
+
* The medium only: a second layer of haze, summed with the first along every ray, each its own
|
|
103
|
+
* density, height and falloff. Absent, one layer.
|
|
104
|
+
*
|
|
105
|
+
* Two layers are what a valley with a ground mist under a high summer haze is: one thin and
|
|
106
|
+
* tall, one dense and hugging the floor. One layer must pick one of the two scale heights, and
|
|
107
|
+
* either gets the other's half of the picture wrong.
|
|
108
|
+
*/
|
|
109
|
+
fogLayer2?: FogLayer | null;
|
|
110
|
+
/**
|
|
111
|
+
* The most of a surface the haze may hide, 0 to 1. Absent or 1, all of it.
|
|
112
|
+
*
|
|
113
|
+
* Below 1 a far mountain keeps that much of itself however much air lies between, which is how a
|
|
114
|
+
* scene keeps a skyline readable behind a haze that should hide everything nearer the ground.
|
|
115
|
+
* **What it gives up** is physics: no medium has a floor on what survives it.
|
|
116
|
+
*/
|
|
117
|
+
fogMaxOpacity?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Metres from the camera past which the haze hides nothing: a surface further away, the sky
|
|
120
|
+
* included, is drawn clear. Absent or 0, none. For a sky or a backdrop that brings its own
|
|
121
|
+
* haze painted in, which the medium would otherwise lay a second time.
|
|
122
|
+
*/
|
|
123
|
+
fogCutoff?: number;
|
|
124
|
+
/** The medium only: the sun's light scattered toward the eye by the haze. Absent, none. */
|
|
125
|
+
fogSunGlow?: FogSunGlow | null;
|
|
63
126
|
underwater: UnderwaterAtmosphere | null;
|
|
64
127
|
}
|
|
65
128
|
|
|
@@ -89,6 +152,16 @@ export function fogDensityAtEye(atmosphere: Atmosphere, cameraY: number): number
|
|
|
89
152
|
*/
|
|
90
153
|
export interface ResolvedAtmosphere {
|
|
91
154
|
readonly fogColor: Float32Array;
|
|
155
|
+
/**
|
|
156
|
+
* Four floats: the second layer's extinction per metre at the camera's height and its falloff,
|
|
157
|
+
* then the least of a surface the haze must leave, `fogMaxOpacity`'s complement, and the cutoff
|
|
158
|
+
* distance. **Every lane is none at zero**, so a pass that never wrote them draws as before.
|
|
159
|
+
*/
|
|
160
|
+
readonly fogLayer2: Float32Array;
|
|
161
|
+
/** Four floats: the sun glow's colour, black for none, and its exponent. */
|
|
162
|
+
readonly fogGlow: Float32Array;
|
|
163
|
+
/** Four floats: surface → the glow's light, and how far past the haze's start the glow begins. */
|
|
164
|
+
readonly fogGlowDir: Float32Array;
|
|
92
165
|
fogDensity: number;
|
|
93
166
|
fogHeightFalloff: number;
|
|
94
167
|
fogEyeY: number;
|
|
@@ -109,7 +182,7 @@ export interface ResolvedAtmosphere {
|
|
|
109
182
|
* it; nothing here allocates and nothing is shared between them.
|
|
110
183
|
*/
|
|
111
184
|
export function resolveAtmosphere(
|
|
112
|
-
atmosphere: Atmosphere,
|
|
185
|
+
atmosphere: Atmosphere & { readonly directionalDir?: ArrayLike<number> },
|
|
113
186
|
cameraY: number,
|
|
114
187
|
underwaterEnabled: boolean,
|
|
115
188
|
out: ResolvedAtmosphere,
|
|
@@ -163,22 +236,77 @@ export function resolveAtmosphere(
|
|
|
163
236
|
out.underwaterColor.set(underwater === null ? atmosphere.fogColor : underwater.color);
|
|
164
237
|
out.underwaterFogDensity = underwater === null ? atmosphere.fogDensity : underwater.fogDensity;
|
|
165
238
|
out.underwaterFactor = factor;
|
|
239
|
+
resolveHazeExtras(atmosphere, cameraY, out);
|
|
166
240
|
return out;
|
|
167
241
|
}
|
|
168
242
|
|
|
243
|
+
/**
|
|
244
|
+
* The second layer, the haze's limits and the sun's glow, into `out`: the layer and the glow only
|
|
245
|
+
* in the medium, since a linear ramp has neither a density nor a ray to integrate, and every slot
|
|
246
|
+
* written whatever is asked, so nothing a shader reads keeps what an earlier frame left in it.
|
|
247
|
+
*/
|
|
248
|
+
function resolveHazeExtras(
|
|
249
|
+
atmosphere: Atmosphere & { readonly directionalDir?: ArrayLike<number> },
|
|
250
|
+
cameraY: number,
|
|
251
|
+
out: ResolvedAtmosphere,
|
|
252
|
+
): void {
|
|
253
|
+
const medium = atmosphere.fogMode !== 'linear';
|
|
254
|
+
const layer = medium ? (atmosphere.fogLayer2 ?? null) : null;
|
|
255
|
+
const density = layer === null ? 0 : Math.max(0, layer.density);
|
|
256
|
+
/* The second layer at the camera's height, as `fogDensityAtEye` puts the first. */
|
|
257
|
+
out.fogLayer2[0] =
|
|
258
|
+
density > 0 && layer !== null
|
|
259
|
+
? density * Math.exp(-(cameraY - layer.baseY) * layer.heightFalloff)
|
|
260
|
+
: 0;
|
|
261
|
+
out.fogLayer2[1] = density > 0 && layer !== null ? layer.heightFalloff : 0;
|
|
262
|
+
const most = atmosphere.fogMaxOpacity;
|
|
263
|
+
out.fogLayer2[2] =
|
|
264
|
+
most !== undefined && Number.isFinite(most) ? 1 - Math.min(1, Math.max(0, most)) : 0;
|
|
265
|
+
const cutoff = atmosphere.fogCutoff;
|
|
266
|
+
out.fogLayer2[3] = cutoff !== undefined && Number.isFinite(cutoff) && cutoff > 0 ? cutoff : 0;
|
|
267
|
+
|
|
268
|
+
const glow = medium ? (atmosphere.fogSunGlow ?? null) : null;
|
|
269
|
+
const toward = glow?.direction ?? atmosphere.directionalDir;
|
|
270
|
+
const x = (toward?.[0] as number | undefined) ?? 0;
|
|
271
|
+
const y = (toward?.[1] as number | undefined) ?? 1;
|
|
272
|
+
const z = (toward?.[2] as number | undefined) ?? 0;
|
|
273
|
+
const length = Math.hypot(x, y, z);
|
|
274
|
+
const lit = glow !== null && length > 1e-6;
|
|
275
|
+
for (let c = 0; c < 3; c++) {
|
|
276
|
+
out.fogGlow[c] = lit && glow !== null ? Math.max(0, glow.color[c] as number) : 0;
|
|
277
|
+
}
|
|
278
|
+
/* Above zero, so the shader's pow never meets 0 to the 0: a lobe that flat is the whole sky. */
|
|
279
|
+
out.fogGlow[3] = lit && glow !== null ? Math.max(1e-3, glow.exponent) : 1;
|
|
280
|
+
out.fogGlowDir[0] = lit ? x / length : 0;
|
|
281
|
+
out.fogGlowDir[1] = lit ? y / length : 1;
|
|
282
|
+
out.fogGlowDir[2] = lit ? z / length : 0;
|
|
283
|
+
out.fogGlowDir[3] = lit && glow !== null ? Math.max(0, glow.start ?? 0) : 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* A target for `resolveAtmosphere`, made once outside the frame loop: one factory, so a field added
|
|
288
|
+
* to the resolved medium is a field every holder of one gets rather than one each must remember.
|
|
289
|
+
*/
|
|
290
|
+
export function createResolvedAtmosphere(): ResolvedAtmosphere {
|
|
291
|
+
return {
|
|
292
|
+
fogColor: new Float32Array(3),
|
|
293
|
+
fogLayer2: new Float32Array(4),
|
|
294
|
+
fogGlow: new Float32Array(4),
|
|
295
|
+
fogGlowDir: new Float32Array(4),
|
|
296
|
+
underwaterColor: new Float32Array(3),
|
|
297
|
+
fogDensity: 0,
|
|
298
|
+
fogHeightFalloff: 0,
|
|
299
|
+
fogEyeY: 0,
|
|
300
|
+
fogMode: 0,
|
|
301
|
+
fogNear: 0,
|
|
302
|
+
fogFar: 1,
|
|
303
|
+
underwaterFogDensity: 0,
|
|
304
|
+
underwaterFactor: 0,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
169
308
|
/** One per GL binder, refilled per call. See `resolveAtmosphere` on why it is not allocated. */
|
|
170
|
-
const glResolved: ResolvedAtmosphere =
|
|
171
|
-
fogColor: new Float32Array(3),
|
|
172
|
-
underwaterColor: new Float32Array(3),
|
|
173
|
-
fogDensity: 0,
|
|
174
|
-
fogHeightFalloff: 0,
|
|
175
|
-
fogEyeY: 0,
|
|
176
|
-
fogMode: 0,
|
|
177
|
-
fogNear: 0,
|
|
178
|
-
fogFar: 1,
|
|
179
|
-
underwaterFogDensity: 0,
|
|
180
|
-
underwaterFactor: 0,
|
|
181
|
-
};
|
|
309
|
+
const glResolved: ResolvedAtmosphere = createResolvedAtmosphere();
|
|
182
310
|
|
|
183
311
|
/**
|
|
184
312
|
* Bind the same camera medium to any shader that declares atmosphere uniforms.
|
|
@@ -195,13 +323,18 @@ export function bindAtmosphere(
|
|
|
195
323
|
const m = resolveAtmosphere(atmosphere, cameraY, underwaterEnabled, glResolved);
|
|
196
324
|
|
|
197
325
|
gl.uniform3fv(uniforms['uFogColor'] ?? null, m.fogColor);
|
|
198
|
-
|
|
199
|
-
gl.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
326
|
+
/* Packed as `shaders/fog.ts` declares them, four to a row: see `uFogMedium` there for why. */
|
|
327
|
+
gl.uniform4f(
|
|
328
|
+
uniforms['uFogMedium'] ?? null,
|
|
329
|
+
m.fogDensity,
|
|
330
|
+
m.fogHeightFalloff,
|
|
331
|
+
m.fogEyeY,
|
|
332
|
+
m.underwaterFogDensity,
|
|
333
|
+
);
|
|
334
|
+
gl.uniform4f(uniforms['uFogRange'] ?? null, m.fogNear, m.fogFar, m.fogMode, 0);
|
|
204
335
|
gl.uniform3fv(uniforms['uUnderwaterColor'] ?? null, m.underwaterColor);
|
|
205
|
-
gl.uniform1f(uniforms['uUnderwaterFogDensity'] ?? null, m.underwaterFogDensity);
|
|
206
336
|
gl.uniform1f(uniforms['uUnderwaterFactor'] ?? null, m.underwaterFactor);
|
|
337
|
+
gl.uniform4fv(uniforms['uFogLayer2'] ?? null, m.fogLayer2);
|
|
338
|
+
gl.uniform4fv(uniforms['uFogGlow'] ?? null, m.fogGlow);
|
|
339
|
+
gl.uniform4fv(uniforms['uFogGlowDir'] ?? null, m.fogGlowDir);
|
|
207
340
|
}
|
|
@@ -42,14 +42,18 @@ import {
|
|
|
42
42
|
resolveFrameReflections,
|
|
43
43
|
type ResolvedFrameReflections,
|
|
44
44
|
} from '../../frameReflections.ts';
|
|
45
|
+
import { noEnvironmentMixing, resolveEnvironmentMixing } from '../../environmentMixing.ts';
|
|
45
46
|
import { GlobalMediumPass } from '../../globalMediumPass.ts';
|
|
46
47
|
import {
|
|
47
48
|
DEFAULT_GLOBAL_MEDIUM,
|
|
48
49
|
WEAK_GPU_MEDIUM_STEPS,
|
|
50
|
+
createResolvedMediumLamps,
|
|
49
51
|
mediumActive,
|
|
50
52
|
resolveGlobalMedium,
|
|
53
|
+
resolveGlobalMediumSettings,
|
|
54
|
+
resolveMediumLamps,
|
|
51
55
|
} from '../../globalMedium.ts';
|
|
52
|
-
import type {
|
|
56
|
+
import type { GlobalMediumSettings, ResolvedGlobalMedium } from '../../globalMedium.ts';
|
|
53
57
|
import { REFLECTION_EDGE_FADE, ReflectionQueue } from '../../screenSpaceReflection.ts';
|
|
54
58
|
import type { ReflectiveSurface } from '../../screenSpaceReflection.ts';
|
|
55
59
|
import { DecalQueue } from '../../decalQueue.ts';
|
|
@@ -86,6 +90,7 @@ import type {
|
|
|
86
90
|
PassRegistry,
|
|
87
91
|
} from '../../pass.ts';
|
|
88
92
|
import type { Vec3 } from '../../../math/color.ts';
|
|
93
|
+
import type { LocalExposureOptions } from '../../shaders/localExposure.ts';
|
|
89
94
|
import { clamp } from '../../../math/scalar.ts';
|
|
90
95
|
import { attachContextLoss } from '../../contextLoss.ts';
|
|
91
96
|
import { isWeakGpuFamily, webgl2RendererName } from '../../gpuCapability.ts';
|
|
@@ -177,6 +182,8 @@ import {
|
|
|
177
182
|
MODEL_MAP_TEXTURE_UNIT,
|
|
178
183
|
MORPH_DELTA_TEXTURE_UNIT,
|
|
179
184
|
SKIN_PALETTE_TEXTURE_UNIT,
|
|
185
|
+
createResolvedPointLights,
|
|
186
|
+
resolvePointLights,
|
|
180
187
|
} from '../../lightBudget.ts';
|
|
181
188
|
import {
|
|
182
189
|
MODEL_PARAM_FLOATS,
|
|
@@ -528,6 +535,12 @@ export interface Environment extends Atmosphere {
|
|
|
528
535
|
* every light. See `PointLightSource.falloffExponent`.
|
|
529
536
|
*/
|
|
530
537
|
lightFalloffExponents?: Float32Array;
|
|
538
|
+
/**
|
|
539
|
+
* One per active light: how strongly it scatters in the global medium, a share of how strongly it
|
|
540
|
+
* lights a surface, beside `setGlobalMedium`'s `lampScatter`. Optional: absent is 1 for every
|
|
541
|
+
* light. See `PointLightSet.lightMediumScatter`.
|
|
542
|
+
*/
|
|
543
|
+
lightMediumScatter?: Float32Array;
|
|
531
544
|
/** One per active light: a row of the photometric atlas, or −1 for none. */
|
|
532
545
|
lightIesProfiles: Float32Array;
|
|
533
546
|
/**
|
|
@@ -1551,6 +1564,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1551
1564
|
* vector on the device, so written whole, and kept here so a setter moving one keeps the others.
|
|
1552
1565
|
*/
|
|
1553
1566
|
private readonly environmentDials = new Float32Array(3);
|
|
1567
|
+
/** `uEnvironmentMixing` as it stands: see `setEnvironmentMixing`. None until a draw asks. */
|
|
1568
|
+
private readonly environmentMixing = noEnvironmentMixing(new Float32Array(4));
|
|
1554
1569
|
/** `uEmission` as it stands: the emissive gain and the night factor. */
|
|
1555
1570
|
private readonly emission = new Float32Array([1, 0]);
|
|
1556
1571
|
/** The ramp and each level's tint, as `resolveBloomResponse` writes them; the subtraction and white. */
|
|
@@ -4880,6 +4895,31 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4880
4895
|
this.writeEnvironmentDials();
|
|
4881
4896
|
}
|
|
4882
4897
|
|
|
4898
|
+
/**
|
|
4899
|
+
* How much of the light where the surfaces drawn next stand their reflection is brought to, for
|
|
4900
|
+
* every surface, metal or not: what each reflects of the environment times
|
|
4901
|
+
* `lerp(1, weight, smoothstep(roughnessLow, roughnessHigh, roughness))`. 1 is the default and
|
|
4902
|
+
* identity; held until changed, and put back at `bindMeshPass`.
|
|
4903
|
+
*
|
|
4904
|
+
* **Why there is a weight at all**: a probe is captured at one place and seen from many, so a
|
|
4905
|
+
* figure standing where the indirect light is twice what the probe saw reflects the probe at the
|
|
4906
|
+
* probe's brightness and reads as dull against everything lit around it. `weight` is that
|
|
4907
|
+
* ratio, the place's indirect light over the probe's own average, and a caller that knows both
|
|
4908
|
+
* passes it; above 1 brightens, below dims. A mirror keeps the capture as it is, because its
|
|
4909
|
+
* image is the point, and a rough surface, whose reflection is an average anyway, takes its
|
|
4910
|
+
* place's light: the roughnesses say where between, 0.1 and 0.3 by default.
|
|
4911
|
+
*
|
|
4912
|
+
* Unlike `setEnvironmentGain`, which reaches metals only, and `setSurfaceReflectivity`, which
|
|
4913
|
+
* stops at 1. **What it gives up** is a reflection that stays the probe's image on a rough
|
|
4914
|
+
* surface: past the upper roughness it is the probe scaled to the light, not the place relit.
|
|
4915
|
+
*/
|
|
4916
|
+
setEnvironmentMixing(weight: number, roughnessLow = 0.1, roughnessHigh = 0.3): void {
|
|
4917
|
+
if (this.contextLost) return;
|
|
4918
|
+
resolveEnvironmentMixing(weight, roughnessLow, roughnessHigh, this.environmentMixing);
|
|
4919
|
+
this.materials.dirty();
|
|
4920
|
+
this.writeFlatVector('uEnvironmentMixing', this.environmentMixing);
|
|
4921
|
+
}
|
|
4922
|
+
|
|
4883
4923
|
/**
|
|
4884
4924
|
* Kept as a no-op that says so once, because it was a stand-in for a term that now exists.
|
|
4885
4925
|
*
|
|
@@ -8098,12 +8138,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8098
8138
|
* two regions of one brightness band differ. Needs `screenEffects` and `hdrScene`, and says so
|
|
8099
8139
|
* once rather than doing nothing. See `shaders/localExposure.ts`.
|
|
8100
8140
|
*/
|
|
8101
|
-
setLocalExposure(
|
|
8141
|
+
setLocalExposure(setting: number | LocalExposureOptions): void {
|
|
8102
8142
|
if (this.sceneTarget === null || !this.quality.hdrScene) {
|
|
8103
|
-
this.warnLookWithoutComposite(
|
|
8143
|
+
this.warnLookWithoutComposite(typeof setting === 'number' ? setting : 1);
|
|
8104
8144
|
return;
|
|
8105
8145
|
}
|
|
8106
|
-
this.sceneTarget.setLocalExposure(
|
|
8146
|
+
this.sceneTarget.setLocalExposure(setting);
|
|
8107
8147
|
}
|
|
8108
8148
|
|
|
8109
8149
|
/**
|
|
@@ -8214,21 +8254,19 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8214
8254
|
* of the two that can put the shape of a doorway on a floor.
|
|
8215
8255
|
*/
|
|
8216
8256
|
setGlobalMedium(
|
|
8217
|
-
density: number,
|
|
8218
|
-
albedo?: number,
|
|
8257
|
+
density: number | GlobalMediumSettings,
|
|
8258
|
+
albedo?: number | Vec3,
|
|
8219
8259
|
anisotropy?: number,
|
|
8220
8260
|
maxDistance?: number,
|
|
8221
8261
|
): void {
|
|
8222
|
-
this.mediumOptions =
|
|
8223
|
-
density: Number.isFinite(density) ? density : 0,
|
|
8224
|
-
albedo,
|
|
8225
|
-
anisotropy,
|
|
8226
|
-
maxDistance,
|
|
8227
|
-
});
|
|
8262
|
+
this.mediumOptions = resolveGlobalMediumSettings(density, albedo, anisotropy, maxDistance);
|
|
8228
8263
|
}
|
|
8229
8264
|
|
|
8230
8265
|
/** This frame's medium. Off until a caller says otherwise. See `setGlobalMedium`. */
|
|
8231
|
-
private mediumOptions:
|
|
8266
|
+
private mediumOptions: ResolvedGlobalMedium = resolveGlobalMedium(DEFAULT_GLOBAL_MEDIUM);
|
|
8267
|
+
/** The lamps the medium is lit by, and the lit stage's lamps they come from, refilled a frame. */
|
|
8268
|
+
private readonly mediumLamps = createResolvedMediumLamps();
|
|
8269
|
+
private readonly mediumLights = createResolvedPointLights();
|
|
8232
8270
|
/** Built on the first frame that asks for a medium, and never on one that does not. */
|
|
8233
8271
|
private medium: GlobalMediumPass | null = null;
|
|
8234
8272
|
|
|
@@ -9081,6 +9119,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9081
9119
|
so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
|
|
9082
9120
|
sunShadows: this.sunShadows?.texture ?? this.emptyTexture2DArray,
|
|
9083
9121
|
peeledEnabled: this.peelFilled,
|
|
9122
|
+
/* The lit stage's lamps and the shadows it resolved for them, this frame's. */
|
|
9123
|
+
lamps: resolveMediumLamps(
|
|
9124
|
+
this.mediumOptions,
|
|
9125
|
+
resolvePointLights(env, this.quality.pointLightFalloff, this.mediumLights),
|
|
9126
|
+
this.pointShadows === null ? null : this.resolvedPointShadows,
|
|
9127
|
+
env?.lightMediumScatter,
|
|
9128
|
+
this.mediumLamps,
|
|
9129
|
+
),
|
|
9130
|
+
pointShadows: this.pointShadowArray?.texture ?? this.emptyTexture2DArray,
|
|
9084
9131
|
},
|
|
9085
9132
|
);
|
|
9086
9133
|
if (marched) {
|
|
@@ -9356,6 +9403,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9356
9403
|
this.materialEnvironmentGain = null;
|
|
9357
9404
|
this.environmentDials[1] = 0;
|
|
9358
9405
|
this.environmentDials[2] = 1;
|
|
9406
|
+
/* And no mixing, for the same reason. See `setEnvironmentMixing`. */
|
|
9407
|
+
noEnvironmentMixing(this.environmentMixing);
|
|
9359
9408
|
this.emission[0] = env.emissiveGain;
|
|
9360
9409
|
this.emission[1] = env.nightFactor;
|
|
9361
9410
|
/* The moving layer's own matrix, the first time a frame draws that layer with one. */
|
|
@@ -9612,6 +9661,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9612
9661
|
* See `setSurfaceReflectivity` and `setEnvironmentGain`.
|
|
9613
9662
|
*/
|
|
9614
9663
|
gl.uniform3fv(u['uEnvironmentDials'] ?? null, this.environmentDials);
|
|
9664
|
+
gl.uniform4fv(u['uEnvironmentMixing'] ?? null, this.environmentMixing);
|
|
9615
9665
|
/* And no relief unless a caller asks, for the same reason. */
|
|
9616
9666
|
gl.uniform2f(u['uRelief'] ?? null, 0, 60);
|
|
9617
9667
|
/* And no relief read off a surface texture either, for the same reason. */
|
|
@@ -182,11 +182,11 @@ export const VERTEX_FLOATS = GPU_DRIVEN_VERTEX_FLOATS;
|
|
|
182
182
|
/**
|
|
183
183
|
* Floats the shading pass's frame block holds. Matches `FRAME_BLOCK_WGSL`'s `Frame`.
|
|
184
184
|
*
|
|
185
|
-
* Two matrices and
|
|
185
|
+
* Two matrices and eighteen vectors: the camera's, the light's, then the eye, the light's direction
|
|
186
186
|
* and colour, the sky, the ground, the target's size, the shadow's settings in two, the probe's in
|
|
187
|
-
* two, the medium's four,
|
|
187
|
+
* two, the medium's four, the visibility buffer's row, and the medium's last three after it.
|
|
188
188
|
*/
|
|
189
|
-
const FRAME_FLOATS =
|
|
189
|
+
const FRAME_FLOATS = 104;
|
|
190
190
|
|
|
191
191
|
/** Where each block starts in it, so the writer and the shader cannot drift apart. */
|
|
192
192
|
const FRAME_LIGHT_VIEW_PROJ = 16;
|
|
@@ -205,6 +205,9 @@ const FRAME_FOG_UNDERWATER = 76;
|
|
|
205
205
|
const FRAME_FOG_HEIGHT = 80;
|
|
206
206
|
const FRAME_FOG_RANGE = 84;
|
|
207
207
|
const FRAME_PITCH = 88;
|
|
208
|
+
const FRAME_FOG_LIMITS = 92;
|
|
209
|
+
const FRAME_FOG_GLOW = 96;
|
|
210
|
+
const FRAME_FOG_GLOW_DIR = 100;
|
|
208
211
|
|
|
209
212
|
/**
|
|
210
213
|
* Pixels the internal width is rounded up to.
|
|
@@ -2693,6 +2696,20 @@ export class GpuDrivenPass implements PassDefinition {
|
|
|
2693
2696
|
params[FRAME_FOG_RANGE + 1] = fog?.far ?? 0;
|
|
2694
2697
|
params[FRAME_FOG_RANGE + 2] = fog?.mode ?? MEDIUM_FOG;
|
|
2695
2698
|
params[FRAME_FOG_RANGE + 3] = fog?.underwaterDensity ?? 0;
|
|
2699
|
+
/* The second layer, the floor, the cutoff and the glow: every one none at zero, as a view
|
|
2700
|
+
written before they existed asks. */
|
|
2701
|
+
params[FRAME_FOG_HEIGHT + 2] = fog?.density2 ?? 0;
|
|
2702
|
+
params[FRAME_FOG_HEIGHT + 3] = fog?.heightFalloff2 ?? 0;
|
|
2703
|
+
params[FRAME_FOG_LIMITS] = fog?.floor ?? 0;
|
|
2704
|
+
params[FRAME_FOG_LIMITS + 1] = fog?.cutoff ?? 0;
|
|
2705
|
+
params[FRAME_FOG_GLOW] = fog?.glowColour?.[0] ?? 0;
|
|
2706
|
+
params[FRAME_FOG_GLOW + 1] = fog?.glowColour?.[1] ?? 0;
|
|
2707
|
+
params[FRAME_FOG_GLOW + 2] = fog?.glowColour?.[2] ?? 0;
|
|
2708
|
+
params[FRAME_FOG_GLOW + 3] = fog?.glowExponent ?? 1;
|
|
2709
|
+
params[FRAME_FOG_GLOW_DIR] = fog?.glowDirection?.[0] ?? 0;
|
|
2710
|
+
params[FRAME_FOG_GLOW_DIR + 1] = fog?.glowDirection?.[1] ?? 1;
|
|
2711
|
+
params[FRAME_FOG_GLOW_DIR + 2] = fog?.glowDirection?.[2] ?? 0;
|
|
2712
|
+
params[FRAME_FOG_GLOW_DIR + 3] = fog?.glowStart ?? 0;
|
|
2696
2713
|
/* The scene's own decision about whether its grid supplies the ambient, as it is on the
|
|
2697
2714
|
forward path: `ProbeBakeOptions.irradiance` reaches here as `PassEnvironment.irradiance`. */
|
|
2698
2715
|
params[FRAME_ENVIRONMENT_MIX] = room !== null && room.irradiance ? 1 : 0;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The medium's fields, every uniform `shaders/fog.ts` declares, written into a pass's block by name
|
|
3
|
+
* from one resolved atmosphere.
|
|
4
|
+
*
|
|
5
|
+
* **One writer for every pass that fogs.** Ten passes wrote the same ten fields by hand, so a field
|
|
6
|
+
* the medium grows was ten places to remember and a pass that missed one read zero: the fourth
|
|
7
|
+
* point of the rule `AGENTS.md` dates 2026-08-13. A field a pass's shader never reads is compiled
|
|
8
|
+
* out of its block, so each is written only where the block holds it. An absent field is not an
|
|
9
|
+
* offset of zero, which is the first field of the block.
|
|
10
|
+
*/
|
|
11
|
+
import type { ResolvedAtmosphere } from '../../atmosphere.ts';
|
|
12
|
+
import type { UniformFields } from './scatterPass.ts';
|
|
13
|
+
import type { UniformRing } from './uniformRing.ts';
|
|
14
|
+
|
|
15
|
+
/** A field's byte offset in the block, or -1 where the shader compiled it out. */
|
|
16
|
+
function placed(fields: UniformFields, name: string): number {
|
|
17
|
+
return fields[name]?.offset ?? -1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Into a block staged as floats, indexed in four-byte words. */
|
|
21
|
+
export function writeMediumFields(
|
|
22
|
+
fields: UniformFields,
|
|
23
|
+
f: Float32Array,
|
|
24
|
+
m: ResolvedAtmosphere,
|
|
25
|
+
): void {
|
|
26
|
+
let at = placed(fields, 'uFogColor');
|
|
27
|
+
if (at >= 0) f.set(m.fogColor, at / 4);
|
|
28
|
+
at = placed(fields, 'uFogMedium');
|
|
29
|
+
if (at >= 0) {
|
|
30
|
+
f[at / 4] = m.fogDensity;
|
|
31
|
+
f[at / 4 + 1] = m.fogHeightFalloff;
|
|
32
|
+
f[at / 4 + 2] = m.fogEyeY;
|
|
33
|
+
f[at / 4 + 3] = m.underwaterFogDensity;
|
|
34
|
+
}
|
|
35
|
+
at = placed(fields, 'uFogRange');
|
|
36
|
+
if (at >= 0) {
|
|
37
|
+
f[at / 4] = m.fogNear;
|
|
38
|
+
f[at / 4 + 1] = m.fogFar;
|
|
39
|
+
f[at / 4 + 2] = m.fogMode;
|
|
40
|
+
f[at / 4 + 3] = 0;
|
|
41
|
+
}
|
|
42
|
+
at = placed(fields, 'uUnderwaterColor');
|
|
43
|
+
if (at >= 0) f.set(m.underwaterColor, at / 4);
|
|
44
|
+
at = placed(fields, 'uUnderwaterFactor');
|
|
45
|
+
if (at >= 0) f[at / 4] = m.underwaterFactor;
|
|
46
|
+
at = placed(fields, 'uFogLayer2');
|
|
47
|
+
if (at >= 0) f.set(m.fogLayer2, at / 4);
|
|
48
|
+
at = placed(fields, 'uFogGlow');
|
|
49
|
+
if (at >= 0) f.set(m.fogGlow, at / 4);
|
|
50
|
+
at = placed(fields, 'uFogGlowDir');
|
|
51
|
+
if (at >= 0) f.set(m.fogGlowDir, at / 4);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Into one slot of a ring, at the fields' byte offsets. */
|
|
55
|
+
export function writeMediumSlot(
|
|
56
|
+
ring: UniformRing,
|
|
57
|
+
slot: number,
|
|
58
|
+
fields: UniformFields,
|
|
59
|
+
m: ResolvedAtmosphere,
|
|
60
|
+
): void {
|
|
61
|
+
let at = placed(fields, 'uFogColor');
|
|
62
|
+
if (at >= 0) ring.writeFloats(slot, at, m.fogColor);
|
|
63
|
+
at = placed(fields, 'uFogMedium');
|
|
64
|
+
if (at >= 0) {
|
|
65
|
+
ring.writeFloat(slot, at, m.fogDensity);
|
|
66
|
+
ring.writeFloat(slot, at + 4, m.fogHeightFalloff);
|
|
67
|
+
ring.writeFloat(slot, at + 8, m.fogEyeY);
|
|
68
|
+
ring.writeFloat(slot, at + 12, m.underwaterFogDensity);
|
|
69
|
+
}
|
|
70
|
+
at = placed(fields, 'uFogRange');
|
|
71
|
+
if (at >= 0) {
|
|
72
|
+
ring.writeFloat(slot, at, m.fogNear);
|
|
73
|
+
ring.writeFloat(slot, at + 4, m.fogFar);
|
|
74
|
+
ring.writeFloat(slot, at + 8, m.fogMode);
|
|
75
|
+
ring.writeFloat(slot, at + 12, 0);
|
|
76
|
+
}
|
|
77
|
+
at = placed(fields, 'uUnderwaterColor');
|
|
78
|
+
if (at >= 0) ring.writeFloats(slot, at, m.underwaterColor);
|
|
79
|
+
at = placed(fields, 'uUnderwaterFactor');
|
|
80
|
+
if (at >= 0) ring.writeFloat(slot, at, m.underwaterFactor);
|
|
81
|
+
at = placed(fields, 'uFogLayer2');
|
|
82
|
+
if (at >= 0) ring.writeFloats(slot, at, m.fogLayer2);
|
|
83
|
+
at = placed(fields, 'uFogGlow');
|
|
84
|
+
if (at >= 0) ring.writeFloats(slot, at, m.fogGlow);
|
|
85
|
+
at = placed(fields, 'uFogGlowDir');
|
|
86
|
+
if (at >= 0) ring.writeFloats(slot, at, m.fogGlowDir);
|
|
87
|
+
}
|