@driftengine/core 4.10.0 → 4.11.1
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 +107 -0
- package/README.md +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2 -2
- package/dist/render/ambientOcclusionPass.d.ts +3 -2
- package/dist/render/ambientOcclusionPass.js +4 -2
- package/dist/render/backend/api.d.ts +21 -1
- package/dist/render/backend/webgl2/renderer.d.ts +141 -3
- package/dist/render/backend/webgl2/renderer.js +454 -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 +100 -1
- package/dist/render/backend/webgpu/renderer.js +423 -95
- 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/instances.d.ts +13 -1
- package/dist/render/instances.js +10 -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/occlusionFade.d.ts +21 -0
- package/dist/render/occlusionFade.js +27 -0
- 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/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/ambientOcclusion.js +12 -1
- 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 +24 -4
- package/dist/render/shaders/flat/layered.d.ts +27 -0
- package/dist/render/shaders/flat/layered.js +198 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +50 -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 +23 -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/ambientOcclusion.wgsl.d.ts +6 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
- 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 +107 -0
- package/dist/render/surfaceLayers.js +82 -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 +39 -0
- package/dist/render/transmission.d.ts +21 -0
- package/dist/render/transmission.js +21 -0
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/dist/ui/splash.d.ts +42 -2
- package/dist/ui/splash.js +78 -13
- package/package.json +6 -6
- package/src/index.ts +6 -2
- package/src/render/ambientOcclusionPass.ts +4 -1
- package/src/render/backend/api.ts +31 -0
- package/src/render/backend/webgl2/renderer.ts +519 -56
- 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 +622 -164
- 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/instances.ts +18 -1
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/mesh.ts +66 -14
- package/src/render/occlusionFade.ts +29 -0
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/ambientOcclusion.ts +12 -1
- 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 +39 -4
- package/src/render/shaders/flat/layered.ts +198 -0
- package/src/render/shaders/flat/main.ts +50 -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 +23 -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/ambientOcclusion.wgsl.ts +6 -1
- 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 +176 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +39 -0
- package/src/render/transmission.ts +27 -0
- package/src/render/vertexPacking.ts +109 -0
- package/src/ui/splash.ts +83 -14
- 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
package/src/render/mesh.ts
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
import { MorphTexture } from './backend/webgl2/morphTexture.ts';
|
|
4
4
|
import { ABSENT_ATTRIBUTE } from './vertexDefaults.ts';
|
|
5
5
|
import { UPLOAD_BYTES_PER_STEP } from './uploadStep.ts';
|
|
6
|
+
import { compactIndices } from './indexWidth.ts';
|
|
7
|
+
import {
|
|
8
|
+
fixedArray,
|
|
9
|
+
sharedValue,
|
|
10
|
+
vertexPackingOf,
|
|
11
|
+
type AttributePacking,
|
|
12
|
+
} from './vertexPacking.ts';
|
|
6
13
|
|
|
7
14
|
/*
|
|
8
15
|
* `MeshData` and its validator live in `@driftengine/drft`, because the container's whole
|
|
@@ -92,7 +99,14 @@ export class Mesh {
|
|
|
92
99
|
private readonly vao: WebGLVertexArrayObject;
|
|
93
100
|
private readonly buffers: WebGLBuffer[] = [];
|
|
94
101
|
/** Every attribute bound from a buffer, so a batch can build a vertex array of its own. */
|
|
95
|
-
private readonly layout: {
|
|
102
|
+
private readonly layout: {
|
|
103
|
+
location: number;
|
|
104
|
+
buffer: WebGLBuffer;
|
|
105
|
+
size: number;
|
|
106
|
+
/** `FLOAT`, or a sixteen-bit type read normalised: `vertexPacking.ts`. */
|
|
107
|
+
type: number;
|
|
108
|
+
normalized: boolean;
|
|
109
|
+
}[] = [];
|
|
96
110
|
private indexBuffer: WebGLBuffer | null = null;
|
|
97
111
|
/**
|
|
98
112
|
* The position and normal buffers, kept so `update` can rewrite them.
|
|
@@ -110,6 +124,8 @@ export class Mesh {
|
|
|
110
124
|
* same frame.
|
|
111
125
|
*/
|
|
112
126
|
readonly indexCount: number;
|
|
127
|
+
/** `UNSIGNED_SHORT` or `UNSIGNED_INT`, as `compactIndices` stored them: every draw says which. */
|
|
128
|
+
private readonly indexType: number;
|
|
113
129
|
/**
|
|
114
130
|
* The constant values this mesh's *absent* attributes are shaded with, re-applied on
|
|
115
131
|
* every draw.
|
|
@@ -226,11 +242,13 @@ export class Mesh {
|
|
|
226
242
|
* that has been told a lie every frame is the shape of a stall nobody can find.
|
|
227
243
|
*/
|
|
228
244
|
const usage = dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW;
|
|
245
|
+
/* How each attribute travels: sixteen-bit fixed point, or one value as a constant. */
|
|
246
|
+
const packing = vertexPackingOf(data, dynamic);
|
|
229
247
|
const positions = this.attachAttribute(gl, ATTR_POSITION, data.positions, 3, usage);
|
|
230
|
-
const normals = this.attachAttribute(gl, ATTR_NORMAL, data.normals, 3, usage);
|
|
248
|
+
const normals = this.attachAttribute(gl, ATTR_NORMAL, data.normals, 3, usage, packing.normals);
|
|
231
249
|
if (dynamic) this.deformable = { positions, normals };
|
|
232
|
-
this.
|
|
233
|
-
this.
|
|
250
|
+
this.attachShared(gl, ATTR_COLOR, data.colors, 3, packing.colors);
|
|
251
|
+
this.attachShared(gl, ATTR_EMISSIVE, data.emissive, 1, packing.emissive);
|
|
234
252
|
/*
|
|
235
253
|
* Absent means "not shiny", supplied as a constant rather than as a buffer of
|
|
236
254
|
* zeroes. `vertexAttrib1f` sets the value a *disabled* attribute reads, so this
|
|
@@ -304,7 +322,7 @@ export class Mesh {
|
|
|
304
322
|
this.constants.push({ location: ATTR_WEIGHTS, value: ABSENT_ATTRIBUTE['weights'] });
|
|
305
323
|
} else {
|
|
306
324
|
this.attachAttribute(gl, ATTR_JOINTS, data.joints, 4);
|
|
307
|
-
this.attachAttribute(gl, ATTR_WEIGHTS, data.weights, 4);
|
|
325
|
+
this.attachAttribute(gl, ATTR_WEIGHTS, data.weights, 4, gl.STATIC_DRAW, packing.weights);
|
|
308
326
|
}
|
|
309
327
|
/* The fifth to eighth influences, or zeros, which add nothing. See `isSkinnedEight`. */
|
|
310
328
|
this.isSkinnedEight = this.isSkinned && data.joints2 !== undefined;
|
|
@@ -313,7 +331,7 @@ export class Mesh {
|
|
|
313
331
|
this.constants.push({ location: ATTR_WEIGHTS2, value: ABSENT_ATTRIBUTE['weights2'] });
|
|
314
332
|
} else {
|
|
315
333
|
this.attachAttribute(gl, ATTR_JOINTS2, data.joints2, 4);
|
|
316
|
-
this.attachAttribute(gl, ATTR_WEIGHTS2, data.weights2, 4);
|
|
334
|
+
this.attachAttribute(gl, ATTR_WEIGHTS2, data.weights2, 4, gl.STATIC_DRAW, packing.weights2);
|
|
317
335
|
}
|
|
318
336
|
|
|
319
337
|
this.hasTangents = data.tangents !== undefined;
|
|
@@ -321,7 +339,7 @@ export class Mesh {
|
|
|
321
339
|
gl.disableVertexAttribArray(ATTR_TANGENT);
|
|
322
340
|
this.constants.push({ location: ATTR_TANGENT, value: ABSENT_ATTRIBUTE['tangents'] });
|
|
323
341
|
} else {
|
|
324
|
-
this.attachAttribute(gl, ATTR_TANGENT, data.tangents, 4);
|
|
342
|
+
this.attachAttribute(gl, ATTR_TANGENT, data.tangents, 4, gl.STATIC_DRAW, packing.tangents);
|
|
325
343
|
}
|
|
326
344
|
|
|
327
345
|
this.hasGrain = data.grain !== undefined;
|
|
@@ -351,7 +369,9 @@ export class Mesh {
|
|
|
351
369
|
this.buffers.push(indexBuffer);
|
|
352
370
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
|
353
371
|
this.indexBuffer = indexBuffer;
|
|
354
|
-
|
|
372
|
+
const indices = compactIndices(data.indices, this.vertexCount);
|
|
373
|
+
this.indexType = indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT;
|
|
374
|
+
this.allocate(gl, gl.ELEMENT_ARRAY_BUFFER, indexBuffer, indices, gl.STATIC_DRAW);
|
|
355
375
|
|
|
356
376
|
gl.bindVertexArray(null);
|
|
357
377
|
}
|
|
@@ -370,7 +390,7 @@ export class Mesh {
|
|
|
370
390
|
const value = constant.value;
|
|
371
391
|
applyConstant(gl, constant.location, value);
|
|
372
392
|
}
|
|
373
|
-
gl.drawElements(gl.TRIANGLES, this.indexCount,
|
|
393
|
+
gl.drawElements(gl.TRIANGLES, this.indexCount, this.indexType, 0);
|
|
374
394
|
}
|
|
375
395
|
|
|
376
396
|
/**
|
|
@@ -394,7 +414,14 @@ export class Mesh {
|
|
|
394
414
|
for (const attribute of this.layout) {
|
|
395
415
|
gl.bindBuffer(gl.ARRAY_BUFFER, attribute.buffer);
|
|
396
416
|
gl.enableVertexAttribArray(attribute.location);
|
|
397
|
-
gl.vertexAttribPointer(
|
|
417
|
+
gl.vertexAttribPointer(
|
|
418
|
+
attribute.location,
|
|
419
|
+
attribute.size,
|
|
420
|
+
attribute.type,
|
|
421
|
+
attribute.normalized,
|
|
422
|
+
0,
|
|
423
|
+
0,
|
|
424
|
+
);
|
|
398
425
|
}
|
|
399
426
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
|
|
400
427
|
gl.bindBuffer(gl.ARRAY_BUFFER, instances);
|
|
@@ -424,7 +451,7 @@ export class Mesh {
|
|
|
424
451
|
const value = constant.value;
|
|
425
452
|
applyConstant(gl, constant.location, value);
|
|
426
453
|
}
|
|
427
|
-
gl.drawElementsInstanced(gl.TRIANGLES, this.indexCount,
|
|
454
|
+
gl.drawElementsInstanced(gl.TRIANGLES, this.indexCount, this.indexType, 0, count);
|
|
428
455
|
}
|
|
429
456
|
|
|
430
457
|
dispose(gl: WebGL2RenderingContext): void {
|
|
@@ -468,24 +495,49 @@ export class Mesh {
|
|
|
468
495
|
}
|
|
469
496
|
}
|
|
470
497
|
|
|
498
|
+
/**
|
|
499
|
+
* One attribute's buffer, as floats or in its sixteen-bit fixed point (`vertexPacking.ts`), which
|
|
500
|
+
* WebGL2 reads back through a normalised `SHORT` or `UNSIGNED_SHORT`: three lanes wide where
|
|
501
|
+
* WebGPU needs four, so a normal costs six bytes here.
|
|
502
|
+
*/
|
|
471
503
|
private attachAttribute(
|
|
472
504
|
gl: WebGL2RenderingContext,
|
|
473
505
|
location: number,
|
|
474
506
|
data: Float32Array,
|
|
475
507
|
size: number,
|
|
476
508
|
usage: number = gl.STATIC_DRAW,
|
|
509
|
+
packing: AttributePacking = 'float',
|
|
477
510
|
): WebGLBuffer {
|
|
478
511
|
const buffer = gl.createBuffer();
|
|
479
512
|
if (buffer === null) throw new Error('createBuffer failed');
|
|
480
513
|
this.buffers.push(buffer);
|
|
481
514
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
482
|
-
|
|
515
|
+
const fixed = packing === 'snorm16' || packing === 'unorm16';
|
|
516
|
+
this.allocate(gl, gl.ARRAY_BUFFER, buffer, fixed ? fixedArray(data, packing) : data, usage);
|
|
517
|
+
const type =
|
|
518
|
+
packing === 'snorm16' ? gl.SHORT : packing === 'unorm16' ? gl.UNSIGNED_SHORT : gl.FLOAT;
|
|
483
519
|
gl.enableVertexAttribArray(location);
|
|
484
|
-
gl.vertexAttribPointer(location, size,
|
|
485
|
-
this.layout.push({ location, buffer, size });
|
|
520
|
+
gl.vertexAttribPointer(location, size, type, fixed, 0, 0);
|
|
521
|
+
this.layout.push({ location, buffer, size, type, normalized: fixed });
|
|
486
522
|
return buffer;
|
|
487
523
|
}
|
|
488
524
|
|
|
525
|
+
/** A buffer, or the one value every vertex shares as a constant, as the packing says. */
|
|
526
|
+
private attachShared(
|
|
527
|
+
gl: WebGL2RenderingContext,
|
|
528
|
+
location: number,
|
|
529
|
+
data: Float32Array,
|
|
530
|
+
size: number,
|
|
531
|
+
packing: AttributePacking,
|
|
532
|
+
): void {
|
|
533
|
+
if (packing !== 'constant') {
|
|
534
|
+
this.attachAttribute(gl, location, data, size, gl.STATIC_DRAW, packing);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
gl.disableVertexAttribArray(location);
|
|
538
|
+
this.constants.push({ location, value: sharedValue(data, size) });
|
|
539
|
+
}
|
|
540
|
+
|
|
489
541
|
/**
|
|
490
542
|
* Give a bound buffer its storage, and its contents now or later.
|
|
491
543
|
*
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where ambient occlusion fades out with distance: `setAmbientOcclusionFade`'s two numbers as the
|
|
3
|
+
* estimate reads them, `uFade` in `shaders/ambientOcclusion.ts`.
|
|
4
|
+
*
|
|
5
|
+
* **Occlusion is whole up to `distance` metres from the eye and gone `radius` metres past it.** A
|
|
6
|
+
* screen-space estimate reaches as far as the depth does, and far off the depth buffer's steps are
|
|
7
|
+
* metres apart: a sky dome or a mountain range kilometres away is shaded in rings that follow
|
|
8
|
+
* those steps, and nothing there is in contact with anything the estimate could be right about.
|
|
9
|
+
* A pixel past the fade skips the estimate's walk, as the sky does, so it costs less as well.
|
|
10
|
+
*
|
|
11
|
+
* **What it gives up**: distant contact shade that is real — a doorway a hundred metres off — fades
|
|
12
|
+
* with the rest, and the distance is the eye's and not the subject's, so a long lens fades what it
|
|
13
|
+
* frames close up. What would make it wrong is a scene whose far depth is precise enough to shade,
|
|
14
|
+
* which is a reason to fade further, not to leave the fade off.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** `uFade` for no fade: a distance below zero, which the estimate reads as "never". */
|
|
18
|
+
const NO_FADE = -1;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `distance` and `radius` into `out`, `[distance, radius]`. A distance that is not a finite number
|
|
22
|
+
* at or above zero is no fade, `[−1, 0]`, so `Infinity` reads as "never fades". A radius below zero
|
|
23
|
+
* or not a number is 0, which is a cut at `distance` rather than a ramp.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveOcclusionFade(distance: number, radius: number, out: Float32Array): void {
|
|
26
|
+
const fades = Number.isFinite(distance) && distance >= 0;
|
|
27
|
+
out[0] = fades ? distance : NO_FADE;
|
|
28
|
+
out[1] = fades && Number.isFinite(radius) ? Math.max(radius, 0) : 0;
|
|
29
|
+
}
|
|
@@ -50,6 +50,9 @@ export type ShadowFilterTaps = 4 | 8 | 12;
|
|
|
50
50
|
export type DirectionalShadowDepthLayers = 1 | 2;
|
|
51
51
|
export type WaterReflectionFilterTaps = 1 | 5 | 9;
|
|
52
52
|
|
|
53
|
+
/** What a draw does when its pipeline has not compiled yet: see `RenderQuality.pipelineCompile`. */
|
|
54
|
+
export type PipelineCompile = 'wait' | 'skip';
|
|
55
|
+
|
|
53
56
|
/** How light through glass is coloured: see `RenderQuality.glassShadows`. */
|
|
54
57
|
export type GlassShadows = 'full' | 'half' | 'off';
|
|
55
58
|
|
|
@@ -220,6 +223,34 @@ export interface RenderQuality {
|
|
|
220
223
|
* lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
|
|
221
224
|
*/
|
|
222
225
|
readonly glassShadows: GlassShadows;
|
|
226
|
+
/**
|
|
227
|
+
* What a lit draw does when the pipeline it needs has not compiled: one first needed inside a
|
|
228
|
+
* frame, by a material's own state — two-sided, cut out, a surface model — or a draw state a
|
|
229
|
+
* mesh's creation did not warm.
|
|
230
|
+
*
|
|
231
|
+
* **`'wait'`, the default**: the pipeline is built where the draw asks for it, and on WebGPU the
|
|
232
|
+
* browser compiles it on its GPU process's main thread, holding the frame — 130 to 400 ms a
|
|
233
|
+
* pipeline was reported on a phone, for a material first drawn mid-scene. **`'skip'`** starts the
|
|
234
|
+
* compile off that thread and leaves the draw out until it lands, a few frames to a few dozen,
|
|
235
|
+
* and the draw then appears; nothing waits. Static draws always wait, since a recorded list keeps
|
|
236
|
+
* what it recorded, and a compile that fails is built where it is next asked for, so the device
|
|
237
|
+
* says why rather than the draw vanishing for good.
|
|
238
|
+
*
|
|
239
|
+
* **The way to have neither** is to draw what a scene will use once, behind a loading screen, and
|
|
240
|
+
* await `ready()`, which waits for every compile started — whichever this says. What would make
|
|
241
|
+
* `'skip'` wrong is a frame that must hold everything it was given, such as a capture of the first
|
|
242
|
+
* frame of a scene not awaited.
|
|
243
|
+
*
|
|
244
|
+
* **Measured on a desktop GPU** (`demo/dev/pipelineCompile.html`, eight new variants at once): on
|
|
245
|
+
* WebGPU the frame that met them took 700 to 3,700 ms waiting and 17 ms skipping, the draws
|
|
246
|
+
* landing 21 to 26 frames later. WebGL2 compiles its lit programs when it is made, all but a
|
|
247
|
+
* surface model's, which it compiles at the first draw that wants one: there `'skip'` asks the
|
|
248
|
+
* driver through `KHR_parallel_shader_compile` and waits where a context does not offer it — which
|
|
249
|
+
* ANGLE on Vulkan does not. Where it is offered, ANGLE on OpenGL, the longest frame went from 820 to
|
|
250
|
+
* 1,120 ms to 630 to 700 ms: the driver still finishes its own compile at the program's first
|
|
251
|
+
* draw, which nothing above it can move.
|
|
252
|
+
*/
|
|
253
|
+
readonly pipelineCompile: PipelineCompile;
|
|
223
254
|
/**
|
|
224
255
|
* How light that scatters beneath skin is drawn, for a material whose model is `skinModel`.
|
|
225
256
|
*
|
|
@@ -973,6 +1004,7 @@ export const DEFAULT_RENDER_QUALITY: Readonly<RenderQuality> = Object.freeze({
|
|
|
973
1004
|
lightVolumeSamples: 32,
|
|
974
1005
|
directionalShadowDepthLayers: 2,
|
|
975
1006
|
glassShadows: 'full',
|
|
1007
|
+
pipelineCompile: 'wait',
|
|
976
1008
|
skinScattering: 'pre-integrated',
|
|
977
1009
|
directionalShadowMaxDistance: 6,
|
|
978
1010
|
directionalShadowMaxSlope: 3,
|
|
@@ -1140,6 +1172,7 @@ export function resolveRenderQuality(options: RenderQualityOptions = {}): Readon
|
|
|
1140
1172
|
directionalShadowDepthLayers:
|
|
1141
1173
|
options.directionalShadowDepthLayers ?? DEFAULT_RENDER_QUALITY.directionalShadowDepthLayers,
|
|
1142
1174
|
glassShadows: options.glassShadows ?? DEFAULT_RENDER_QUALITY.glassShadows,
|
|
1175
|
+
pipelineCompile: options.pipelineCompile ?? DEFAULT_RENDER_QUALITY.pipelineCompile,
|
|
1143
1176
|
skinScattering: options.skinScattering ?? DEFAULT_RENDER_QUALITY.skinScattering,
|
|
1144
1177
|
directionalShadowMaxDistance:
|
|
1145
1178
|
options.directionalShadowMaxDistance ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxDistance,
|
|
@@ -1298,6 +1331,11 @@ export function resolveRenderQuality(options: RenderQualityOptions = {}): Readon
|
|
|
1298
1331
|
`RenderQuality.glassShadows must be 'full', 'half' or 'off', got ${String(quality.glassShadows)}`,
|
|
1299
1332
|
);
|
|
1300
1333
|
}
|
|
1334
|
+
if (quality.pipelineCompile !== 'wait' && quality.pipelineCompile !== 'skip') {
|
|
1335
|
+
throw new Error(
|
|
1336
|
+
`RenderQuality.pipelineCompile must be 'wait' or 'skip', got ${String(quality.pipelineCompile)}`,
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1301
1339
|
if (quality.skinScattering !== 'pre-integrated' && quality.skinScattering !== 'screen-space') {
|
|
1302
1340
|
throw new Error(
|
|
1303
1341
|
`RenderQuality.skinScattering must be 'pre-integrated' or 'screen-space', got ${String(quality.skinScattering)}`,
|
|
@@ -58,8 +58,14 @@ export function recordingGl(
|
|
|
58
58
|
* recording.
|
|
59
59
|
*/
|
|
60
60
|
readonly uniforms?: readonly string[];
|
|
61
|
+
/**
|
|
62
|
+
* Whether a program started under `KHR_parallel_shader_compile` has finished, which the test
|
|
63
|
+
* flips: `COMPLETION_STATUS_KHR` answers `done`. Finished by default, as every program here is.
|
|
64
|
+
*/
|
|
65
|
+
readonly compiling?: { done: boolean };
|
|
61
66
|
} = {},
|
|
62
67
|
) {
|
|
68
|
+
const compiling = options.compiling ?? { done: true };
|
|
63
69
|
const extensions = new Set(options.extensions ?? []);
|
|
64
70
|
const uniforms = options.uniforms ?? [];
|
|
65
71
|
/* What `clearDepth` last set, so a module that borrows the value can be seen to put it back. */
|
|
@@ -136,6 +142,13 @@ export function recordingGl(
|
|
|
136
142
|
HALF_FLOAT: 0x140b,
|
|
137
143
|
DEPTH_COMPONENT: 0x1902,
|
|
138
144
|
UNSIGNED_INT: 0x1405,
|
|
145
|
+
/* Named so a mesh's index width and each attribute's type can be read back: the buffer its
|
|
146
|
+
indices fill, the type every draw of it names, and how each attribute is read. See
|
|
147
|
+
`mesh.test.ts`. */
|
|
148
|
+
UNSIGNED_SHORT: 0x1403,
|
|
149
|
+
SHORT: 0x1402,
|
|
150
|
+
FLOAT: 0x1406,
|
|
151
|
+
ELEMENT_ARRAY_BUFFER: 0x8893,
|
|
139
152
|
LINEAR: 0x2601,
|
|
140
153
|
COLOR_BUFFER_BIT: 0x00004000,
|
|
141
154
|
/* Named so a test can tell a mesh that declared itself deforming from one that did not. */
|
|
@@ -173,6 +186,7 @@ export function recordingGl(
|
|
|
173
186
|
if (name === 'EXT_clip_control') {
|
|
174
187
|
return { LOWER_LEFT_EXT: 0x8ca1, ZERO_TO_ONE_EXT: 0x935f, clipControlEXT: () => undefined };
|
|
175
188
|
}
|
|
189
|
+
if (name === 'KHR_parallel_shader_compile') return { COMPLETION_STATUS_KHR: 0x91b1 };
|
|
176
190
|
return {};
|
|
177
191
|
},
|
|
178
192
|
getShaderParameter: () => true,
|
|
@@ -200,6 +214,7 @@ export function recordingGl(
|
|
|
200
214
|
/* Linked yes; zero active uniforms and attributes, which ends every reflection loop at once
|
|
201
215
|
and is why this harness does not have to know a single uniform name. */
|
|
202
216
|
getProgramParameter: (program: unknown, pname: unknown) => {
|
|
217
|
+
if (pname === 0x91b1) return compiling.done;
|
|
203
218
|
if (pname === constants.ACTIVE_UNIFORMS) return uniforms.length;
|
|
204
219
|
if (pname === constants.ACTIVE_ATTRIBUTES) return 0;
|
|
205
220
|
if (pname !== constants.LINK_STATUS) return true;
|
|
@@ -800,6 +800,8 @@ export class SceneTarget {
|
|
|
800
800
|
/** The projection's x and y scales, which turn a world radius into a screen one. */
|
|
801
801
|
readonly projScale: Float32Array;
|
|
802
802
|
readonly invProjection: Float32Array;
|
|
803
|
+
/** Where it fades out with distance, `uFade`: see `occlusionFade.ts`. */
|
|
804
|
+
readonly fade: Float32Array;
|
|
803
805
|
/** Where the frame's jitter put the occlusion, in uv: see the shader's `uAoOffset`. */
|
|
804
806
|
readonly offset?: Float32Array;
|
|
805
807
|
},
|
|
@@ -959,6 +961,7 @@ export class SceneTarget {
|
|
|
959
961
|
ao.radius,
|
|
960
962
|
ao.projScale,
|
|
961
963
|
ao.invProjection,
|
|
964
|
+
ao.fade,
|
|
962
965
|
);
|
|
963
966
|
}
|
|
964
967
|
|
package/src/render/shader.ts
CHANGED
|
@@ -26,6 +26,65 @@ export function compileProgram(
|
|
|
26
26
|
return program;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* A program compiled and linked without asking whether that worked, since asking waits for the
|
|
31
|
+
* driver to finish: for a context offering `KHR_parallel_shader_compile`, whose
|
|
32
|
+
* `COMPLETION_STATUS_KHR` says when asking no longer waits. `finishProgram` asks then, and fails
|
|
33
|
+
* as loudly as `compileProgram` does; the stages are kept until it does, for their logs.
|
|
34
|
+
*/
|
|
35
|
+
export interface StartedProgram {
|
|
36
|
+
readonly program: WebGLProgram;
|
|
37
|
+
readonly vertex: WebGLShader;
|
|
38
|
+
readonly fragment: WebGLShader;
|
|
39
|
+
readonly label: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function startProgram(
|
|
43
|
+
gl: WebGL2RenderingContext,
|
|
44
|
+
vertexSource: string,
|
|
45
|
+
fragmentSource: string,
|
|
46
|
+
label: string,
|
|
47
|
+
): StartedProgram {
|
|
48
|
+
const stage = (type: number, source: string, name: string): WebGLShader => {
|
|
49
|
+
const shader = gl.createShader(type);
|
|
50
|
+
if (shader === null) throw new Error(`[${label}.${name}] createShader failed`);
|
|
51
|
+
gl.shaderSource(shader, source);
|
|
52
|
+
gl.compileShader(shader);
|
|
53
|
+
return shader;
|
|
54
|
+
};
|
|
55
|
+
const vertex = stage(gl.VERTEX_SHADER, vertexSource, 'vert');
|
|
56
|
+
const fragment = stage(gl.FRAGMENT_SHADER, fragmentSource, 'frag');
|
|
57
|
+
const program = gl.createProgram();
|
|
58
|
+
if (program === null) throw new Error(`[${label}] createProgram failed`);
|
|
59
|
+
gl.attachShader(program, vertex);
|
|
60
|
+
gl.attachShader(program, fragment);
|
|
61
|
+
gl.linkProgram(program);
|
|
62
|
+
return { program, vertex, fragment, label };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A started program, checked as `compileProgram` checks one: its stages, then its link. */
|
|
66
|
+
export function finishProgram(gl: WebGL2RenderingContext, started: StartedProgram): WebGLProgram {
|
|
67
|
+
const { program, vertex, fragment, label } = started;
|
|
68
|
+
const fail = (what: string, log: string | null): never => {
|
|
69
|
+
gl.deleteShader(vertex);
|
|
70
|
+
gl.deleteShader(fragment);
|
|
71
|
+
gl.deleteProgram(program);
|
|
72
|
+
throw new Error(`[${label}${what}] ${log ?? 'no log'}`);
|
|
73
|
+
};
|
|
74
|
+
if (!gl.getShaderParameter(vertex, gl.COMPILE_STATUS)) {
|
|
75
|
+
fail('.vert compile failed:', gl.getShaderInfoLog(vertex));
|
|
76
|
+
}
|
|
77
|
+
if (!gl.getShaderParameter(fragment, gl.COMPILE_STATUS)) {
|
|
78
|
+
fail('.frag compile failed:', gl.getShaderInfoLog(fragment));
|
|
79
|
+
}
|
|
80
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
81
|
+
fail(' link failed:', gl.getProgramInfoLog(program));
|
|
82
|
+
}
|
|
83
|
+
gl.deleteShader(vertex);
|
|
84
|
+
gl.deleteShader(fragment);
|
|
85
|
+
return program;
|
|
86
|
+
}
|
|
87
|
+
|
|
29
88
|
/**
|
|
30
89
|
* Whether a uniform lookup that cannot reach the GPU is reported.
|
|
31
90
|
*
|
|
@@ -76,6 +76,11 @@ uniform vec2 uProjScale;
|
|
|
76
76
|
uniform mat4 uInvProjection;
|
|
77
77
|
/** How far the sampling reaches, in metres of the world. */
|
|
78
78
|
uniform float uRadius;
|
|
79
|
+
/**
|
|
80
|
+
* Where occlusion fades out: whole up to x metres from the eye, gone y metres past it, and no fade
|
|
81
|
+
* where x is below zero. See occlusionFade.ts.
|
|
82
|
+
*/
|
|
83
|
+
uniform vec2 uFade;
|
|
79
84
|
|
|
80
85
|
out float fragColor;
|
|
81
86
|
|
|
@@ -168,6 +173,12 @@ void main() {
|
|
|
168
173
|
fragColor = 1.0;
|
|
169
174
|
return;
|
|
170
175
|
}
|
|
176
|
+
/* How far past the caller's fade this pixel is, and open with no walk where it is all the way. */
|
|
177
|
+
float faded = uFade.x < 0.0 ? 0.0 : clamp((length(p) - uFade.x) / max(uFade.y, 1e-4), 0.0, 1.0);
|
|
178
|
+
if (faded >= 1.0) {
|
|
179
|
+
fragColor = AO_STORE;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
171
182
|
|
|
172
183
|
/*
|
|
173
184
|
* At a silhouette, the quad derivative above is replaced by the one-sided difference that
|
|
@@ -308,7 +319,7 @@ void main() {
|
|
|
308
319
|
visible += projectedLength * gtaoArc(h0, h1, normalAngle);
|
|
309
320
|
}
|
|
310
321
|
|
|
311
|
-
fragColor = clamp(visible / float(AO_SLICES) * AO_STORE, 0.0, 1.0);
|
|
322
|
+
fragColor = clamp(mix(visible / float(AO_SLICES), 1.0, faded) * AO_STORE, 0.0, 1.0);
|
|
312
323
|
}
|
|
313
324
|
`;
|
|
314
325
|
|
|
@@ -36,13 +36,56 @@ export const DIRECTIONALSHADOW_GLSL = `vec2 receiverPlaneDepthGradient(vec3 p) {
|
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
/* One stored depth against a receiver, in a map spanning \`span\` metres of depth. */
|
|
40
|
+
float directionalVisibilityAt(float receiverDepth, float compareDepth, float storedDepth, float span) {
|
|
40
41
|
if (compareDepth <= storedDepth) return 1.0;
|
|
41
|
-
float rayDistance = max(receiverDepth - storedDepth, 0.0) *
|
|
42
|
+
float rayDistance = max(receiverDepth - storedDepth, 0.0) * span;
|
|
42
43
|
float groundDistance = rayDistance * length(uDirectionalDir.xz);
|
|
43
44
|
return 1.0 - shadowReach(groundDistance, uShadowMaxDistance);
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
float directionalVisibility(float receiverDepth, float compareDepth, float storedDepth) {
|
|
48
|
+
return directionalVisibilityAt(receiverDepth, compareDepth, storedDepth, uShadowDepthSpan);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* How far inside its square a receiver stands in a layer's map: 1 well inside, 0 at its border. */
|
|
52
|
+
float sunMapFade(vec3 p) {
|
|
53
|
+
if (p.z > 1.0 || p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0) return 0.0;
|
|
54
|
+
vec2 fromCentre = abs(p.xy - 0.5) * 2.0;
|
|
55
|
+
return (1.0 - smoothstep(0.72, 0.98, max(fromCentre.x, fromCentre.y))) *
|
|
56
|
+
(1.0 - smoothstep(0.90, 1.0, p.z));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
* The moving layer at its own place, where it has a matrix of its own (movingSun.ts): the coarse
|
|
61
|
+
* test of shadowFactor, and its filter, each against the moving map's own depth span, and lit by
|
|
62
|
+
* how far inside the moving map's square the receiver stands rather than cut at its border.
|
|
63
|
+
*/
|
|
64
|
+
float movingSunCoarse(vec3 pm, float span) {
|
|
65
|
+
float inside = sunMapFade(pm);
|
|
66
|
+
if (inside <= 0.0) return 1.0;
|
|
67
|
+
float stored = textureLod(uSunShadows, vec3(pm.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
|
|
68
|
+
return mix(1.0, directionalVisibilityAt(pm.z, pm.z - 1.0 / span, stored, span), inside);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
float movingSunFiltered(vec3 pm, vec2 gradient, float span) {
|
|
72
|
+
float inside = sunMapFade(pm);
|
|
73
|
+
if (inside <= 0.0) return 1.0;
|
|
74
|
+
float texel = 1.35 / uShadowMapSize;
|
|
75
|
+
float slopeLimit = 9.1 / uShadowMapSize;
|
|
76
|
+
float lit = 0.0;
|
|
77
|
+
for (int i = 0; i < ${MAX_SHADOW_FILTER_TAPS}; i++) {
|
|
78
|
+
if (i >= uShadowFilterTaps) break;
|
|
79
|
+
vec2 sampleUv = pm.xy + DIRECTIONAL_PCF_OFFSETS[i] * texel;
|
|
80
|
+
vec2 texelCentre = (floor(sampleUv * uShadowMapSize) + 0.5) / uShadowMapSize;
|
|
81
|
+
float slopeOffset = clamp(dot(gradient, texelCentre - pm.xy), -slopeLimit, slopeLimit);
|
|
82
|
+
float tapReceiverDepth = pm.z + slopeOffset;
|
|
83
|
+
float stored = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
|
|
84
|
+
lit += directionalVisibilityAt(tapReceiverDepth, tapReceiverDepth - 0.14 / span, stored, span);
|
|
85
|
+
}
|
|
86
|
+
return mix(1.0, lit / float(max(uShadowFilterTaps, 1)), inside);
|
|
87
|
+
}
|
|
88
|
+
|
|
46
89
|
float shadowFactor(float directionalNdl) {
|
|
47
90
|
if (uShadowStrength <= 0.0) return 1.0;
|
|
48
91
|
|
|
@@ -72,6 +115,14 @@ float shadowFactor(float directionalNdl) {
|
|
|
72
115
|
vec3 p = vLightPos.xyz / vLightPos.w;
|
|
73
116
|
p = p * 0.5 + 0.5;
|
|
74
117
|
vec2 depthGradient = receiverPlaneDepthGradient(p);
|
|
118
|
+
/* The moving layer's own place and plane where it has a matrix of its own (movingSun.ts), taken
|
|
119
|
+
here beside the static layer's for the same reason: above every varying-dependent return. */
|
|
120
|
+
vec3 pm = p;
|
|
121
|
+
vec2 movingGradient = depthGradient;
|
|
122
|
+
if (MOVING_SUN) {
|
|
123
|
+
pm = movingSunAt();
|
|
124
|
+
movingGradient = receiverPlaneDepthGradient(pm);
|
|
125
|
+
}
|
|
75
126
|
|
|
76
127
|
// Near a surface's own light terminator its direct-light term is already
|
|
77
128
|
// approaching zero, while the orthographic projection of that surface
|
|
@@ -136,7 +187,8 @@ float shadowFactor(float directionalNdl) {
|
|
|
136
187
|
float coarseCompare = p.z - 1.0 / uShadowDepthSpan;
|
|
137
188
|
float coarse =
|
|
138
189
|
directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_STATIC_LAYER}.0), 0.0).r) *
|
|
139
|
-
|
|
190
|
+
(MOVING_SUN ? movingSunCoarse(pm, movingSunSpan()) :
|
|
191
|
+
directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r));
|
|
140
192
|
if (uPeeledShadowEnabled != 0) {
|
|
141
193
|
coarse *= directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_PEELED_LAYER}.0), 0.0).r);
|
|
142
194
|
}
|
|
@@ -200,7 +252,6 @@ float shadowFactor(float directionalNdl) {
|
|
|
200
252
|
* there is no mip to select and level zero is the only thing \`texture\` could have read.
|
|
201
253
|
*/
|
|
202
254
|
float staticDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_STATIC_LAYER}.0), 0.0).r;
|
|
203
|
-
float dynamicDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
|
|
204
255
|
staticLit += directionalVisibility(tapReceiverDepth, compare, staticDepth);
|
|
205
256
|
if (uPeeledShadowEnabled != 0) {
|
|
206
257
|
float peeledDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_PEELED_LAYER}.0), 0.0).r;
|
|
@@ -208,12 +259,18 @@ float shadowFactor(float directionalNdl) {
|
|
|
208
259
|
} else {
|
|
209
260
|
peeledLit += 1.0;
|
|
210
261
|
}
|
|
211
|
-
|
|
262
|
+
/* The moving layer here where it shares the static one's place; at its own below otherwise.
|
|
263
|
+
Branched on the switch itself, never its negation: see overridableConstants in transform.mjs. */
|
|
264
|
+
if (MOVING_SUN) {
|
|
265
|
+
} else {
|
|
266
|
+
float dynamicDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
|
|
267
|
+
dynamicLit += directionalVisibility(tapReceiverDepth, compare, dynamicDepth);
|
|
268
|
+
}
|
|
212
269
|
}
|
|
213
270
|
float tapCount = float(max(uShadowFilterTaps, 1));
|
|
214
271
|
staticLit /= tapCount;
|
|
215
272
|
peeledLit /= tapCount;
|
|
216
|
-
dynamicLit
|
|
273
|
+
dynamicLit = MOVING_SUN ? movingSunFiltered(pm, movingGradient, movingSunSpan()) : dynamicLit / tapCount;
|
|
217
274
|
|
|
218
275
|
// Independent layers preserve every recorded fact at an overlap: each
|
|
219
276
|
// caster keeps its own distance fade, and their transmissions compose
|
|
@@ -234,11 +291,11 @@ float shadowFactor(float directionalNdl) {
|
|
|
234
291
|
* behind the receiver lending it colour within the spread, which only a receiver between two panes
|
|
235
292
|
* meets. One function for both layers, because two copies were most of the lit shaders' growth.
|
|
236
293
|
*/
|
|
237
|
-
vec3 sunGlassLayer(vec3 p, float paneLayer, float tintLayer, float bias, float texel, float uvPerMetre) {
|
|
294
|
+
vec3 sunGlassLayer(vec3 p, float paneLayer, float tintLayer, float bias, float texel, float uvPerMetre, float span) {
|
|
238
295
|
float tintSize = float(textureSize(uSunGlassTints, 0).x);
|
|
239
296
|
float clarity = textureLod(uSunGlassTints, vec3(p.xy, tintLayer), 0.0).a;
|
|
240
297
|
float behind =
|
|
241
|
-
max(p.z - textureLod(uSunShadows, vec3(p.xy, paneLayer), 0.0).r, 0.0) *
|
|
298
|
+
max(p.z - textureLod(uSunShadows, vec3(p.xy, paneLayer), 0.0).r, 0.0) * span;
|
|
242
299
|
float radius = clamp(
|
|
243
300
|
(1.0 - clarity) * ${FROST_SPREAD.toFixed(6)} * behind * uvPerMetre,
|
|
244
301
|
texel,
|
|
@@ -278,6 +335,13 @@ vec3 sunGlassLookup(float directionalNdl) {
|
|
|
278
335
|
/* Map units per metre along the receiver, from derivatives taken first in uniform control flow,
|
|
279
336
|
so a frost spread stated in metres lands the same whatever size of light matrix a consumer built. */
|
|
280
337
|
float uvPerMetre = length(dFdx(p.xy)) / max(length(dFdx(vWorldPos)), 1e-6);
|
|
338
|
+
/* The moving glass at its own place where its layer has a matrix of its own: movingSun.ts. */
|
|
339
|
+
vec3 pm = p;
|
|
340
|
+
float movingPerMetre = uvPerMetre;
|
|
341
|
+
if (MOVING_SUN) {
|
|
342
|
+
pm = movingSunAt();
|
|
343
|
+
movingPerMetre = length(dFdx(pm.xy)) / max(length(dFdx(vWorldPos)), 1e-6);
|
|
344
|
+
}
|
|
281
345
|
if (uShadowStrength <= 0.0 || textureSize(uSunGlassTints, 0).x <= 1) return vec3(1.0);
|
|
282
346
|
if (directionalNdl <= 0.0) return vec3(1.0);
|
|
283
347
|
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);
|
|
@@ -295,8 +359,18 @@ vec3 sunGlassLookup(float directionalNdl) {
|
|
|
295
359
|
|
|
296
360
|
float bias = 0.14 / uShadowDepthSpan;
|
|
297
361
|
float texel = 1.35 / uShadowMapSize;
|
|
298
|
-
vec3 staticTint = sunGlassLayer(p, ${SUN_GLASS_STATIC_LAYER}.0, 0.0, bias, texel, uvPerMetre);
|
|
299
|
-
vec3 movingTint =
|
|
362
|
+
vec3 staticTint = sunGlassLayer(p, ${SUN_GLASS_STATIC_LAYER}.0, 0.0, bias, texel, uvPerMetre, uShadowDepthSpan);
|
|
363
|
+
vec3 movingTint = MOVING_SUN && sunMapFade(pm) <= 0.0
|
|
364
|
+
? vec3(1.0)
|
|
365
|
+
: sunGlassLayer(
|
|
366
|
+
pm,
|
|
367
|
+
${SUN_GLASS_MOVING_LAYER}.0,
|
|
368
|
+
1.0,
|
|
369
|
+
MOVING_SUN ? 0.14 / movingSunSpan() : bias,
|
|
370
|
+
texel,
|
|
371
|
+
movingPerMetre,
|
|
372
|
+
MOVING_SUN ? movingSunSpan() : uShadowDepthSpan
|
|
373
|
+
);
|
|
300
374
|
return mix(vec3(1.0), staticTint * movingTint, strength);
|
|
301
375
|
}
|
|
302
376
|
|
|
@@ -36,7 +36,10 @@ export type LitSwitch =
|
|
|
36
36
|
| 'DRIFT_LIGHT'
|
|
37
37
|
| 'PHYSICAL_SPECULAR'
|
|
38
38
|
| 'SURFACE_OVERLAY'
|
|
39
|
-
| 'REFLECTION_MAPS'
|
|
39
|
+
| 'REFLECTION_MAPS'
|
|
40
|
+
| 'WORLD_UVS'
|
|
41
|
+
| 'MOVING_SUN'
|
|
42
|
+
| 'LAYERED';
|
|
40
43
|
|
|
41
44
|
/** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
|
|
42
45
|
export function noLitSwitches(): Record<LitSwitch, boolean> {
|
|
@@ -49,6 +52,9 @@ export function noLitSwitches(): Record<LitSwitch, boolean> {
|
|
|
49
52
|
PHYSICAL_SPECULAR: false,
|
|
50
53
|
SURFACE_OVERLAY: false,
|
|
51
54
|
REFLECTION_MAPS: false,
|
|
55
|
+
WORLD_UVS: false,
|
|
56
|
+
MOVING_SUN: false,
|
|
57
|
+
LAYERED: false,
|
|
52
58
|
};
|
|
53
59
|
}
|
|
54
60
|
|