@driftengine/core 4.10.0 → 4.11.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/CHANGELOG.json +71 -0
- package/README.md +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/render/backend/webgl2/renderer.d.ts +99 -2
- package/dist/render/backend/webgl2/renderer.js +361 -42
- package/dist/render/backend/webgpu/buffers.d.ts +5 -0
- package/dist/render/backend/webgpu/buffers.js +98 -48
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
- package/dist/render/backend/webgpu/flatPass.js +2 -1
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
- package/dist/render/backend/webgpu/pipelineCache.js +39 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +48 -0
- package/dist/render/backend/webgpu/renderer.js +207 -27
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- package/dist/render/backend/webgpu/staticBundles.js +2 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- package/dist/render/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/mesh.d.ts +9 -0
- package/dist/render/mesh.js +38 -14
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/flat/directionalShadow.js +84 -10
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +15 -0
- package/dist/render/shaders/flat/index.js +15 -2
- package/dist/render/shaders/flat/layered.d.ts +19 -0
- package/dist/render/shaders/flat/layered.js +115 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +43 -16
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +14 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +41 -19
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
- package/dist/render/shaders/generated/flat.wgsl.js +791 -391
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/surfaceLayers.d.ts +39 -0
- package/dist/render/surfaceLayers.js +26 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +30 -0
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +4 -2
- package/src/render/backend/webgl2/renderer.ts +408 -55
- package/src/render/backend/webgpu/buffers.ts +153 -54
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- package/src/render/backend/webgpu/drawCommand.ts +3 -0
- package/src/render/backend/webgpu/flatPass.ts +26 -28
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +46 -7
- package/src/render/backend/webgpu/renderer.ts +274 -48
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/staticBundles.ts +2 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/mesh.ts +66 -14
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +30 -2
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +43 -16
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +14 -3
- package/src/render/shaders/flat/overlay.ts +41 -19
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/flat.wgsl.ts +791 -391
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/surfaceLayers.ts +57 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +30 -0
- package/src/render/vertexPacking.ts +109 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
import { mat4 } from 'gl-matrix';
|
|
2
2
|
import type { Vec3 } from '../math/color.ts';
|
|
3
3
|
export declare function computeLightMatrix(lightDir: Vec3, focusX: number, focusY: number, focusZ: number, radius: number, shadowMapSize: number, out: mat4): number;
|
|
4
|
+
/**
|
|
5
|
+
* The depth an orthographic light matrix spans, in metres: what `computeLightMatrix` returns, read
|
|
6
|
+
* back off any such matrix. Its depth row is the light's axis scaled by two over the span, so the
|
|
7
|
+
* span is two over that row's length — whatever focus, radius or bearing built it.
|
|
8
|
+
*
|
|
9
|
+
* **For a matrix the renderer is handed rather than one it built**: the sun's moving layer is read
|
|
10
|
+
* through the matrix its shadow pass was drawn with, and its depth tolerance and distance fade are
|
|
11
|
+
* in metres of that matrix. What would make this wrong is a perspective light, whose depth is not
|
|
12
|
+
* linear; the sun's never is.
|
|
13
|
+
*/
|
|
14
|
+
export declare function orthographicDepthSpan(m: ArrayLike<number>): number;
|
|
@@ -43,3 +43,17 @@ export function computeLightMatrix(lightDir, focusX, focusY, focusZ, radius, sha
|
|
|
43
43
|
// to express its residual depth tolerance in stable world-space metres.
|
|
44
44
|
return radius * 6;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* The depth an orthographic light matrix spans, in metres: what `computeLightMatrix` returns, read
|
|
48
|
+
* back off any such matrix. Its depth row is the light's axis scaled by two over the span, so the
|
|
49
|
+
* span is two over that row's length — whatever focus, radius or bearing built it.
|
|
50
|
+
*
|
|
51
|
+
* **For a matrix the renderer is handed rather than one it built**: the sun's moving layer is read
|
|
52
|
+
* through the matrix its shadow pass was drawn with, and its depth tolerance and distance fade are
|
|
53
|
+
* in metres of that matrix. What would make this wrong is a perspective light, whose depth is not
|
|
54
|
+
* linear; the sun's never is.
|
|
55
|
+
*/
|
|
56
|
+
export function orthographicDepthSpan(m) {
|
|
57
|
+
const length = Math.hypot(m[2], m[6], m[10]);
|
|
58
|
+
return length > 0 ? 2 / length : 1;
|
|
59
|
+
}
|
package/dist/render/mesh.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export declare class Mesh {
|
|
|
26
26
|
* same frame.
|
|
27
27
|
*/
|
|
28
28
|
readonly indexCount: number;
|
|
29
|
+
/** `UNSIGNED_SHORT` or `UNSIGNED_INT`, as `compactIndices` stored them: every draw says which. */
|
|
30
|
+
private readonly indexType;
|
|
29
31
|
/**
|
|
30
32
|
* The constant values this mesh's *absent* attributes are shaded with, re-applied on
|
|
31
33
|
* every draw.
|
|
@@ -142,7 +144,14 @@ export declare class Mesh {
|
|
|
142
144
|
* still visible, which is the kind of bug that only shows up at the edge of the frame.
|
|
143
145
|
*/
|
|
144
146
|
update(gl: WebGL2RenderingContext, positions: Float32Array, normals?: Float32Array): void;
|
|
147
|
+
/**
|
|
148
|
+
* One attribute's buffer, as floats or in its sixteen-bit fixed point (`vertexPacking.ts`), which
|
|
149
|
+
* WebGL2 reads back through a normalised `SHORT` or `UNSIGNED_SHORT`: three lanes wide where
|
|
150
|
+
* WebGPU needs four, so a normal costs six bytes here.
|
|
151
|
+
*/
|
|
145
152
|
private attachAttribute;
|
|
153
|
+
/** A buffer, or the one value every vertex shares as a constant, as the packing says. */
|
|
154
|
+
private attachShared;
|
|
146
155
|
/**
|
|
147
156
|
* Give a bound buffer its storage, and its contents now or later.
|
|
148
157
|
*
|
package/dist/render/mesh.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { MorphTexture } from './backend/webgl2/morphTexture.js';
|
|
3
3
|
import { ABSENT_ATTRIBUTE } from './vertexDefaults.js';
|
|
4
4
|
import { UPLOAD_BYTES_PER_STEP } from './uploadStep.js';
|
|
5
|
+
import { compactIndices } from './indexWidth.js';
|
|
6
|
+
import { fixedArray, sharedValue, vertexPackingOf, } from './vertexPacking.js';
|
|
5
7
|
/*
|
|
6
8
|
* `MeshData` and its validator live in `@driftengine/drft`, because the container's whole
|
|
7
9
|
* job is to carry one and the format package must be readable by something that never
|
|
@@ -99,6 +101,8 @@ export class Mesh {
|
|
|
99
101
|
* same frame.
|
|
100
102
|
*/
|
|
101
103
|
indexCount;
|
|
104
|
+
/** `UNSIGNED_SHORT` or `UNSIGNED_INT`, as `compactIndices` stored them: every draw says which. */
|
|
105
|
+
indexType;
|
|
102
106
|
/**
|
|
103
107
|
* The constant values this mesh's *absent* attributes are shaded with, re-applied on
|
|
104
108
|
* every draw.
|
|
@@ -207,12 +211,14 @@ export class Mesh {
|
|
|
207
211
|
* that has been told a lie every frame is the shape of a stall nobody can find.
|
|
208
212
|
*/
|
|
209
213
|
const usage = dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW;
|
|
214
|
+
/* How each attribute travels: sixteen-bit fixed point, or one value as a constant. */
|
|
215
|
+
const packing = vertexPackingOf(data, dynamic);
|
|
210
216
|
const positions = this.attachAttribute(gl, ATTR_POSITION, data.positions, 3, usage);
|
|
211
|
-
const normals = this.attachAttribute(gl, ATTR_NORMAL, data.normals, 3, usage);
|
|
217
|
+
const normals = this.attachAttribute(gl, ATTR_NORMAL, data.normals, 3, usage, packing.normals);
|
|
212
218
|
if (dynamic)
|
|
213
219
|
this.deformable = { positions, normals };
|
|
214
|
-
this.
|
|
215
|
-
this.
|
|
220
|
+
this.attachShared(gl, ATTR_COLOR, data.colors, 3, packing.colors);
|
|
221
|
+
this.attachShared(gl, ATTR_EMISSIVE, data.emissive, 1, packing.emissive);
|
|
216
222
|
/*
|
|
217
223
|
* Absent means "not shiny", supplied as a constant rather than as a buffer of
|
|
218
224
|
* zeroes. `vertexAttrib1f` sets the value a *disabled* attribute reads, so this
|
|
@@ -289,7 +295,7 @@ export class Mesh {
|
|
|
289
295
|
}
|
|
290
296
|
else {
|
|
291
297
|
this.attachAttribute(gl, ATTR_JOINTS, data.joints, 4);
|
|
292
|
-
this.attachAttribute(gl, ATTR_WEIGHTS, data.weights, 4);
|
|
298
|
+
this.attachAttribute(gl, ATTR_WEIGHTS, data.weights, 4, gl.STATIC_DRAW, packing.weights);
|
|
293
299
|
}
|
|
294
300
|
/* The fifth to eighth influences, or zeros, which add nothing. See `isSkinnedEight`. */
|
|
295
301
|
this.isSkinnedEight = this.isSkinned && data.joints2 !== undefined;
|
|
@@ -299,7 +305,7 @@ export class Mesh {
|
|
|
299
305
|
}
|
|
300
306
|
else {
|
|
301
307
|
this.attachAttribute(gl, ATTR_JOINTS2, data.joints2, 4);
|
|
302
|
-
this.attachAttribute(gl, ATTR_WEIGHTS2, data.weights2, 4);
|
|
308
|
+
this.attachAttribute(gl, ATTR_WEIGHTS2, data.weights2, 4, gl.STATIC_DRAW, packing.weights2);
|
|
303
309
|
}
|
|
304
310
|
this.hasTangents = data.tangents !== undefined;
|
|
305
311
|
if (data.tangents === undefined) {
|
|
@@ -307,7 +313,7 @@ export class Mesh {
|
|
|
307
313
|
this.constants.push({ location: ATTR_TANGENT, value: ABSENT_ATTRIBUTE['tangents'] });
|
|
308
314
|
}
|
|
309
315
|
else {
|
|
310
|
-
this.attachAttribute(gl, ATTR_TANGENT, data.tangents, 4);
|
|
316
|
+
this.attachAttribute(gl, ATTR_TANGENT, data.tangents, 4, gl.STATIC_DRAW, packing.tangents);
|
|
311
317
|
}
|
|
312
318
|
this.hasGrain = data.grain !== undefined;
|
|
313
319
|
if (data.grain === undefined) {
|
|
@@ -337,7 +343,9 @@ export class Mesh {
|
|
|
337
343
|
this.buffers.push(indexBuffer);
|
|
338
344
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
|
339
345
|
this.indexBuffer = indexBuffer;
|
|
340
|
-
|
|
346
|
+
const indices = compactIndices(data.indices, this.vertexCount);
|
|
347
|
+
this.indexType = indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT;
|
|
348
|
+
this.allocate(gl, gl.ELEMENT_ARRAY_BUFFER, indexBuffer, indices, gl.STATIC_DRAW);
|
|
341
349
|
gl.bindVertexArray(null);
|
|
342
350
|
}
|
|
343
351
|
/** Caller is responsible for having the flat program + uniforms bound. */
|
|
@@ -354,7 +362,7 @@ export class Mesh {
|
|
|
354
362
|
const value = constant.value;
|
|
355
363
|
applyConstant(gl, constant.location, value);
|
|
356
364
|
}
|
|
357
|
-
gl.drawElements(gl.TRIANGLES, this.indexCount,
|
|
365
|
+
gl.drawElements(gl.TRIANGLES, this.indexCount, this.indexType, 0);
|
|
358
366
|
}
|
|
359
367
|
/**
|
|
360
368
|
* A vertex array for one instanced batch: every attribute this mesh binds from a buffer, its index
|
|
@@ -374,7 +382,7 @@ export class Mesh {
|
|
|
374
382
|
for (const attribute of this.layout) {
|
|
375
383
|
gl.bindBuffer(gl.ARRAY_BUFFER, attribute.buffer);
|
|
376
384
|
gl.enableVertexAttribArray(attribute.location);
|
|
377
|
-
gl.vertexAttribPointer(attribute.location, attribute.size,
|
|
385
|
+
gl.vertexAttribPointer(attribute.location, attribute.size, attribute.type, attribute.normalized, 0, 0);
|
|
378
386
|
}
|
|
379
387
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
|
|
380
388
|
gl.bindBuffer(gl.ARRAY_BUFFER, instances);
|
|
@@ -399,7 +407,7 @@ export class Mesh {
|
|
|
399
407
|
const value = constant.value;
|
|
400
408
|
applyConstant(gl, constant.location, value);
|
|
401
409
|
}
|
|
402
|
-
gl.drawElementsInstanced(gl.TRIANGLES, this.indexCount,
|
|
410
|
+
gl.drawElementsInstanced(gl.TRIANGLES, this.indexCount, this.indexType, 0, count);
|
|
403
411
|
}
|
|
404
412
|
dispose(gl) {
|
|
405
413
|
this.morph?.dispose(gl);
|
|
@@ -437,18 +445,34 @@ export class Mesh {
|
|
|
437
445
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, normals);
|
|
438
446
|
}
|
|
439
447
|
}
|
|
440
|
-
|
|
448
|
+
/**
|
|
449
|
+
* One attribute's buffer, as floats or in its sixteen-bit fixed point (`vertexPacking.ts`), which
|
|
450
|
+
* WebGL2 reads back through a normalised `SHORT` or `UNSIGNED_SHORT`: three lanes wide where
|
|
451
|
+
* WebGPU needs four, so a normal costs six bytes here.
|
|
452
|
+
*/
|
|
453
|
+
attachAttribute(gl, location, data, size, usage = gl.STATIC_DRAW, packing = 'float') {
|
|
441
454
|
const buffer = gl.createBuffer();
|
|
442
455
|
if (buffer === null)
|
|
443
456
|
throw new Error('createBuffer failed');
|
|
444
457
|
this.buffers.push(buffer);
|
|
445
458
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
446
|
-
|
|
459
|
+
const fixed = packing === 'snorm16' || packing === 'unorm16';
|
|
460
|
+
this.allocate(gl, gl.ARRAY_BUFFER, buffer, fixed ? fixedArray(data, packing) : data, usage);
|
|
461
|
+
const type = packing === 'snorm16' ? gl.SHORT : packing === 'unorm16' ? gl.UNSIGNED_SHORT : gl.FLOAT;
|
|
447
462
|
gl.enableVertexAttribArray(location);
|
|
448
|
-
gl.vertexAttribPointer(location, size,
|
|
449
|
-
this.layout.push({ location, buffer, size });
|
|
463
|
+
gl.vertexAttribPointer(location, size, type, fixed, 0, 0);
|
|
464
|
+
this.layout.push({ location, buffer, size, type, normalized: fixed });
|
|
450
465
|
return buffer;
|
|
451
466
|
}
|
|
467
|
+
/** A buffer, or the one value every vertex shares as a constant, as the packing says. */
|
|
468
|
+
attachShared(gl, location, data, size, packing) {
|
|
469
|
+
if (packing !== 'constant') {
|
|
470
|
+
this.attachAttribute(gl, location, data, size, gl.STATIC_DRAW, packing);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
gl.disableVertexAttribArray(location);
|
|
474
|
+
this.constants.push({ location, value: sharedValue(data, size) });
|
|
475
|
+
}
|
|
452
476
|
/**
|
|
453
477
|
* Give a bound buffer its storage, and its contents now or later.
|
|
454
478
|
*
|
|
@@ -42,6 +42,8 @@ export type OutputTransform = 'none' | 'srgb' | 'aces' | 'shoulder' | 'filmic';
|
|
|
42
42
|
export type ShadowFilterTaps = 4 | 8 | 12;
|
|
43
43
|
export type DirectionalShadowDepthLayers = 1 | 2;
|
|
44
44
|
export type WaterReflectionFilterTaps = 1 | 5 | 9;
|
|
45
|
+
/** What a draw does when its pipeline has not compiled yet: see `RenderQuality.pipelineCompile`. */
|
|
46
|
+
export type PipelineCompile = 'wait' | 'skip';
|
|
45
47
|
/** How light through glass is coloured: see `RenderQuality.glassShadows`. */
|
|
46
48
|
export type GlassShadows = 'full' | 'half' | 'off';
|
|
47
49
|
/** How light scattered beneath skin is drawn: see `RenderQuality.skinScattering`. */
|
|
@@ -210,6 +212,34 @@ export interface RenderQuality {
|
|
|
210
212
|
* lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
|
|
211
213
|
*/
|
|
212
214
|
readonly glassShadows: GlassShadows;
|
|
215
|
+
/**
|
|
216
|
+
* What a lit draw does when the pipeline it needs has not compiled: one first needed inside a
|
|
217
|
+
* frame, by a material's own state — two-sided, cut out, a surface model — or a draw state a
|
|
218
|
+
* mesh's creation did not warm.
|
|
219
|
+
*
|
|
220
|
+
* **`'wait'`, the default**: the pipeline is built where the draw asks for it, and on WebGPU the
|
|
221
|
+
* browser compiles it on its GPU process's main thread, holding the frame — 130 to 400 ms a
|
|
222
|
+
* pipeline was reported on a phone, for a material first drawn mid-scene. **`'skip'`** starts the
|
|
223
|
+
* compile off that thread and leaves the draw out until it lands, a few frames to a few dozen,
|
|
224
|
+
* and the draw then appears; nothing waits. Static draws always wait, since a recorded list keeps
|
|
225
|
+
* what it recorded, and a compile that fails is built where it is next asked for, so the device
|
|
226
|
+
* says why rather than the draw vanishing for good.
|
|
227
|
+
*
|
|
228
|
+
* **The way to have neither** is to draw what a scene will use once, behind a loading screen, and
|
|
229
|
+
* await `ready()`, which waits for every compile started — whichever this says. What would make
|
|
230
|
+
* `'skip'` wrong is a frame that must hold everything it was given, such as a capture of the first
|
|
231
|
+
* frame of a scene not awaited.
|
|
232
|
+
*
|
|
233
|
+
* **Measured on a desktop GPU** (`demo/dev/pipelineCompile.html`, eight new variants at once): on
|
|
234
|
+
* WebGPU the frame that met them took 700 to 3,700 ms waiting and 17 ms skipping, the draws
|
|
235
|
+
* landing 21 to 26 frames later. WebGL2 compiles its lit programs when it is made, all but a
|
|
236
|
+
* surface model's, which it compiles at the first draw that wants one: there `'skip'` asks the
|
|
237
|
+
* driver through `KHR_parallel_shader_compile` and waits where a context does not offer it — which
|
|
238
|
+
* ANGLE on Vulkan does not. Where it is offered, ANGLE on OpenGL, the longest frame went from 820 to
|
|
239
|
+
* 1,120 ms to 630 to 700 ms: the driver still finishes its own compile at the program's first
|
|
240
|
+
* draw, which nothing above it can move.
|
|
241
|
+
*/
|
|
242
|
+
readonly pipelineCompile: PipelineCompile;
|
|
213
243
|
/**
|
|
214
244
|
* How light that scatters beneath skin is drawn, for a material whose model is `skinModel`.
|
|
215
245
|
*
|
|
@@ -63,6 +63,7 @@ export const DEFAULT_RENDER_QUALITY = Object.freeze({
|
|
|
63
63
|
lightVolumeSamples: 32,
|
|
64
64
|
directionalShadowDepthLayers: 2,
|
|
65
65
|
glassShadows: 'full',
|
|
66
|
+
pipelineCompile: 'wait',
|
|
66
67
|
skinScattering: 'pre-integrated',
|
|
67
68
|
directionalShadowMaxDistance: 6,
|
|
68
69
|
directionalShadowMaxSlope: 3,
|
|
@@ -218,6 +219,7 @@ export function resolveRenderQuality(options = {}) {
|
|
|
218
219
|
lightVolumeSamples: options.lightVolumeSamples ?? DEFAULT_RENDER_QUALITY.lightVolumeSamples,
|
|
219
220
|
directionalShadowDepthLayers: options.directionalShadowDepthLayers ?? DEFAULT_RENDER_QUALITY.directionalShadowDepthLayers,
|
|
220
221
|
glassShadows: options.glassShadows ?? DEFAULT_RENDER_QUALITY.glassShadows,
|
|
222
|
+
pipelineCompile: options.pipelineCompile ?? DEFAULT_RENDER_QUALITY.pipelineCompile,
|
|
221
223
|
skinScattering: options.skinScattering ?? DEFAULT_RENDER_QUALITY.skinScattering,
|
|
222
224
|
directionalShadowMaxDistance: options.directionalShadowMaxDistance ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxDistance,
|
|
223
225
|
directionalShadowMaxSlope: options.directionalShadowMaxSlope ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxSlope,
|
|
@@ -318,6 +320,9 @@ export function resolveRenderQuality(options = {}) {
|
|
|
318
320
|
quality.glassShadows !== 'off') {
|
|
319
321
|
throw new Error(`RenderQuality.glassShadows must be 'full', 'half' or 'off', got ${String(quality.glassShadows)}`);
|
|
320
322
|
}
|
|
323
|
+
if (quality.pipelineCompile !== 'wait' && quality.pipelineCompile !== 'skip') {
|
|
324
|
+
throw new Error(`RenderQuality.pipelineCompile must be 'wait' or 'skip', got ${String(quality.pipelineCompile)}`);
|
|
325
|
+
}
|
|
321
326
|
if (quality.skinScattering !== 'pre-integrated' && quality.skinScattering !== 'screen-space') {
|
|
322
327
|
throw new Error(`RenderQuality.skinScattering must be 'pre-integrated' or 'screen-space', got ${String(quality.skinScattering)}`);
|
|
323
328
|
}
|
|
@@ -38,6 +38,13 @@ export declare function recordingGl(options?: {
|
|
|
38
38
|
* recording.
|
|
39
39
|
*/
|
|
40
40
|
readonly uniforms?: readonly string[];
|
|
41
|
+
/**
|
|
42
|
+
* Whether a program started under `KHR_parallel_shader_compile` has finished, which the test
|
|
43
|
+
* flips: `COMPLETION_STATUS_KHR` answers `done`. Finished by default, as every program here is.
|
|
44
|
+
*/
|
|
45
|
+
readonly compiling?: {
|
|
46
|
+
done: boolean;
|
|
47
|
+
};
|
|
41
48
|
}): {
|
|
42
49
|
gl: WebGL2RenderingContext;
|
|
43
50
|
canvas: HTMLCanvasElement;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { countUniformVectors } from './uniformVectorBudget.js';
|
|
19
19
|
export function recordingGl(options = {}) {
|
|
20
|
+
const compiling = options.compiling ?? { done: true };
|
|
20
21
|
const extensions = new Set(options.extensions ?? []);
|
|
21
22
|
const uniforms = options.uniforms ?? [];
|
|
22
23
|
/* What `clearDepth` last set, so a module that borrows the value can be seen to put it back. */
|
|
@@ -93,6 +94,13 @@ export function recordingGl(options = {}) {
|
|
|
93
94
|
HALF_FLOAT: 0x140b,
|
|
94
95
|
DEPTH_COMPONENT: 0x1902,
|
|
95
96
|
UNSIGNED_INT: 0x1405,
|
|
97
|
+
/* Named so a mesh's index width and each attribute's type can be read back: the buffer its
|
|
98
|
+
indices fill, the type every draw of it names, and how each attribute is read. See
|
|
99
|
+
`mesh.test.ts`. */
|
|
100
|
+
UNSIGNED_SHORT: 0x1403,
|
|
101
|
+
SHORT: 0x1402,
|
|
102
|
+
FLOAT: 0x1406,
|
|
103
|
+
ELEMENT_ARRAY_BUFFER: 0x8893,
|
|
96
104
|
LINEAR: 0x2601,
|
|
97
105
|
COLOR_BUFFER_BIT: 0x00004000,
|
|
98
106
|
/* Named so a test can tell a mesh that declared itself deforming from one that did not. */
|
|
@@ -137,6 +145,8 @@ export function recordingGl(options = {}) {
|
|
|
137
145
|
if (name === 'EXT_clip_control') {
|
|
138
146
|
return { LOWER_LEFT_EXT: 0x8ca1, ZERO_TO_ONE_EXT: 0x935f, clipControlEXT: () => undefined };
|
|
139
147
|
}
|
|
148
|
+
if (name === 'KHR_parallel_shader_compile')
|
|
149
|
+
return { COMPLETION_STATUS_KHR: 0x91b1 };
|
|
140
150
|
return {};
|
|
141
151
|
},
|
|
142
152
|
getShaderParameter: () => true,
|
|
@@ -166,6 +176,8 @@ export function recordingGl(options = {}) {
|
|
|
166
176
|
/* Linked yes; zero active uniforms and attributes, which ends every reflection loop at once
|
|
167
177
|
and is why this harness does not have to know a single uniform name. */
|
|
168
178
|
getProgramParameter: (program, pname) => {
|
|
179
|
+
if (pname === 0x91b1)
|
|
180
|
+
return compiling.done;
|
|
169
181
|
if (pname === constants.ACTIVE_UNIFORMS)
|
|
170
182
|
return uniforms.length;
|
|
171
183
|
if (pname === constants.ACTIVE_ATTRIBUTES)
|
package/dist/render/shader.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
/** Shader compilation with fail-fast diagnostics (init-time only, never per-frame). */
|
|
2
2
|
export declare function compileProgram(gl: WebGL2RenderingContext, vertexSource: string, fragmentSource: string, label: string): WebGLProgram;
|
|
3
|
+
/**
|
|
4
|
+
* A program compiled and linked without asking whether that worked, since asking waits for the
|
|
5
|
+
* driver to finish: for a context offering `KHR_parallel_shader_compile`, whose
|
|
6
|
+
* `COMPLETION_STATUS_KHR` says when asking no longer waits. `finishProgram` asks then, and fails
|
|
7
|
+
* as loudly as `compileProgram` does; the stages are kept until it does, for their logs.
|
|
8
|
+
*/
|
|
9
|
+
export interface StartedProgram {
|
|
10
|
+
readonly program: WebGLProgram;
|
|
11
|
+
readonly vertex: WebGLShader;
|
|
12
|
+
readonly fragment: WebGLShader;
|
|
13
|
+
readonly label: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function startProgram(gl: WebGL2RenderingContext, vertexSource: string, fragmentSource: string, label: string): StartedProgram;
|
|
16
|
+
/** A started program, checked as `compileProgram` checks one: its stages, then its link. */
|
|
17
|
+
export declare function finishProgram(gl: WebGL2RenderingContext, started: StartedProgram): WebGLProgram;
|
|
3
18
|
export declare function setUniformStrictMode(on: boolean): void;
|
|
4
19
|
/** Locations of every active uniform, keyed by name. */
|
|
5
20
|
export declare function uniformLocations(gl: WebGL2RenderingContext, program: WebGLProgram, label?: string): Record<string, WebGLUniformLocation>;
|
package/dist/render/shader.js
CHANGED
|
@@ -17,6 +17,47 @@ export function compileProgram(gl, vertexSource, fragmentSource, label) {
|
|
|
17
17
|
}
|
|
18
18
|
return program;
|
|
19
19
|
}
|
|
20
|
+
export function startProgram(gl, vertexSource, fragmentSource, label) {
|
|
21
|
+
const stage = (type, source, name) => {
|
|
22
|
+
const shader = gl.createShader(type);
|
|
23
|
+
if (shader === null)
|
|
24
|
+
throw new Error(`[${label}.${name}] createShader failed`);
|
|
25
|
+
gl.shaderSource(shader, source);
|
|
26
|
+
gl.compileShader(shader);
|
|
27
|
+
return shader;
|
|
28
|
+
};
|
|
29
|
+
const vertex = stage(gl.VERTEX_SHADER, vertexSource, 'vert');
|
|
30
|
+
const fragment = stage(gl.FRAGMENT_SHADER, fragmentSource, 'frag');
|
|
31
|
+
const program = gl.createProgram();
|
|
32
|
+
if (program === null)
|
|
33
|
+
throw new Error(`[${label}] createProgram failed`);
|
|
34
|
+
gl.attachShader(program, vertex);
|
|
35
|
+
gl.attachShader(program, fragment);
|
|
36
|
+
gl.linkProgram(program);
|
|
37
|
+
return { program, vertex, fragment, label };
|
|
38
|
+
}
|
|
39
|
+
/** A started program, checked as `compileProgram` checks one: its stages, then its link. */
|
|
40
|
+
export function finishProgram(gl, started) {
|
|
41
|
+
const { program, vertex, fragment, label } = started;
|
|
42
|
+
const fail = (what, log) => {
|
|
43
|
+
gl.deleteShader(vertex);
|
|
44
|
+
gl.deleteShader(fragment);
|
|
45
|
+
gl.deleteProgram(program);
|
|
46
|
+
throw new Error(`[${label}${what}] ${log ?? 'no log'}`);
|
|
47
|
+
};
|
|
48
|
+
if (!gl.getShaderParameter(vertex, gl.COMPILE_STATUS)) {
|
|
49
|
+
fail('.vert compile failed:', gl.getShaderInfoLog(vertex));
|
|
50
|
+
}
|
|
51
|
+
if (!gl.getShaderParameter(fragment, gl.COMPILE_STATUS)) {
|
|
52
|
+
fail('.frag compile failed:', gl.getShaderInfoLog(fragment));
|
|
53
|
+
}
|
|
54
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
55
|
+
fail(' link failed:', gl.getProgramInfoLog(program));
|
|
56
|
+
}
|
|
57
|
+
gl.deleteShader(vertex);
|
|
58
|
+
gl.deleteShader(fragment);
|
|
59
|
+
return program;
|
|
60
|
+
}
|
|
20
61
|
/**
|
|
21
62
|
* Whether a uniform lookup that cannot reach the GPU is reported.
|
|
22
63
|
*
|
|
@@ -29,13 +29,56 @@ export const DIRECTIONALSHADOW_GLSL = `vec2 receiverPlaneDepthGradient(vec3 p) {
|
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
/* One stored depth against a receiver, in a map spanning \`span\` metres of depth. */
|
|
33
|
+
float directionalVisibilityAt(float receiverDepth, float compareDepth, float storedDepth, float span) {
|
|
33
34
|
if (compareDepth <= storedDepth) return 1.0;
|
|
34
|
-
float rayDistance = max(receiverDepth - storedDepth, 0.0) *
|
|
35
|
+
float rayDistance = max(receiverDepth - storedDepth, 0.0) * span;
|
|
35
36
|
float groundDistance = rayDistance * length(uDirectionalDir.xz);
|
|
36
37
|
return 1.0 - shadowReach(groundDistance, uShadowMaxDistance);
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
float directionalVisibility(float receiverDepth, float compareDepth, float storedDepth) {
|
|
41
|
+
return directionalVisibilityAt(receiverDepth, compareDepth, storedDepth, uShadowDepthSpan);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* How far inside its square a receiver stands in a layer's map: 1 well inside, 0 at its border. */
|
|
45
|
+
float sunMapFade(vec3 p) {
|
|
46
|
+
if (p.z > 1.0 || p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0) return 0.0;
|
|
47
|
+
vec2 fromCentre = abs(p.xy - 0.5) * 2.0;
|
|
48
|
+
return (1.0 - smoothstep(0.72, 0.98, max(fromCentre.x, fromCentre.y))) *
|
|
49
|
+
(1.0 - smoothstep(0.90, 1.0, p.z));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* The moving layer at its own place, where it has a matrix of its own (movingSun.ts): the coarse
|
|
54
|
+
* test of shadowFactor, and its filter, each against the moving map's own depth span, and lit by
|
|
55
|
+
* how far inside the moving map's square the receiver stands rather than cut at its border.
|
|
56
|
+
*/
|
|
57
|
+
float movingSunCoarse(vec3 pm, float span) {
|
|
58
|
+
float inside = sunMapFade(pm);
|
|
59
|
+
if (inside <= 0.0) return 1.0;
|
|
60
|
+
float stored = textureLod(uSunShadows, vec3(pm.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
|
|
61
|
+
return mix(1.0, directionalVisibilityAt(pm.z, pm.z - 1.0 / span, stored, span), inside);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
float movingSunFiltered(vec3 pm, vec2 gradient, float span) {
|
|
65
|
+
float inside = sunMapFade(pm);
|
|
66
|
+
if (inside <= 0.0) return 1.0;
|
|
67
|
+
float texel = 1.35 / uShadowMapSize;
|
|
68
|
+
float slopeLimit = 9.1 / uShadowMapSize;
|
|
69
|
+
float lit = 0.0;
|
|
70
|
+
for (int i = 0; i < ${MAX_SHADOW_FILTER_TAPS}; i++) {
|
|
71
|
+
if (i >= uShadowFilterTaps) break;
|
|
72
|
+
vec2 sampleUv = pm.xy + DIRECTIONAL_PCF_OFFSETS[i] * texel;
|
|
73
|
+
vec2 texelCentre = (floor(sampleUv * uShadowMapSize) + 0.5) / uShadowMapSize;
|
|
74
|
+
float slopeOffset = clamp(dot(gradient, texelCentre - pm.xy), -slopeLimit, slopeLimit);
|
|
75
|
+
float tapReceiverDepth = pm.z + slopeOffset;
|
|
76
|
+
float stored = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
|
|
77
|
+
lit += directionalVisibilityAt(tapReceiverDepth, tapReceiverDepth - 0.14 / span, stored, span);
|
|
78
|
+
}
|
|
79
|
+
return mix(1.0, lit / float(max(uShadowFilterTaps, 1)), inside);
|
|
80
|
+
}
|
|
81
|
+
|
|
39
82
|
float shadowFactor(float directionalNdl) {
|
|
40
83
|
if (uShadowStrength <= 0.0) return 1.0;
|
|
41
84
|
|
|
@@ -65,6 +108,14 @@ float shadowFactor(float directionalNdl) {
|
|
|
65
108
|
vec3 p = vLightPos.xyz / vLightPos.w;
|
|
66
109
|
p = p * 0.5 + 0.5;
|
|
67
110
|
vec2 depthGradient = receiverPlaneDepthGradient(p);
|
|
111
|
+
/* The moving layer's own place and plane where it has a matrix of its own (movingSun.ts), taken
|
|
112
|
+
here beside the static layer's for the same reason: above every varying-dependent return. */
|
|
113
|
+
vec3 pm = p;
|
|
114
|
+
vec2 movingGradient = depthGradient;
|
|
115
|
+
if (MOVING_SUN) {
|
|
116
|
+
pm = movingSunAt();
|
|
117
|
+
movingGradient = receiverPlaneDepthGradient(pm);
|
|
118
|
+
}
|
|
68
119
|
|
|
69
120
|
// Near a surface's own light terminator its direct-light term is already
|
|
70
121
|
// approaching zero, while the orthographic projection of that surface
|
|
@@ -129,7 +180,8 @@ float shadowFactor(float directionalNdl) {
|
|
|
129
180
|
float coarseCompare = p.z - 1.0 / uShadowDepthSpan;
|
|
130
181
|
float coarse =
|
|
131
182
|
directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_STATIC_LAYER}.0), 0.0).r) *
|
|
132
|
-
|
|
183
|
+
(MOVING_SUN ? movingSunCoarse(pm, movingSunSpan()) :
|
|
184
|
+
directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r));
|
|
133
185
|
if (uPeeledShadowEnabled != 0) {
|
|
134
186
|
coarse *= directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_PEELED_LAYER}.0), 0.0).r);
|
|
135
187
|
}
|
|
@@ -193,7 +245,6 @@ float shadowFactor(float directionalNdl) {
|
|
|
193
245
|
* there is no mip to select and level zero is the only thing \`texture\` could have read.
|
|
194
246
|
*/
|
|
195
247
|
float staticDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_STATIC_LAYER}.0), 0.0).r;
|
|
196
|
-
float dynamicDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
|
|
197
248
|
staticLit += directionalVisibility(tapReceiverDepth, compare, staticDepth);
|
|
198
249
|
if (uPeeledShadowEnabled != 0) {
|
|
199
250
|
float peeledDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_PEELED_LAYER}.0), 0.0).r;
|
|
@@ -201,12 +252,18 @@ float shadowFactor(float directionalNdl) {
|
|
|
201
252
|
} else {
|
|
202
253
|
peeledLit += 1.0;
|
|
203
254
|
}
|
|
204
|
-
|
|
255
|
+
/* The moving layer here where it shares the static one's place; at its own below otherwise.
|
|
256
|
+
Branched on the switch itself, never its negation: see overridableConstants in transform.mjs. */
|
|
257
|
+
if (MOVING_SUN) {
|
|
258
|
+
} else {
|
|
259
|
+
float dynamicDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
|
|
260
|
+
dynamicLit += directionalVisibility(tapReceiverDepth, compare, dynamicDepth);
|
|
261
|
+
}
|
|
205
262
|
}
|
|
206
263
|
float tapCount = float(max(uShadowFilterTaps, 1));
|
|
207
264
|
staticLit /= tapCount;
|
|
208
265
|
peeledLit /= tapCount;
|
|
209
|
-
dynamicLit
|
|
266
|
+
dynamicLit = MOVING_SUN ? movingSunFiltered(pm, movingGradient, movingSunSpan()) : dynamicLit / tapCount;
|
|
210
267
|
|
|
211
268
|
// Independent layers preserve every recorded fact at an overlap: each
|
|
212
269
|
// caster keeps its own distance fade, and their transmissions compose
|
|
@@ -227,11 +284,11 @@ float shadowFactor(float directionalNdl) {
|
|
|
227
284
|
* behind the receiver lending it colour within the spread, which only a receiver between two panes
|
|
228
285
|
* meets. One function for both layers, because two copies were most of the lit shaders' growth.
|
|
229
286
|
*/
|
|
230
|
-
vec3 sunGlassLayer(vec3 p, float paneLayer, float tintLayer, float bias, float texel, float uvPerMetre) {
|
|
287
|
+
vec3 sunGlassLayer(vec3 p, float paneLayer, float tintLayer, float bias, float texel, float uvPerMetre, float span) {
|
|
231
288
|
float tintSize = float(textureSize(uSunGlassTints, 0).x);
|
|
232
289
|
float clarity = textureLod(uSunGlassTints, vec3(p.xy, tintLayer), 0.0).a;
|
|
233
290
|
float behind =
|
|
234
|
-
max(p.z - textureLod(uSunShadows, vec3(p.xy, paneLayer), 0.0).r, 0.0) *
|
|
291
|
+
max(p.z - textureLod(uSunShadows, vec3(p.xy, paneLayer), 0.0).r, 0.0) * span;
|
|
235
292
|
float radius = clamp(
|
|
236
293
|
(1.0 - clarity) * ${FROST_SPREAD.toFixed(6)} * behind * uvPerMetre,
|
|
237
294
|
texel,
|
|
@@ -271,6 +328,13 @@ vec3 sunGlassLookup(float directionalNdl) {
|
|
|
271
328
|
/* Map units per metre along the receiver, from derivatives taken first in uniform control flow,
|
|
272
329
|
so a frost spread stated in metres lands the same whatever size of light matrix a consumer built. */
|
|
273
330
|
float uvPerMetre = length(dFdx(p.xy)) / max(length(dFdx(vWorldPos)), 1e-6);
|
|
331
|
+
/* The moving glass at its own place where its layer has a matrix of its own: movingSun.ts. */
|
|
332
|
+
vec3 pm = p;
|
|
333
|
+
float movingPerMetre = uvPerMetre;
|
|
334
|
+
if (MOVING_SUN) {
|
|
335
|
+
pm = movingSunAt();
|
|
336
|
+
movingPerMetre = length(dFdx(pm.xy)) / max(length(dFdx(vWorldPos)), 1e-6);
|
|
337
|
+
}
|
|
274
338
|
if (uShadowStrength <= 0.0 || textureSize(uSunGlassTints, 0).x <= 1) return vec3(1.0);
|
|
275
339
|
if (directionalNdl <= 0.0) return vec3(1.0);
|
|
276
340
|
if (p.z > 1.0 || p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0) return vec3(1.0);
|
|
@@ -288,8 +352,18 @@ vec3 sunGlassLookup(float directionalNdl) {
|
|
|
288
352
|
|
|
289
353
|
float bias = 0.14 / uShadowDepthSpan;
|
|
290
354
|
float texel = 1.35 / uShadowMapSize;
|
|
291
|
-
vec3 staticTint = sunGlassLayer(p, ${SUN_GLASS_STATIC_LAYER}.0, 0.0, bias, texel, uvPerMetre);
|
|
292
|
-
vec3 movingTint =
|
|
355
|
+
vec3 staticTint = sunGlassLayer(p, ${SUN_GLASS_STATIC_LAYER}.0, 0.0, bias, texel, uvPerMetre, uShadowDepthSpan);
|
|
356
|
+
vec3 movingTint = MOVING_SUN && sunMapFade(pm) <= 0.0
|
|
357
|
+
? vec3(1.0)
|
|
358
|
+
: sunGlassLayer(
|
|
359
|
+
pm,
|
|
360
|
+
${SUN_GLASS_MOVING_LAYER}.0,
|
|
361
|
+
1.0,
|
|
362
|
+
MOVING_SUN ? 0.14 / movingSunSpan() : bias,
|
|
363
|
+
texel,
|
|
364
|
+
movingPerMetre,
|
|
365
|
+
MOVING_SUN ? movingSunSpan() : uShadowDepthSpan
|
|
366
|
+
);
|
|
293
367
|
return mix(vec3(1.0), staticTint * movingTint, strength);
|
|
294
368
|
}
|
|
295
369
|
|
|
@@ -8,7 +8,7 @@ export declare function glassTintGlsl(glassShadows: boolean): string;
|
|
|
8
8
|
* The lit stage's switches, by the name of the constant each is: the decision both backends make
|
|
9
9
|
* about which optional code a lit program carries. See `litSwitchesGlsl`.
|
|
10
10
|
*/
|
|
11
|
-
export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT' | 'PHYSICAL_SPECULAR' | 'SURFACE_OVERLAY' | 'REFLECTION_MAPS';
|
|
11
|
+
export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT' | 'PHYSICAL_SPECULAR' | 'SURFACE_OVERLAY' | 'REFLECTION_MAPS' | 'WORLD_UVS' | 'MOVING_SUN' | 'LAYERED';
|
|
12
12
|
/** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
|
|
13
13
|
export declare function noLitSwitches(): Record<LitSwitch, boolean>;
|
|
14
14
|
/** Which of the lit stage's optional features a build reads. See `litSwitchesGlsl`. */
|
|
@@ -125,6 +125,21 @@ export interface FlatShaderOptions {
|
|
|
125
125
|
* the frame's materials' reflections. True when absent. See `reflectionSurfaceGlsl`.
|
|
126
126
|
*/
|
|
127
127
|
readonly reflectionMaps?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Whether it places a material's maps by the world, `uWorldUv` and all (`SurfaceMaterial
|
|
130
|
+
* .projection`). True when absent. See `worldUvGlsl`.
|
|
131
|
+
*/
|
|
132
|
+
readonly worldUvs?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Whether it reads the sun's moving layer through a matrix of its own, \`uMovingLightViewProj\`
|
|
135
|
+
* and all. True when absent. See \`movingSunGlsl\`.
|
|
136
|
+
*/
|
|
137
|
+
readonly movingSun?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Whether it blends a material's layers by a mask, \`uLayers\` and all (\`SurfaceMaterial.layers\`).
|
|
140
|
+
* True when absent. See \`layeredGlsl\`.
|
|
141
|
+
*/
|
|
142
|
+
readonly layered?: boolean;
|
|
128
143
|
/**
|
|
129
144
|
* How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
|
|
130
145
|
*
|
|
@@ -21,6 +21,10 @@ import { DRIFT_LIGHT_GLSL } from './driftLight.js';
|
|
|
21
21
|
import { SURFACE_EFFECTS_GLSL } from './surfaceEffects.js';
|
|
22
22
|
import { overlayGlsl } from './overlay.js';
|
|
23
23
|
import { reflectionSurfaceGlsl } from './reflectionSurface.js';
|
|
24
|
+
import { worldUvGlsl } from './worldUv.js';
|
|
25
|
+
import { movingSunGlsl } from './movingSun.js';
|
|
26
|
+
import { layeredGlsl } from './layered.js';
|
|
27
|
+
import { MAP_READS_GLSL } from './mapReads.js';
|
|
24
28
|
import { CHANNEL_BEND, LIT_CHANNEL_ATTRIBUTE } from '../vertexChannel.js';
|
|
25
29
|
import { BONE_ANIMATION_GLSL } from '../boneAnimation.js';
|
|
26
30
|
import { FULL_LIGHT_BUDGET } from '../../uniformVectorBudget.js';
|
|
@@ -461,6 +465,8 @@ export function flatFrag(options) {
|
|
|
461
465
|
surfaceEffects: options.surfaceEffects ?? true,
|
|
462
466
|
driftLight: options.driftLight ?? true,
|
|
463
467
|
}),
|
|
468
|
+
/* Depth zero and ahead of the sun's lookup, which calls it; its switch takes the next id. */
|
|
469
|
+
movingSunGlsl(options.movingSun ?? true),
|
|
464
470
|
LOBES_GLSL,
|
|
465
471
|
POINTSHADOW_GLSL,
|
|
466
472
|
DIRECTIONALSHADOW_GLSL,
|
|
@@ -486,8 +492,6 @@ export function flatFrag(options) {
|
|
|
486
492
|
* and `main` is unconditional by construction.
|
|
487
493
|
*/
|
|
488
494
|
TANGENT_FRAME_GLSL,
|
|
489
|
-
/* After the frame it calls, unconditional for the same reason. See normalMap.ts. */
|
|
490
|
-
NORMAL_MAP_GLSL,
|
|
491
495
|
/* Unconditional for the same reason, and called only where a cutoff was asked for. */
|
|
492
496
|
CUTOUT_COVERAGE_GLSL,
|
|
493
497
|
CUTOUT_DITHER_GLSL,
|
|
@@ -499,6 +503,15 @@ export function flatFrag(options) {
|
|
|
499
503
|
overlayGlsl(options.surfaceOverlay ?? true),
|
|
500
504
|
/* After the overlay, so its switch takes the next id. See reflectionSurface.ts. */
|
|
501
505
|
reflectionSurfaceGlsl(options.reflectionMaps ?? true),
|
|
506
|
+
/* After the reflection maps, so its switch takes the next id; main calls it. worldUv.ts. */
|
|
507
|
+
worldUvGlsl(options.worldUvs ?? true),
|
|
508
|
+
/* After the projection, so its switch takes the next id; the normal map below calls it. */
|
|
509
|
+
layeredGlsl(options.layered ?? true),
|
|
510
|
+
/* Where each map is read, after both of the ways it can be read otherwise. See mapReads.ts. */
|
|
511
|
+
MAP_READS_GLSL,
|
|
512
|
+
/* After the frame and the projection it calls, unconditional for the same reason as the
|
|
513
|
+
frame. See normalMap.ts. */
|
|
514
|
+
NORMAL_MAP_GLSL,
|
|
502
515
|
/* After the models, whose answer to a rectangle it asks for. See areaLight.ts. */
|
|
503
516
|
AREA_LIGHT_GLSL,
|
|
504
517
|
MAIN_GLSL,
|