@driftengine/core 4.4.2 → 4.5.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 +46 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +79 -8
- package/dist/render/backend/webgl2/renderer.js +360 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +2 -12
- package/dist/render/backend/webgpu/renderer.d.ts +171 -54
- package/dist/render/backend/webgpu/renderer.js +944 -390
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +7 -1
- package/dist/render/sceneTarget.js +58 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +401 -108
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/renderer.ts +1108 -421
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +58 -11
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/translucentQueue.ts +20 -1
|
@@ -36,6 +36,19 @@ export { FACE_COUNT, POINT_SHADOW_NEAR };
|
|
|
36
36
|
|
|
37
37
|
const scratchViewProj = mat4.create();
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* What a bake asks of the renderer about glass, after each face's opaque depth.
|
|
41
|
+
*
|
|
42
|
+
* **Asked before it is drawn** because the answer decides whether the face binds a glass target at
|
|
43
|
+
* all: a face that offers no glass and holds none from a previous owner does no glass work.
|
|
44
|
+
*/
|
|
45
|
+
export interface PointGlassDraw {
|
|
46
|
+
/** Whether the face just drawn offered any glass. */
|
|
47
|
+
offered(): boolean;
|
|
48
|
+
/** Draw that glass: its depth, or what it lets through lit from the bake origin. */
|
|
49
|
+
draw(pass: 'depth' | 'tint', x: number, y: number, z: number): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
export class PointShadowMap {
|
|
40
53
|
readonly size: number;
|
|
41
54
|
/**
|
|
@@ -163,6 +176,8 @@ export class PointShadowMap {
|
|
|
163
176
|
near = POINT_SHADOW_NEAR,
|
|
164
177
|
sourceRadius = DEFAULT_SOURCE_RADIUS,
|
|
165
178
|
maxFaces = FACE_COUNT,
|
|
179
|
+
/** The face's glass, when glass shadows are on: `PointGlassDraw`. */
|
|
180
|
+
glass: PointGlassDraw | null = null,
|
|
166
181
|
): number {
|
|
167
182
|
/*
|
|
168
183
|
* What to render now, **and what to render it for**. A bake in flight owns the parameters
|
|
@@ -220,6 +235,7 @@ export class PointShadowMap {
|
|
|
220
235
|
* next face's pass does care about.
|
|
221
236
|
*/
|
|
222
237
|
array.resolveFace(gl, this.layer, face, plan.range, plan.near);
|
|
238
|
+
if (glass !== null) this.bakeGlassFace(gl, array, glass, face, plan);
|
|
223
239
|
}
|
|
224
240
|
|
|
225
241
|
gl.enable(gl.CULL_FACE);
|
|
@@ -237,5 +253,30 @@ export class PointShadowMap {
|
|
|
237
253
|
* needing disposal is exactly the shape of a leak nobody notices. The array is disposed by the
|
|
238
254
|
* renderer that built it.
|
|
239
255
|
*/
|
|
256
|
+
/**
|
|
257
|
+
* The face's glass, twice: where the nearest pane is, then what the panes let through.
|
|
258
|
+
*
|
|
259
|
+
* A face that offers no glass still clears a region a previous owner's glass is in — the depth to
|
|
260
|
+
* the far plane and the tint to white, from scratches cleared and not drawn into — because a pool
|
|
261
|
+
* slot changes hands with its layer.
|
|
262
|
+
*/
|
|
263
|
+
private bakeGlassFace(
|
|
264
|
+
gl: WebGL2RenderingContext,
|
|
265
|
+
array: PointShadowTarget,
|
|
266
|
+
glass: PointGlassDraw,
|
|
267
|
+
face: number,
|
|
268
|
+
plan: FaceRange,
|
|
269
|
+
): void {
|
|
270
|
+
if (!array.hasGlass) return;
|
|
271
|
+
const offered = glass.offered();
|
|
272
|
+
if (!offered && !array.heldGlass(this.layer, face)) return;
|
|
273
|
+
array.beginGlassFace(gl);
|
|
274
|
+
if (offered) glass.draw('depth', plan.x, plan.y, plan.z);
|
|
275
|
+
array.resolveGlassFace(gl, this.layer, face, plan.range, plan.near);
|
|
276
|
+
array.beginTintFace(gl);
|
|
277
|
+
if (offered) glass.draw('tint', plan.x, plan.y, plan.z);
|
|
278
|
+
array.resolveTintFace(gl, this.layer, face, offered);
|
|
279
|
+
}
|
|
280
|
+
|
|
240
281
|
dispose(_gl: WebGL2RenderingContext): void {}
|
|
241
282
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which frame a change to a dynamic mesh or an instanced batch belongs to.
|
|
3
|
+
*
|
|
4
|
+
* **A consumer may change geometry in its simulation step, before `beginFrame`.** That change is
|
|
5
|
+
* what the *next* frame draws, so it is that frame's — counting it against the frame last begun
|
|
6
|
+
* would say the geometry changed a frame early and then did not change at all, which is a motion
|
|
7
|
+
* vector on the wrong frame and none on the right one.
|
|
8
|
+
*
|
|
9
|
+
* Only the **first** change in a frame keeps the state before it: a second rewrite in the same
|
|
10
|
+
* frame must not replace "where it was last frame" with "where it was a moment ago".
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export function frameOfChange(serial: number, inFrame: boolean): number {
|
|
14
|
+
return inFrame ? serial : serial + 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function keepsPrevious(lastChange: number, frame: number): boolean {
|
|
18
|
+
return lastChange !== frame;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Slots `0 … n-1` pair with last frame's; a slot past last frame's count is new and has no past. */
|
|
22
|
+
export function pairedInstances(previousCount: number, count: number): number {
|
|
23
|
+
return Math.min(previousCount, count);
|
|
24
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two decisions about one draw in a reconstructing frame, and why they are the same set.
|
|
3
|
+
*
|
|
4
|
+
* A blended draw goes **late** — after the reconstruction, at output resolution, unjittered — and a
|
|
5
|
+
* mover's motion is **tracked**, only for draws that land in the picture being reconstructed. A
|
|
6
|
+
* planar mirror and a probe bake are other pictures drawn through the same verbs, and the overlay
|
|
7
|
+
* after `endFrame` is past the picture altogether; routing any of them late, or counting a mirrored
|
|
8
|
+
* draw as a mover's second draw of the frame, would be wrong in the way that reads as a ghost.
|
|
9
|
+
*
|
|
10
|
+
* Plain booleans rather than a state object, so the per-draw call allocates nothing.
|
|
11
|
+
*/
|
|
12
|
+
export function routesLate(
|
|
13
|
+
reconstructing: boolean,
|
|
14
|
+
mirror: boolean,
|
|
15
|
+
probe: boolean,
|
|
16
|
+
presented: boolean,
|
|
17
|
+
): boolean {
|
|
18
|
+
return reconstructing && !mirror && !probe && !presented;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The same set as `routesLate`, named for the other reader. */
|
|
22
|
+
export function tracksMotion(
|
|
23
|
+
reconstructing: boolean,
|
|
24
|
+
mirror: boolean,
|
|
25
|
+
probe: boolean,
|
|
26
|
+
presented: boolean,
|
|
27
|
+
): boolean {
|
|
28
|
+
return routesLate(reconstructing, mirror, probe, presented);
|
|
29
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which object a draw is, so a reconstruction can find where that object was last frame.
|
|
3
|
+
*
|
|
4
|
+
* **The engine cannot infer this and does not try.** `drawMesh` is immediate: a mesh drawn three
|
|
5
|
+
* times is three objects, and nothing in the call says which of last frame's three each one is. A
|
|
6
|
+
* consumer that makes one `Mover` per moving object and passes it with every draw of that object
|
|
7
|
+
* says exactly that, and the renderer keeps the rest — last frame's model and, for a skinned
|
|
8
|
+
* draw, last frame's palette.
|
|
9
|
+
*
|
|
10
|
+
* **Plain data, advanced by the renderer.** No renderer is needed to make one, so a consumer holds
|
|
11
|
+
* it beside the object it belongs to. Only a reconstructing WebGPU renderer reads or writes it; it
|
|
12
|
+
* costs nothing anywhere else. The fields past `kind` are the renderer's and a caller leaves them
|
|
13
|
+
* alone.
|
|
14
|
+
*
|
|
15
|
+
* **What would make this wrong** is a consumer whose objects are rebuilt every frame with no state
|
|
16
|
+
* of their own to hold a mover in; the identity then has to live in whatever *does* persist.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const MOVER_KIND = 'drift.mover' as const;
|
|
20
|
+
|
|
21
|
+
export interface Mover {
|
|
22
|
+
readonly kind: typeof MOVER_KIND;
|
|
23
|
+
/** The renderer that last drew it, from `claimRendererId`; -1 before its first draw. */
|
|
24
|
+
renderer: number;
|
|
25
|
+
/** That renderer's frame serial at the last draw. */
|
|
26
|
+
frame: number;
|
|
27
|
+
readonly model: Float32Array;
|
|
28
|
+
readonly previousModel: Float32Array;
|
|
29
|
+
/**
|
|
30
|
+
* This frame's palette, exactly `paletteLength` floats long once a skinned draw has set it.
|
|
31
|
+
* Swapped with the previous one each frame and reallocated only when the rig changes.
|
|
32
|
+
*/
|
|
33
|
+
palette: Float32Array;
|
|
34
|
+
previousPalette: Float32Array;
|
|
35
|
+
paletteLength: number;
|
|
36
|
+
previousPaletteLength: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Nothing to reproject from: first draw, a missed frame, another renderer, or a changed rig. */
|
|
40
|
+
export const MOVER_NONE = 0;
|
|
41
|
+
/** Drawn on the frame before by this renderer: `previousModel` and `previousPalette` are real. */
|
|
42
|
+
export const MOVER_MOTION = 1;
|
|
43
|
+
/** Drawn already this frame. The second draw is a different object and the caller's error. */
|
|
44
|
+
export const MOVER_TWICE = 2;
|
|
45
|
+
export type MoverVerdict = typeof MOVER_NONE | typeof MOVER_MOTION | typeof MOVER_TWICE;
|
|
46
|
+
|
|
47
|
+
let nextRendererId = 1;
|
|
48
|
+
|
|
49
|
+
/** A number no other renderer in this page holds, so a mover can tell renderers apart. */
|
|
50
|
+
export function claimRendererId(): number {
|
|
51
|
+
const id = nextRendererId;
|
|
52
|
+
nextRendererId += 1;
|
|
53
|
+
return id;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createMover(): Mover {
|
|
57
|
+
return {
|
|
58
|
+
kind: MOVER_KIND,
|
|
59
|
+
renderer: -1,
|
|
60
|
+
frame: -2,
|
|
61
|
+
model: new Float32Array(16),
|
|
62
|
+
previousModel: new Float32Array(16),
|
|
63
|
+
palette: new Float32Array(0),
|
|
64
|
+
previousPalette: new Float32Array(0),
|
|
65
|
+
paletteLength: 0,
|
|
66
|
+
previousPaletteLength: 0,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isMover(value: unknown): value is Mover {
|
|
71
|
+
return (
|
|
72
|
+
typeof value === 'object' && value !== null && (value as { kind?: unknown }).kind === MOVER_KIND
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Advance a mover to this draw, and say whether last frame's state is usable.
|
|
78
|
+
*
|
|
79
|
+
* **A second draw in one frame changes nothing**, so the first draw's motion stands; the caller
|
|
80
|
+
* warns. Otherwise what the mover held becomes its previous and this draw becomes its current —
|
|
81
|
+
* the palettes by swapping two arrays, so a steady rig allocates nothing after its first frame.
|
|
82
|
+
*
|
|
83
|
+
* **A changed joint count is no motion**: skinning a vertex with a palette of a different rig is
|
|
84
|
+
* not an old pose of this one.
|
|
85
|
+
*/
|
|
86
|
+
export function advanceMover(
|
|
87
|
+
mover: Mover,
|
|
88
|
+
renderer: number,
|
|
89
|
+
frame: number,
|
|
90
|
+
model: ArrayLike<number>,
|
|
91
|
+
palette: Float32Array | null,
|
|
92
|
+
): MoverVerdict {
|
|
93
|
+
if (mover.renderer === renderer && mover.frame === frame) return MOVER_TWICE;
|
|
94
|
+
const drawnLast = mover.renderer === renderer && mover.frame === frame - 1;
|
|
95
|
+
|
|
96
|
+
mover.previousModel.set(mover.model);
|
|
97
|
+
const spare = mover.previousPalette;
|
|
98
|
+
mover.previousPalette = mover.palette;
|
|
99
|
+
mover.palette = spare;
|
|
100
|
+
mover.previousPaletteLength = mover.paletteLength;
|
|
101
|
+
|
|
102
|
+
mover.model.set(model);
|
|
103
|
+
if (palette === null) {
|
|
104
|
+
mover.paletteLength = 0;
|
|
105
|
+
} else {
|
|
106
|
+
/* Exactly the rig's length, because the array is uploaded as it stands; reallocated only when
|
|
107
|
+
the rig changes, which a steady character never does. */
|
|
108
|
+
if (mover.palette.length !== palette.length) mover.palette = new Float32Array(palette.length);
|
|
109
|
+
mover.palette.set(palette);
|
|
110
|
+
mover.paletteLength = palette.length;
|
|
111
|
+
}
|
|
112
|
+
mover.renderer = renderer;
|
|
113
|
+
mover.frame = frame;
|
|
114
|
+
|
|
115
|
+
if (!drawnLast) return MOVER_NONE;
|
|
116
|
+
if (mover.paletteLength !== mover.previousPaletteLength) return MOVER_NONE;
|
|
117
|
+
return MOVER_MOTION;
|
|
118
|
+
}
|
|
@@ -37,6 +37,9 @@ export type ShadowFilterTaps = 4 | 8 | 12;
|
|
|
37
37
|
export type DirectionalShadowDepthLayers = 1 | 2;
|
|
38
38
|
export type WaterReflectionFilterTaps = 1 | 5 | 9;
|
|
39
39
|
|
|
40
|
+
/** How light through glass is coloured: see `RenderQuality.glassShadows`. */
|
|
41
|
+
export type GlassShadows = 'full' | 'half' | 'off';
|
|
42
|
+
|
|
40
43
|
export interface RenderQuality {
|
|
41
44
|
/**
|
|
42
45
|
* The transfer curve applied at the end of every shaded pass.
|
|
@@ -149,6 +152,25 @@ export interface RenderQuality {
|
|
|
149
152
|
readonly lightVolumeSamples: number;
|
|
150
153
|
/** Independently composited static sun depths; two preserves common overlaps. */
|
|
151
154
|
readonly directionalShadowDepthLayers: DirectionalShadowDepthLayers;
|
|
155
|
+
/**
|
|
156
|
+
* Whether light through glass takes the glass's colour, and at what size.
|
|
157
|
+
*
|
|
158
|
+
* **`'full'`, the default**: every shadow a glass caster reaches gains a layer holding the depth of
|
|
159
|
+
* the nearest pane and a tint layer holding what the panes let through, at the size of the depth
|
|
160
|
+
* map beside it — 8 bytes a texel, so 32 MB for a 2048 sun map and 8 MB for a 1024 lamp layer.
|
|
161
|
+
* **`'half'`** halves the tint layers only (the glass depth must match its array): 20 MB and 5 MB,
|
|
162
|
+
* and a coloured patch softer than an opaque shadow's edge. **`'off'`** is glass before this
|
|
163
|
+
* existed: a pane casts nothing at all.
|
|
164
|
+
*
|
|
165
|
+
* Nothing is allocated until a glass caster first reaches a shadow, so a scene with no glass pays
|
|
166
|
+
* no memory and no pass whatever this says. **The lit shader is the exception**: `'full'` and
|
|
167
|
+
* `'half'` compile the glass lookup into it, and a lookup that finds no glass and returns at its
|
|
168
|
+
* first line still costs the lit pass its registers — measured on the courtyard at night, 0.27 ms
|
|
169
|
+
* at 720p on WebGPU, 0.42 ms on WebGL2 and 1.2 ms at 4K. `'off'` compiles none of it, so a world
|
|
170
|
+
* that never casts through glass is that much faster for saying so. What would make `'full'` wrong
|
|
171
|
+
* is a phone budget with a lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
|
|
172
|
+
*/
|
|
173
|
+
readonly glassShadows: GlassShadows;
|
|
152
174
|
/** Horizontal world-space reach over which a directional shadow dissolves. */
|
|
153
175
|
readonly directionalShadowMaxDistance: number;
|
|
154
176
|
/** Maximum horizontal projection per vertical metre before low-angle shadows vanish. */
|
|
@@ -556,6 +578,20 @@ export interface RenderQuality {
|
|
|
556
578
|
* the render is a quarter of the output and no reconstruction holds an edge through that. A
|
|
557
579
|
* negative number is off, not a clamp to the bottom: it cannot mean "a little".
|
|
558
580
|
*
|
|
581
|
+
* **What it needs from you.** A `createMover()` per moving object, passed with that object's
|
|
582
|
+
* draws where `drawMesh` takes last frame's matrix — the renderer keeps last frame's model and,
|
|
583
|
+
* for a skinned draw, last frame's pose. Instanced batches and meshes rewritten with
|
|
584
|
+
* `updateMesh` need nothing: the batch and the mesh are their own identity. And `cameraCut()` at
|
|
585
|
+
* every cut, because nothing detects one: without it a cut blends the previous shot into the
|
|
586
|
+
* next. Blended draws — translucent meshes, particles, plumes, film, bolts, lines, world text —
|
|
587
|
+
* are drawn after the upscale at the output size and need nothing either; light volumes stay in
|
|
588
|
+
* the reconstructed picture, being a march that keeps its history.
|
|
589
|
+
*
|
|
590
|
+
* **What it cannot give back** is the resolution it draws at: an edge that never moves is softer
|
|
591
|
+
* than a native frame's at any ratio, because two thirds of the pixels at 1.5 are a guess made
|
|
592
|
+
* sharp. Measured on `demo/dev/ghost.html` against a native frame at four samples, still edges
|
|
593
|
+
* err by about 8 levels where a native frame at one sample errs by 3.
|
|
594
|
+
*
|
|
559
595
|
* `?recon=` is how it gets A/B'd in one reload.
|
|
560
596
|
*/
|
|
561
597
|
readonly reconstruction: number;
|
|
@@ -838,6 +874,7 @@ export const DEFAULT_RENDER_QUALITY: Readonly<RenderQuality> = Object.freeze({
|
|
|
838
874
|
shadowFilterTaps: MAX_SHADOW_FILTER_TAPS,
|
|
839
875
|
lightVolumeSamples: 32,
|
|
840
876
|
directionalShadowDepthLayers: 2,
|
|
877
|
+
glassShadows: 'full',
|
|
841
878
|
directionalShadowMaxDistance: 6,
|
|
842
879
|
directionalShadowMaxSlope: 3,
|
|
843
880
|
pointShadowRebakeDistance: 0.001,
|
|
@@ -998,6 +1035,7 @@ export function resolveRenderQuality(options: RenderQualityOptions = {}): Readon
|
|
|
998
1035
|
lightVolumeSamples: options.lightVolumeSamples ?? DEFAULT_RENDER_QUALITY.lightVolumeSamples,
|
|
999
1036
|
directionalShadowDepthLayers:
|
|
1000
1037
|
options.directionalShadowDepthLayers ?? DEFAULT_RENDER_QUALITY.directionalShadowDepthLayers,
|
|
1038
|
+
glassShadows: options.glassShadows ?? DEFAULT_RENDER_QUALITY.glassShadows,
|
|
1001
1039
|
directionalShadowMaxDistance:
|
|
1002
1040
|
options.directionalShadowMaxDistance ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxDistance,
|
|
1003
1041
|
directionalShadowMaxSlope:
|
|
@@ -1128,6 +1166,15 @@ export function resolveRenderQuality(options: RenderQualityOptions = {}): Readon
|
|
|
1128
1166
|
`RenderQuality.directionalShadowDepthLayers must be 1 or 2, got ${quality.directionalShadowDepthLayers}`,
|
|
1129
1167
|
);
|
|
1130
1168
|
}
|
|
1169
|
+
if (
|
|
1170
|
+
quality.glassShadows !== 'full' &&
|
|
1171
|
+
quality.glassShadows !== 'half' &&
|
|
1172
|
+
quality.glassShadows !== 'off'
|
|
1173
|
+
) {
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
`RenderQuality.glassShadows must be 'full', 'half' or 'off', got ${String(quality.glassShadows)}`,
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1131
1178
|
if (quality.plumeNoiseOctaves > 3) {
|
|
1132
1179
|
throw new Error(
|
|
1133
1180
|
`RenderQuality.plumeNoiseOctaves must be <= 3, got ${quality.plumeNoiseOctaves}`,
|
|
@@ -129,6 +129,18 @@ export function recordingGl(
|
|
|
129
129
|
COLOR_BUFFER_BIT: 0x00004000,
|
|
130
130
|
/* Named so a test can tell a mesh that declared itself deforming from one that did not. */
|
|
131
131
|
ARRAY_BUFFER: 0x8892,
|
|
132
|
+
/* Named so a blend a pass sets can be read back as numbers: glass multiplies what it lets
|
|
133
|
+
through, and a test that cannot tell DST_COLOR from a recorder cannot tell it multiplies. */
|
|
134
|
+
ZERO: 0,
|
|
135
|
+
DST_ALPHA: 0x0304,
|
|
136
|
+
DST_COLOR: 0x0306,
|
|
137
|
+
FUNC_ADD: 0x8006,
|
|
138
|
+
COLOR: 0x1800,
|
|
139
|
+
DEPTH: 0x1801,
|
|
140
|
+
/* Named so a copy can be read as from-and-to: a blit's two framebuffers are the whole claim. */
|
|
141
|
+
READ_FRAMEBUFFER: 0x8ca8,
|
|
142
|
+
DRAW_FRAMEBUFFER: 0x8ca9,
|
|
143
|
+
BLEND: 0x0be2,
|
|
132
144
|
STATIC_DRAW: 0x88e4,
|
|
133
145
|
DYNAMIC_DRAW: 0x88e8,
|
|
134
146
|
};
|
|
@@ -82,6 +82,8 @@ export class SceneTarget {
|
|
|
82
82
|
private colorCopyFramebuffer: WebGLFramebuffer | null = null;
|
|
83
83
|
/** Whether this frame has filled it. One snapshot serves every refracting draw in a frame. */
|
|
84
84
|
private colorCopyFilled = false;
|
|
85
|
+
/** Whether this frame's copy has its mip chain, which frosted glass reads. See `snapshotColor`. */
|
|
86
|
+
private colorCopyMips = false;
|
|
85
87
|
/**
|
|
86
88
|
* The colour grade, as a 3D texture on unit 4, and the table it was built from.
|
|
87
89
|
*
|
|
@@ -275,12 +277,24 @@ export class SceneTarget {
|
|
|
275
277
|
*/
|
|
276
278
|
if (this.colorCopy === null) this.colorCopy = gl.createTexture();
|
|
277
279
|
gl.bindTexture(gl.TEXTURE_2D, this.colorCopy);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
/*
|
|
281
|
+
* **Every level, so glass can read a blurrier one.** Frosted glass reads the copy at a level its
|
|
282
|
+
* frost picks, and a texture sampled through a mipmapping filter with a level missing is
|
|
283
|
+
* incomplete and reads black — so the chain is allocated here, once per size, and filled only on
|
|
284
|
+
* a frame that draws glass (`snapshotColor`). Plain refraction reads level 0, as it always has.
|
|
285
|
+
*/
|
|
286
|
+
const levels = Math.floor(Math.log2(Math.max(width, height))) + 1;
|
|
287
|
+
for (let level = 0; level < levels; level += 1) {
|
|
288
|
+
const w = Math.max(1, width >> level);
|
|
289
|
+
const h = Math.max(1, height >> level);
|
|
290
|
+
if (this.floatColor) {
|
|
291
|
+
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA16F, w, h, 0, gl.RGBA, gl.HALF_FLOAT, null);
|
|
292
|
+
} else {
|
|
293
|
+
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
294
|
+
}
|
|
282
295
|
}
|
|
283
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.
|
|
296
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, levels - 1);
|
|
297
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
284
298
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
285
299
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
286
300
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
@@ -419,6 +433,7 @@ export class SceneTarget {
|
|
|
419
433
|
/* Last frame's snapshot is last frame's scene. See `snapshotDepth`. */
|
|
420
434
|
this.depthCopyFilled = false;
|
|
421
435
|
this.colorCopyFilled = false;
|
|
436
|
+
this.colorCopyMips = false;
|
|
422
437
|
this.bind();
|
|
423
438
|
}
|
|
424
439
|
|
|
@@ -516,12 +531,21 @@ export class SceneTarget {
|
|
|
516
531
|
* Null where the copy could not be made, and a caller must read that as "do not refract" rather
|
|
517
532
|
* than as a black texture — refracting against black paints every pane the colour of a hole.
|
|
518
533
|
*/
|
|
519
|
-
snapshotColor(
|
|
534
|
+
snapshotColor(
|
|
535
|
+
afresh = false,
|
|
536
|
+
/** Fill the copy's mip chain as well, once a frame: what frosted glass reads. */
|
|
537
|
+
mips = false,
|
|
538
|
+
): WebGLTexture | null {
|
|
520
539
|
if (this.colorCopy === null || this.colorCopyFramebuffer === null) return null;
|
|
521
|
-
if (this.colorCopyFilled && !afresh)
|
|
540
|
+
if (this.colorCopyFilled && !afresh) {
|
|
541
|
+
if (mips) this.fillColorCopyMips();
|
|
542
|
+
return this.colorCopy;
|
|
543
|
+
}
|
|
522
544
|
const { gl } = this;
|
|
523
545
|
const source = this.msaaFramebuffer ?? this.framebuffer;
|
|
524
546
|
if (source === null) return null;
|
|
547
|
+
const drawing = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING) as WebGLFramebuffer | null;
|
|
548
|
+
const reading = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING) as WebGLFramebuffer | null;
|
|
525
549
|
|
|
526
550
|
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, source);
|
|
527
551
|
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.colorCopyFramebuffer);
|
|
@@ -546,13 +570,33 @@ export class SceneTarget {
|
|
|
546
570
|
gl.NEAREST,
|
|
547
571
|
);
|
|
548
572
|
const refused = gl.getError() !== gl.NO_ERROR;
|
|
549
|
-
/*
|
|
550
|
-
|
|
573
|
+
/*
|
|
574
|
+
* **Back to what the caller was drawing into, whichever that was** — read before the copy and
|
|
575
|
+
* handed back, never assumed. It was the scene's framebuffer, assumed, and a pane drawn into a
|
|
576
|
+
* reflection probe's face then left the scene's bound: the probe attached its next face to it,
|
|
577
|
+
* a face-sized colour beside a screen-sized depth, and every draw after refused as incomplete.
|
|
578
|
+
*/
|
|
579
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, drawing);
|
|
580
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, reading);
|
|
551
581
|
if (refused) return null;
|
|
552
582
|
this.colorCopyFilled = true;
|
|
583
|
+
this.colorCopyMips = false;
|
|
584
|
+
if (mips) this.fillColorCopyMips();
|
|
553
585
|
return this.colorCopy;
|
|
554
586
|
}
|
|
555
587
|
|
|
588
|
+
/** The copy's mip chain from its level 0, once per copy. */
|
|
589
|
+
private fillColorCopyMips(): void {
|
|
590
|
+
if (this.colorCopyMips || this.colorCopy === null) return;
|
|
591
|
+
const { gl } = this;
|
|
592
|
+
/* Bound on whichever unit is active and handed back, so the caller's texture there survives. */
|
|
593
|
+
const previous = gl.getParameter(gl.TEXTURE_BINDING_2D) as WebGLTexture | null;
|
|
594
|
+
gl.bindTexture(gl.TEXTURE_2D, this.colorCopy);
|
|
595
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
596
|
+
gl.bindTexture(gl.TEXTURE_2D, previous);
|
|
597
|
+
this.colorCopyMips = true;
|
|
598
|
+
}
|
|
599
|
+
|
|
556
600
|
snapshotDepth(
|
|
557
601
|
/**
|
|
558
602
|
* Take the copy again even though one has already been taken this frame.
|
|
@@ -573,6 +617,8 @@ export class SceneTarget {
|
|
|
573
617
|
const { gl } = this;
|
|
574
618
|
const source = this.msaaFramebuffer ?? this.framebuffer;
|
|
575
619
|
if (source === null) return null;
|
|
620
|
+
const drawing = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING) as WebGLFramebuffer | null;
|
|
621
|
+
const reading = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING) as WebGLFramebuffer | null;
|
|
576
622
|
|
|
577
623
|
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, source);
|
|
578
624
|
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.depthCopyFramebuffer);
|
|
@@ -595,8 +641,9 @@ export class SceneTarget {
|
|
|
595
641
|
gl.NEAREST,
|
|
596
642
|
);
|
|
597
643
|
const refused = gl.getError() !== gl.NO_ERROR;
|
|
598
|
-
/* Back to what the caller was drawing into,
|
|
599
|
-
gl.bindFramebuffer(gl.
|
|
644
|
+
/* Back to what the caller was drawing into, read rather than assumed: see `snapshotColor`. */
|
|
645
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, drawing);
|
|
646
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, reading);
|
|
600
647
|
if (refused) return null;
|
|
601
648
|
this.depthCopyFilled = true;
|
|
602
649
|
return this.depthCopy;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveConditionals } from './conditionals.ts';
|
|
2
|
+
import { SUN_STATIC_LAYER } from '../shadowMap.ts';
|
|
2
3
|
import { CUTOUT_COVERAGE_GLSL } from './cutoutCoverage.ts';
|
|
3
4
|
import { SKINNING_GLSL } from './skinning.ts';
|
|
4
5
|
import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from './vertexChannel.ts';
|
|
@@ -73,6 +74,11 @@ out vec4 vLightPosition;
|
|
|
73
74
|
out vec2 vUv;
|
|
74
75
|
flat out float vAlphaCutout;
|
|
75
76
|
#endif
|
|
77
|
+
#if GLASS
|
|
78
|
+
/* Where on the pane this is, for the tint's own normal and its angle to the light. Last, so the
|
|
79
|
+
outputs before it keep the locations the plain variants give them. */
|
|
80
|
+
out vec3 vGlassWorld;
|
|
81
|
+
#endif
|
|
76
82
|
|
|
77
83
|
void main() {
|
|
78
84
|
#if SKINNED
|
|
@@ -100,20 +106,23 @@ void main() {
|
|
|
100
106
|
vUv = aUv * uUvScale;
|
|
101
107
|
vAlphaCutout = uAlphaCutout;
|
|
102
108
|
#endif
|
|
109
|
+
#if GLASS
|
|
110
|
+
vGlassWorld = bent;
|
|
111
|
+
#endif
|
|
103
112
|
}
|
|
104
113
|
`;
|
|
105
114
|
|
|
106
115
|
/** The rigid variant: the world, a prop, a mesh with no rig behind it. */
|
|
107
116
|
export const DEPTH_VERT = resolveConditionals(
|
|
108
117
|
DEPTH_VERT_SOURCE,
|
|
109
|
-
{ SKINNED: false, INSTANCED: false, CUTOUT: false },
|
|
118
|
+
{ SKINNED: false, INSTANCED: false, CUTOUT: false, GLASS: false },
|
|
110
119
|
'depth',
|
|
111
120
|
);
|
|
112
121
|
|
|
113
122
|
/** The skinned variant, which reads a joint palette and moves the vertex by it. */
|
|
114
123
|
export const DEPTH_SKINNED_VERT = resolveConditionals(
|
|
115
124
|
DEPTH_VERT_SOURCE,
|
|
116
|
-
{ SKINNED: true, INSTANCED: false, CUTOUT: false },
|
|
125
|
+
{ SKINNED: true, INSTANCED: false, CUTOUT: false, GLASS: false },
|
|
117
126
|
'depth-skinned',
|
|
118
127
|
);
|
|
119
128
|
|
|
@@ -127,7 +136,7 @@ export const DEPTH_SKINNED_VERT = resolveConditionals(
|
|
|
127
136
|
*/
|
|
128
137
|
export const DEPTH_INSTANCED_VERT = resolveConditionals(
|
|
129
138
|
DEPTH_VERT_SOURCE,
|
|
130
|
-
{ SKINNED: false, INSTANCED: true, CUTOUT: false },
|
|
139
|
+
{ SKINNED: false, INSTANCED: true, CUTOUT: false, GLASS: false },
|
|
131
140
|
'depth-instanced',
|
|
132
141
|
);
|
|
133
142
|
|
|
@@ -143,13 +152,13 @@ export const DEPTH_INSTANCED_VERT = resolveConditionals(
|
|
|
143
152
|
*/
|
|
144
153
|
export const DEPTH_CUTOUT_VERT = resolveConditionals(
|
|
145
154
|
DEPTH_VERT_SOURCE,
|
|
146
|
-
{ SKINNED: false, INSTANCED: false, CUTOUT: true },
|
|
155
|
+
{ SKINNED: false, INSTANCED: false, CUTOUT: true, GLASS: false },
|
|
147
156
|
'depth-cutout',
|
|
148
157
|
);
|
|
149
158
|
|
|
150
159
|
export const DEPTH_INSTANCED_CUTOUT_VERT = resolveConditionals(
|
|
151
160
|
DEPTH_VERT_SOURCE,
|
|
152
|
-
{ SKINNED: false, INSTANCED: true, CUTOUT: true },
|
|
161
|
+
{ SKINNED: false, INSTANCED: true, CUTOUT: true, GLASS: false },
|
|
153
162
|
'depth-instanced-cutout',
|
|
154
163
|
);
|
|
155
164
|
|
|
@@ -157,14 +166,14 @@ export const DEPTH_FRAG = `#version 300 es
|
|
|
157
166
|
precision highp float;
|
|
158
167
|
in vec4 vLightPosition;
|
|
159
168
|
|
|
160
|
-
uniform highp
|
|
169
|
+
uniform highp sampler2DArray uPreviousShadowMap;
|
|
161
170
|
uniform int uPeelShadowLayer;
|
|
162
171
|
|
|
163
172
|
void main() {
|
|
164
173
|
if (uPeelShadowLayer != 0) {
|
|
165
174
|
vec3 p = vLightPosition.xyz / vLightPosition.w;
|
|
166
175
|
vec2 uv = p.xy * 0.5 + 0.5;
|
|
167
|
-
float previousDepth = texture(uPreviousShadowMap, uv).r;
|
|
176
|
+
float previousDepth = texture(uPreviousShadowMap, vec3(uv, ${SUN_STATIC_LAYER}.0)).r;
|
|
168
177
|
// Discard the already-recorded surface and everything in front of it. The
|
|
169
178
|
// ordinary depth test then stores the next independently fading occluder.
|
|
170
179
|
if (gl_FragCoord.z <= previousDepth + 0.00001) discard;
|
|
@@ -190,7 +199,7 @@ in vec4 vLightPosition;
|
|
|
190
199
|
in vec2 vUv;
|
|
191
200
|
flat in float vAlphaCutout;
|
|
192
201
|
|
|
193
|
-
uniform highp
|
|
202
|
+
uniform highp sampler2DArray uPreviousShadowMap;
|
|
194
203
|
uniform int uPeelShadowLayer;
|
|
195
204
|
uniform highp sampler2D uCutoutMap;
|
|
196
205
|
${CUTOUT_COVERAGE_GLSL}
|
|
@@ -200,9 +209,100 @@ void main() {
|
|
|
200
209
|
if (uPeelShadowLayer != 0) {
|
|
201
210
|
vec3 p = vLightPosition.xyz / vLightPosition.w;
|
|
202
211
|
vec2 uv = p.xy * 0.5 + 0.5;
|
|
203
|
-
float previousDepth = textureLod(uPreviousShadowMap, uv, 0.0).r;
|
|
212
|
+
float previousDepth = textureLod(uPreviousShadowMap, vec3(uv, ${SUN_STATIC_LAYER}.0), 0.0).r;
|
|
204
213
|
if (gl_FragCoord.z <= previousDepth + 0.00001) discard;
|
|
205
214
|
}
|
|
206
215
|
if (alpha < vAlphaCutout) discard;
|
|
207
216
|
}
|
|
208
217
|
`;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The glass tint's vertex stages: every way a caster is drawn, with the world position handed on.
|
|
221
|
+
*
|
|
222
|
+
* **The same five as the depth pass, and for the same reason**: a pane must cast its colour from
|
|
223
|
+
* exactly where it casts its depth, deformed and placed the same way, or the coloured patch parts
|
|
224
|
+
* from the pane's own shadow edge. The only addition is `vGlassWorld`.
|
|
225
|
+
*/
|
|
226
|
+
export const GLASS_TINT_VERT = resolveConditionals(
|
|
227
|
+
DEPTH_VERT_SOURCE,
|
|
228
|
+
{ SKINNED: false, INSTANCED: false, CUTOUT: false, GLASS: true },
|
|
229
|
+
'glass-tint',
|
|
230
|
+
);
|
|
231
|
+
export const GLASS_TINT_SKINNED_VERT = resolveConditionals(
|
|
232
|
+
DEPTH_VERT_SOURCE,
|
|
233
|
+
{ SKINNED: true, INSTANCED: false, CUTOUT: false, GLASS: true },
|
|
234
|
+
'glass-tint-skinned',
|
|
235
|
+
);
|
|
236
|
+
export const GLASS_TINT_INSTANCED_VERT = resolveConditionals(
|
|
237
|
+
DEPTH_VERT_SOURCE,
|
|
238
|
+
{ SKINNED: false, INSTANCED: true, CUTOUT: false, GLASS: true },
|
|
239
|
+
'glass-tint-instanced',
|
|
240
|
+
);
|
|
241
|
+
export const GLASS_TINT_CUTOUT_VERT = resolveConditionals(
|
|
242
|
+
DEPTH_VERT_SOURCE,
|
|
243
|
+
{ SKINNED: false, INSTANCED: false, CUTOUT: true, GLASS: true },
|
|
244
|
+
'glass-tint-cutout',
|
|
245
|
+
);
|
|
246
|
+
export const GLASS_TINT_INSTANCED_CUTOUT_VERT = resolveConditionals(
|
|
247
|
+
DEPTH_VERT_SOURCE,
|
|
248
|
+
{ SKINNED: false, INSTANCED: true, CUTOUT: true, GLASS: true },
|
|
249
|
+
'glass-tint-instanced-cutout',
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* What a pane lets through: its transmission and colour, less what it reflects toward the light,
|
|
254
|
+
* and how clear it is — `glassShadow.ts`'s `paneTexel`, stated for the device. Drawn with a
|
|
255
|
+
* multiplying blend and no depth test, so every pane on a ray contributes whatever order they
|
|
256
|
+
* come in.
|
|
257
|
+
*
|
|
258
|
+
* **The pane's normal is its triangle's**, from the derivatives of its world position, first and in
|
|
259
|
+
* uniform control flow: a pane is flat, a sign does not matter to a cosine taken absolutely, and it
|
|
260
|
+
* spares the five vertex stages a normal they would carry for this alone. What it gives up is a
|
|
261
|
+
* curved pane, whose Fresnel steps per triangle; what would change it is glass that is mostly bulbs.
|
|
262
|
+
*/
|
|
263
|
+
const GLASS_TINT_BODY = /* glsl */ `
|
|
264
|
+
vec3 n = normalize(cross(dFdx(vGlassWorld), dFdy(vGlassWorld)));
|
|
265
|
+
vec3 toLight = uGlassLight.w > 0.5 ? normalize(uGlassLight.xyz - vGlassWorld) : uGlassLight.xyz;
|
|
266
|
+
float cosLight = abs(dot(n, toLight));
|
|
267
|
+
float reflected = 0.04 + 0.96 * pow(1.0 - cosLight, 5.0);
|
|
268
|
+
outTint = vec4(uGlassPane.rgb * (1.0 - reflected), uGlassPane.a);
|
|
269
|
+
`;
|
|
270
|
+
|
|
271
|
+
export const GLASS_TINT_FRAG = `#version 300 es
|
|
272
|
+
precision highp float;
|
|
273
|
+
in vec4 vLightPosition;
|
|
274
|
+
in vec3 vGlassWorld;
|
|
275
|
+
|
|
276
|
+
/* rgb: transmission times tint; a: clarity, one less the frost. */
|
|
277
|
+
uniform vec4 uGlassPane;
|
|
278
|
+
/* xyz: the light's position (w 1) or the direction toward it (w 0). */
|
|
279
|
+
uniform vec4 uGlassLight;
|
|
280
|
+
|
|
281
|
+
out vec4 outTint;
|
|
282
|
+
|
|
283
|
+
void main() {
|
|
284
|
+
${GLASS_TINT_BODY}
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
|
|
288
|
+
/** A cut-out pane: its colour lands only where its depth does, by the depth cutout's own test. */
|
|
289
|
+
export const GLASS_TINT_CUTOUT_FRAG = `#version 300 es
|
|
290
|
+
precision highp float;
|
|
291
|
+
in vec4 vLightPosition;
|
|
292
|
+
in vec2 vUv;
|
|
293
|
+
flat in float vAlphaCutout;
|
|
294
|
+
in vec3 vGlassWorld;
|
|
295
|
+
|
|
296
|
+
uniform vec4 uGlassPane;
|
|
297
|
+
uniform vec4 uGlassLight;
|
|
298
|
+
uniform highp sampler2D uCutoutMap;
|
|
299
|
+
${CUTOUT_COVERAGE_GLSL}
|
|
300
|
+
out vec4 outTint;
|
|
301
|
+
|
|
302
|
+
void main() {
|
|
303
|
+
/* First, in uniform control flow, as the depth cutout samples it. */
|
|
304
|
+
float alpha = cutoutAlpha(texture(uCutoutMap, vUv).a, vUv * vec2(textureSize(uCutoutMap, 0)));
|
|
305
|
+
${GLASS_TINT_BODY}
|
|
306
|
+
if (alpha < vAlphaCutout) discard;
|
|
307
|
+
}
|
|
308
|
+
`;
|