@driftengine/core 4.13.6 → 4.14.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 +51 -0
- package/README.md +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/render/backend/api.d.ts +19 -3
- package/dist/render/backend/webgl2/motionTarget.d.ts +39 -0
- package/dist/render/backend/webgl2/motionTarget.js +254 -0
- package/dist/render/backend/webgl2/pointSkinFeedback.d.ts +13 -3
- package/dist/render/backend/webgl2/pointSkinFeedback.js +45 -12
- package/dist/render/backend/webgl2/renderer.d.ts +101 -7
- package/dist/render/backend/webgl2/renderer.js +337 -9
- package/dist/render/backend/webgpu/pointSkinPass.d.ts +16 -3
- package/dist/render/backend/webgpu/pointSkinPass.js +79 -21
- package/dist/render/backend/webgpu/postPass.d.ts +6 -1
- package/dist/render/backend/webgpu/renderer.d.ts +41 -0
- package/dist/render/backend/webgpu/renderer.js +143 -5
- package/dist/render/baseDecals.d.ts +48 -0
- package/dist/render/baseDecals.js +71 -0
- package/dist/render/emissiveFlow.d.ts +40 -0
- package/dist/render/emissiveFlow.js +51 -0
- package/dist/render/fixtureAtlas.d.ts +8 -3
- package/dist/render/fixtureAtlas.js +17 -6
- package/dist/render/globalMedium.d.ts +17 -0
- package/dist/render/globalMedium.js +14 -0
- package/dist/render/globalMediumPass.js +3 -2
- package/dist/render/iridescentOverlay.d.ts +78 -0
- package/dist/render/iridescentOverlay.js +102 -0
- package/dist/render/mesh.d.ts +24 -0
- package/dist/render/mesh.js +79 -0
- package/dist/render/pointSkin.d.ts +18 -0
- package/dist/render/pointSkin.js +50 -5
- package/dist/render/recon/mover.d.ts +3 -2
- package/dist/render/recon/mover.js +3 -2
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shaders/flat/baseDecals.d.ts +17 -0
- package/dist/render/shaders/flat/baseDecals.js +71 -0
- package/dist/render/shaders/flat/emissiveFlow.d.ts +15 -0
- package/dist/render/shaders/flat/emissiveFlow.js +45 -0
- 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 +16 -0
- package/dist/render/shaders/flat/index.js +9 -0
- package/dist/render/shaders/flat/iridescentOverlay.d.ts +15 -0
- package/dist/render/shaders/flat/iridescentOverlay.js +65 -0
- package/dist/render/shaders/flat/layered.js +4 -4
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +12 -1
- package/dist/render/shaders/flat/mapReads.d.ts +1 -1
- package/dist/render/shaders/flat/mapReads.js +7 -3
- package/dist/render/shaders/flat/worldUv.js +6 -5
- package/dist/render/shaders/generated/flat.wgsl.d.ts +480 -16
- package/dist/render/shaders/generated/flat.wgsl.js +519 -55
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +15 -5
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +15 -5
- package/dist/render/shaders/temporalResolve.js +25 -9
- package/dist/render/surfaceTexture.d.ts +32 -0
- package/dist/render/temporalPass.d.ts +3 -1
- package/dist/render/temporalPass.js +9 -1
- package/package.json +6 -6
- package/src/index.ts +3 -0
- package/src/render/backend/api.ts +19 -2
- package/src/render/backend/webgl2/motionTarget.ts +299 -0
- package/src/render/backend/webgl2/pointSkinFeedback.ts +49 -14
- package/src/render/backend/webgl2/renderer.ts +411 -9
- package/src/render/backend/webgpu/pointSkinPass.ts +90 -23
- package/src/render/backend/webgpu/renderer.ts +163 -3
- package/src/render/baseDecals.ts +93 -0
- package/src/render/emissiveFlow.ts +66 -0
- package/src/render/fixtureAtlas.ts +26 -7
- package/src/render/globalMedium.ts +31 -0
- package/src/render/globalMediumPass.ts +3 -7
- package/src/render/iridescentOverlay.ts +177 -0
- package/src/render/mesh.ts +82 -0
- package/src/render/pointSkin.ts +64 -5
- package/src/render/recon/mover.ts +3 -2
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shaders/flat/baseDecals.ts +71 -0
- package/src/render/shaders/flat/emissiveFlow.ts +45 -0
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +25 -0
- package/src/render/shaders/flat/iridescentOverlay.ts +65 -0
- package/src/render/shaders/flat/layered.ts +4 -4
- package/src/render/shaders/flat/main.ts +12 -1
- package/src/render/shaders/flat/mapReads.ts +7 -3
- package/src/render/shaders/flat/worldUv.ts +6 -5
- package/src/render/shaders/generated/flat.wgsl.ts +519 -55
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +15 -5
- package/src/render/shaders/temporalResolve.ts +25 -9
- package/src/render/surfaceTexture.ts +32 -0
- package/src/render/temporalPass.ts +8 -0
|
@@ -20,7 +20,16 @@ import { maskOf } from '../../frame/resources.ts';
|
|
|
20
20
|
/** The two copies a registered pass may declare it reads. See `drawPass`. */
|
|
21
21
|
const COLOR_SNAPSHOT = maskOf('colorSnapshot');
|
|
22
22
|
const DEPTH_SNAPSHOT = maskOf('depthSnapshot');
|
|
23
|
-
import
|
|
23
|
+
import {
|
|
24
|
+
MOVER_MOTION,
|
|
25
|
+
MOVER_TWICE,
|
|
26
|
+
advanceMover,
|
|
27
|
+
claimRendererId,
|
|
28
|
+
isMover,
|
|
29
|
+
type Mover,
|
|
30
|
+
} from '../../recon/mover.ts';
|
|
31
|
+
import { frameOfChange, keepsPrevious } from '../../recon/changeFrames.ts';
|
|
32
|
+
import { GlMotionPass } from './motionTarget.ts';
|
|
24
33
|
import { resolveGlass } from '../../glass.ts';
|
|
25
34
|
import type { GlassOptions, ResolvedGlass } from '../../glass.ts';
|
|
26
35
|
import type { ClipControlExtension } from '../../depthConvention.ts';
|
|
@@ -132,6 +141,20 @@ import {
|
|
|
132
141
|
type SurfaceDetail,
|
|
133
142
|
} from '../../surfaceDetail.ts';
|
|
134
143
|
import { LOOK_FLOATS, asksLayerLooks, packLayerLooks } from '../../surfaceLayerLooks.ts';
|
|
144
|
+
import { EMISSIVE_FLOW_FLOATS, packEmissiveFlow } from '../../emissiveFlow.ts';
|
|
145
|
+
import { sourceSize } from '../../textureSource.ts';
|
|
146
|
+
import {
|
|
147
|
+
BASE_DECAL_FLOATS,
|
|
148
|
+
BASE_DECAL_TILE,
|
|
149
|
+
packBaseDecals,
|
|
150
|
+
type BaseDecal,
|
|
151
|
+
} from '../../baseDecals.ts';
|
|
152
|
+
import {
|
|
153
|
+
IRIDESCENT_FLOATS,
|
|
154
|
+
iridescentRefusal,
|
|
155
|
+
packIridescentOverlay,
|
|
156
|
+
type IridescentOverlay,
|
|
157
|
+
} from '../../iridescentOverlay.ts';
|
|
135
158
|
import type { SurfaceProjection } from '../../surfaceProjection.ts';
|
|
136
159
|
import { FILM_FRAG, FILM_VERT } from '../../shaders/film.ts';
|
|
137
160
|
import { SceneTarget } from '../../sceneTarget.ts';
|
|
@@ -230,7 +253,7 @@ import { ProbeSweeps } from '../../probeSweeps.ts';
|
|
|
230
253
|
import { octahedralEdgeFor } from '../../prefilterEnvMap.ts';
|
|
231
254
|
import { EnvironmentPrefilterPass } from '../../prefilterPass.ts';
|
|
232
255
|
import { packIesAtlas } from '../../iesProfile.ts';
|
|
233
|
-
import { fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.ts';
|
|
256
|
+
import { decalBandTop, fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.ts';
|
|
234
257
|
import { resolveSurfaceScene } from '../../surfaceScene.ts';
|
|
235
258
|
import type { AreaLightBuffer, AreaLightSource } from '../../areaLights.ts';
|
|
236
259
|
import { MAX_AREA_LIGHTS } from '../../areaLights.ts';
|
|
@@ -396,6 +419,7 @@ import { noLitSwitches, type LitSwitch } from '../../shaders/flat/glassTint.ts';
|
|
|
396
419
|
import {
|
|
397
420
|
poseBounds,
|
|
398
421
|
validatePointSkin,
|
|
422
|
+
validatePointSkinRange,
|
|
399
423
|
validatePose,
|
|
400
424
|
type PointSkinData,
|
|
401
425
|
} from '../../pointSkin.ts';
|
|
@@ -1745,6 +1769,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1745
1769
|
private iesAtlas = packIesAtlas([]);
|
|
1746
1770
|
/** The cookie images, kept so a new set of profiles can rebuild the atlas around them. */
|
|
1747
1771
|
private cookieImages: readonly TexImageSource[] = [];
|
|
1772
|
+
/** The images the frame's base decals lay, in the atlas's band of them. See `setBaseDecalImages`. */
|
|
1773
|
+
private decalImages: readonly TexImageSource[] = [];
|
|
1774
|
+
/** The decals as last laid, packed again whenever the atlas moves their band. */
|
|
1775
|
+
private baseDecalList: readonly BaseDecal[] = [];
|
|
1776
|
+
/** `uBaseDecals`, held until the next `setBaseDecals`, across passes. */
|
|
1777
|
+
private readonly baseDecalFloats = new Float32Array(BASE_DECAL_FLOATS);
|
|
1778
|
+
/** Whether the lit stage has room for base decals, asked once. */
|
|
1779
|
+
private decalRoom: boolean | null = null;
|
|
1780
|
+
private warnedDecalRoom = false;
|
|
1781
|
+
/** The refusals of a decal or its image already said, each once. */
|
|
1782
|
+
private readonly warnedBaseDecals = new Set<string>();
|
|
1748
1783
|
/** Rows of profile in the atlas. One until a consumer loads a profile, which is the row of ones. */
|
|
1749
1784
|
private iesRows = 1;
|
|
1750
1785
|
/** Cookie tiles in the atlas. 0 is none, and the whole off path. */
|
|
@@ -1827,6 +1862,18 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1827
1862
|
private warnedLooksRoom = false;
|
|
1828
1863
|
/** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
|
|
1829
1864
|
private readonly detailFloats = new Float32Array(DETAIL_FLOATS);
|
|
1865
|
+
/** `uEmissiveFlow`, a material's glow moved and masked, packed by `packEmissiveFlow`. */
|
|
1866
|
+
private readonly flowFloats = new Float32Array(EMISSIVE_FLOW_FLOATS);
|
|
1867
|
+
/** Whether the lit stage has room for that, asked once. See `emissiveFlowFits`. */
|
|
1868
|
+
private flowRoom: boolean | null = null;
|
|
1869
|
+
private warnedFlowRoom = false;
|
|
1870
|
+
/** `uIridescence`, a material's iridescent overlay packed by `packIridescentOverlay`. */
|
|
1871
|
+
private readonly iridescentFloats = new Float32Array(IRIDESCENT_FLOATS);
|
|
1872
|
+
/** Whether the lit stage has room for it, asked once. See `iridescentOf`. */
|
|
1873
|
+
private iridescentRoom: boolean | null = null;
|
|
1874
|
+
private warnedIridescentRoom = false;
|
|
1875
|
+
/** The refusals of an overlay already said, each once. */
|
|
1876
|
+
private readonly warnedIridescent = new Set<string>();
|
|
1830
1877
|
/** Whether the lit stage has room for detail on this part, asked once. See `detailOfMaterial`. */
|
|
1831
1878
|
private detailRoom: boolean | null = null;
|
|
1832
1879
|
private warnedDetailRoom = false;
|
|
@@ -1990,6 +2037,19 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1990
2037
|
return this.sceneViewProj as unknown as ReadonlyMat4;
|
|
1991
2038
|
}
|
|
1992
2039
|
private shadowPassActive = false;
|
|
2040
|
+
/**
|
|
2041
|
+
* The motion WebGL2's temporal resolve follows: the frame's movers, recorded as they are drawn and
|
|
2042
|
+
* drawn into a target of their own at `endFrame`. See `motionTarget.ts`.
|
|
2043
|
+
*/
|
|
2044
|
+
private readonly motionPass = new GlMotionPass();
|
|
2045
|
+
/** Which renderer a `Mover` was last drawn by, and the frame serial it reads. See `mover.ts`. */
|
|
2046
|
+
private readonly moverRenderer = claimRendererId();
|
|
2047
|
+
private moverFrame = 0;
|
|
2048
|
+
/** Between `beginFrame` and `endFrame`, which decides which frame a rewrite belongs to. */
|
|
2049
|
+
private inFrame = false;
|
|
2050
|
+
private readonly motionJittered = new Float32Array(16);
|
|
2051
|
+
private warnedMoverTwice = false;
|
|
2052
|
+
private warnedMotionUnkept = false;
|
|
1993
2053
|
/** Non-zero while the drawing buffer is pinned; see `lockDrawingBuffer`. */
|
|
1994
2054
|
private lockedWidth = 0;
|
|
1995
2055
|
private lockedHeight = 0;
|
|
@@ -2711,6 +2771,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2711
2771
|
setSpotCookies(images: readonly TexImageSource[]): void {
|
|
2712
2772
|
this.cookieImages = images.slice();
|
|
2713
2773
|
this.buildFixtureAtlas();
|
|
2774
|
+
/* The decals' band starts below the cookies, so their rows move with them. */
|
|
2775
|
+
if (this.baseDecalList.length > 0) this.setBaseDecals(this.baseDecalList);
|
|
2714
2776
|
}
|
|
2715
2777
|
|
|
2716
2778
|
setIesProfiles(profiles: readonly PhotometricProfile[]): void {
|
|
@@ -2718,6 +2780,102 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2718
2780
|
this.buildFixtureAtlas();
|
|
2719
2781
|
}
|
|
2720
2782
|
|
|
2783
|
+
/**
|
|
2784
|
+
* The images the frame's base decals lay, each a `BASE_DECAL_TILE` square, into the fixture
|
|
2785
|
+
* atlas's band of them. **Once a frame is what this is for**: the same count again uploads the
|
|
2786
|
+
* squares and nothing else, while a different count builds the atlas again. An image of another
|
|
2787
|
+
* size is refused, said once, and its square left clear. See `baseDecals.ts`.
|
|
2788
|
+
*/
|
|
2789
|
+
setBaseDecalImages(images: readonly TexImageSource[]): void {
|
|
2790
|
+
if (this.contextLost) return;
|
|
2791
|
+
if (images.length === this.decalImages.length && this.fixtureTexture !== null) {
|
|
2792
|
+
this.decalImages = images;
|
|
2793
|
+
const { gl } = this;
|
|
2794
|
+
gl.activeTexture(gl.TEXTURE0 + FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
2795
|
+
gl.bindTexture(gl.TEXTURE_2D, this.fixtureTexture);
|
|
2796
|
+
this.uploadDecalImages();
|
|
2797
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
2798
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
this.decalImages = images.slice();
|
|
2802
|
+
this.buildFixtureAtlas();
|
|
2803
|
+
this.setBaseDecals(this.baseDecalList);
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
/** The decal images into their squares of the atlas bound on the fixture unit. */
|
|
2807
|
+
private uploadDecalImages(): void {
|
|
2808
|
+
const { gl } = this;
|
|
2809
|
+
for (let tile = 0; tile < this.decalImages.length; tile++) {
|
|
2810
|
+
const image = this.decalImages[tile];
|
|
2811
|
+
if (image === undefined || !this.decalImageFits(image)) continue;
|
|
2812
|
+
gl.texSubImage2D(
|
|
2813
|
+
gl.TEXTURE_2D,
|
|
2814
|
+
0,
|
|
2815
|
+
tile * BASE_DECAL_TILE,
|
|
2816
|
+
decalBandTop(this.cookieImages.length),
|
|
2817
|
+
gl.RGBA,
|
|
2818
|
+
gl.HALF_FLOAT,
|
|
2819
|
+
image,
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
/** Whether an image is a decal's square, said once where it is not. */
|
|
2825
|
+
private decalImageFits(image: TexImageSource): boolean {
|
|
2826
|
+
const { width, height } = sourceSize(image);
|
|
2827
|
+
if (width === BASE_DECAL_TILE && height === BASE_DECAL_TILE) return true;
|
|
2828
|
+
const refusal =
|
|
2829
|
+
`a base decal image of ${width} by ${height}; it is a square of ${BASE_DECAL_TILE} ` +
|
|
2830
|
+
'texels, and is left clear';
|
|
2831
|
+
if (!this.warnedBaseDecals.has(refusal)) {
|
|
2832
|
+
this.warnedBaseDecals.add(refusal);
|
|
2833
|
+
console.warn(`Renderer: ${refusal}.`);
|
|
2834
|
+
}
|
|
2835
|
+
return false;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
/**
|
|
2839
|
+
* The decals laid into the base colours of the draws after it, until the next call, across
|
|
2840
|
+
* passes: each a box, an image of `setBaseDecalImages` and how much of it. **Refused, said once,
|
|
2841
|
+
* where the lit stage has no room** for their thirty-four vectors, and the draws are then marked
|
|
2842
|
+
* by none. See `baseDecals.ts`.
|
|
2843
|
+
*/
|
|
2844
|
+
setBaseDecals(decals: readonly BaseDecal[]): void {
|
|
2845
|
+
if (this.contextLost) return;
|
|
2846
|
+
this.baseDecalList = decals;
|
|
2847
|
+
if (decals.length > 0 && !this.baseDecalsFit()) {
|
|
2848
|
+
this.baseDecalFloats.fill(0);
|
|
2849
|
+
} else {
|
|
2850
|
+
const refusal = packBaseDecals(
|
|
2851
|
+
decals,
|
|
2852
|
+
this.decalImages.length,
|
|
2853
|
+
decalBandTop(this.cookieImages.length),
|
|
2854
|
+
this.baseDecalFloats,
|
|
2855
|
+
);
|
|
2856
|
+
if (refusal !== null && !this.warnedBaseDecals.has(refusal)) {
|
|
2857
|
+
this.warnedBaseDecals.add(refusal);
|
|
2858
|
+
console.warn(`Renderer: ${refusal}.`);
|
|
2859
|
+
}
|
|
2860
|
+
if ((this.baseDecalFloats[0] as number) > 0) this.askLit('BASE_DECALS');
|
|
2861
|
+
}
|
|
2862
|
+
this.materials.dirty();
|
|
2863
|
+
this.writeFlatVector('uBaseDecals', this.baseDecalFloats);
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
private baseDecalsFit(): boolean {
|
|
2867
|
+
this.decalRoom ??= this.switchFits('BASE_DECALS');
|
|
2868
|
+
if (!this.decalRoom && !this.warnedDecalRoom) {
|
|
2869
|
+
this.warnedDecalRoom = true;
|
|
2870
|
+
console.warn(
|
|
2871
|
+
'Renderer: base decals are refused on this device. They take thirty-four fragment uniform ' +
|
|
2872
|
+
'vectors the lit shader does not have, so nothing is marked. A lower `maxLights` makes ' +
|
|
2873
|
+
'the room, or WebGPU has it.',
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2876
|
+
return this.decalRoom;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2721
2879
|
/**
|
|
2722
2880
|
* The fixture atlas from the profiles and cookies in hand: the profiles' rows at the bottom, the
|
|
2723
2881
|
* cookies' tiles on top, one `RGBA16F` texture. See `fixtureAtlas.ts` for the layout.
|
|
@@ -2729,7 +2887,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2729
2887
|
*/
|
|
2730
2888
|
private buildFixtureAtlas(): void {
|
|
2731
2889
|
const { gl } = this;
|
|
2732
|
-
const layout = fixtureAtlasLayout(
|
|
2890
|
+
const layout = fixtureAtlasLayout(
|
|
2891
|
+
this.iesAtlas.height,
|
|
2892
|
+
this.cookieImages.length,
|
|
2893
|
+
this.decalImages.length,
|
|
2894
|
+
);
|
|
2733
2895
|
if (this.fixtureTexture === null) this.fixtureTexture = gl.createTexture();
|
|
2734
2896
|
gl.activeTexture(gl.TEXTURE0 + FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
2735
2897
|
gl.bindTexture(gl.TEXTURE_2D, this.fixtureTexture);
|
|
@@ -2749,6 +2911,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2749
2911
|
if (image === undefined) continue;
|
|
2750
2912
|
gl.texSubImage2D(gl.TEXTURE_2D, 0, tile * COOKIE_TILE, 0, gl.RGBA, gl.HALF_FLOAT, image);
|
|
2751
2913
|
}
|
|
2914
|
+
this.uploadDecalImages();
|
|
2752
2915
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2753
2916
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2754
2917
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
@@ -3547,6 +3710,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3547
3710
|
|
|
3548
3711
|
const { gl } = this;
|
|
3549
3712
|
if (!this.contextLost) this.skinScatter?.dispose();
|
|
3713
|
+
if (!this.contextLost) this.motionPass.dispose(gl);
|
|
3550
3714
|
this.detachContextLoss();
|
|
3551
3715
|
this.contextLostListeners.length = 0;
|
|
3552
3716
|
this.contextRestoredListeners.length = 0;
|
|
@@ -3738,6 +3902,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3738
3902
|
* buffer is the same buffer. Rebinding here would cost a state change for nothing.
|
|
3739
3903
|
*/
|
|
3740
3904
|
updateMesh(mesh: Mesh, positions: Float32Array, normals?: Float32Array): void {
|
|
3905
|
+
if (!this.contextLost) this.keepPositionsBefore(mesh);
|
|
3741
3906
|
mesh.update(this.gl, positions, normals);
|
|
3742
3907
|
}
|
|
3743
3908
|
|
|
@@ -3769,9 +3934,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3769
3934
|
validatePose(matrices, skin.reach);
|
|
3770
3935
|
if (this.contextLost || this.pointSkinPose === null) return;
|
|
3771
3936
|
poseBounds(matrices, skin.reach, skin.mesh.bounds);
|
|
3937
|
+
this.keepPositionsBefore(skin.mesh);
|
|
3772
3938
|
skin.pose(this.gl, this.pointSkinPose, matrices);
|
|
3773
3939
|
}
|
|
3774
3940
|
|
|
3941
|
+
/**
|
|
3942
|
+
* Rewrite part of a skin in place. A command in the stream like a pose, so the pose after it reads
|
|
3943
|
+
* the rewrite. See `GlPointSkin.rewrite`.
|
|
3944
|
+
*/
|
|
3945
|
+
updatePointSkin(skin: GlPointSkin, first: number, data: PointSkinData): void {
|
|
3946
|
+
validatePointSkinRange(data, first, skin.mesh.vertexCount);
|
|
3947
|
+
if (this.contextLost) return;
|
|
3948
|
+
skin.rewrite(this.gl, first, data);
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3775
3951
|
disposePointSkin(skin: GlPointSkin): void {
|
|
3776
3952
|
skin.dispose(this.gl);
|
|
3777
3953
|
}
|
|
@@ -4473,6 +4649,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4473
4649
|
emissiveScale?.[1] ?? 1,
|
|
4474
4650
|
emissiveScale?.[2] ?? 1,
|
|
4475
4651
|
);
|
|
4652
|
+
/* And its glow's scroll and mask, where the lit stage has room for them: emissiveFlow.ts. */
|
|
4653
|
+
if (packEmissiveFlow(material, this.flowFloats) && this.emissiveFlowFits()) {
|
|
4654
|
+
this.askLit('EMISSIVE_FLOW');
|
|
4655
|
+
} else {
|
|
4656
|
+
this.flowFloats.fill(0, 0, 4);
|
|
4657
|
+
}
|
|
4658
|
+
gl.uniform4fv(u['uEmissiveFlow'] ?? null, this.flowFloats);
|
|
4659
|
+
/* And its iridescent overlay, where it can be drawn: iridescentOverlay.ts. */
|
|
4660
|
+
const iridescent = this.iridescentOf(material);
|
|
4661
|
+
packIridescentOverlay(iridescent, material ?? {}, this.iridescentFloats);
|
|
4662
|
+
if (iridescent !== null) this.askLit('IRIDESCENT_OVERLAY');
|
|
4663
|
+
gl.uniform4fv(u['uIridescence'] ?? null, this.iridescentFloats);
|
|
4664
|
+
/* And whether the frame's decals mark it. See `SurfaceMaterial.receivesDecals`. */
|
|
4665
|
+
gl.uniform4f(u['uDecalReceiver'] ?? null, material?.receivesDecals === false ? 1 : 0, 0, 0, 0);
|
|
4476
4666
|
|
|
4477
4667
|
/*
|
|
4478
4668
|
* **The scale belongs to the material, not to the albedo.** It was uploaded on the albedo
|
|
@@ -4716,6 +4906,53 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4716
4906
|
return this.overlayRoom;
|
|
4717
4907
|
}
|
|
4718
4908
|
|
|
4909
|
+
/**
|
|
4910
|
+
* A material's iridescent overlay, or null where it is refused: malformed, or the lit stage has no
|
|
4911
|
+
* room for its thirty-six vectors. Each said once.
|
|
4912
|
+
*/
|
|
4913
|
+
private iridescentOf(material: SurfaceMaterial | null): IridescentOverlay | null {
|
|
4914
|
+
const overlay = material?.iridescentOverlay ?? null;
|
|
4915
|
+
if (overlay === null) return null;
|
|
4916
|
+
const refusal = iridescentRefusal(overlay);
|
|
4917
|
+
if (refusal !== null) {
|
|
4918
|
+
if (!this.warnedIridescent.has(refusal)) {
|
|
4919
|
+
this.warnedIridescent.add(refusal);
|
|
4920
|
+
console.warn(`Renderer: an iridescent overlay is refused: ${refusal}. Drawn without it.`);
|
|
4921
|
+
}
|
|
4922
|
+
return null;
|
|
4923
|
+
}
|
|
4924
|
+
this.iridescentRoom ??= this.switchFits('IRIDESCENT_OVERLAY');
|
|
4925
|
+
if (this.iridescentRoom) return overlay;
|
|
4926
|
+
if (!this.warnedIridescentRoom) {
|
|
4927
|
+
this.warnedIridescentRoom = true;
|
|
4928
|
+
console.warn(
|
|
4929
|
+
"Renderer: a material's iridescent overlay is refused on this device. It takes thirty-six " +
|
|
4930
|
+
'fragment uniform vectors the lit shader does not have, so the material is drawn without ' +
|
|
4931
|
+
'it. A lower `maxLights` makes the room, or WebGPU has it.',
|
|
4932
|
+
);
|
|
4933
|
+
}
|
|
4934
|
+
return null;
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4937
|
+
/**
|
|
4938
|
+
* Whether a material's glow can be moved and masked: the lit stage has room for its two vectors.
|
|
4939
|
+
* **Refused, said once, where it has not**, and the glow is then drawn still and unmasked, as it
|
|
4940
|
+
* was before it existed.
|
|
4941
|
+
*/
|
|
4942
|
+
private emissiveFlowFits(): boolean {
|
|
4943
|
+
this.flowRoom ??= this.switchFits('EMISSIVE_FLOW');
|
|
4944
|
+
if (!this.flowRoom && !this.warnedFlowRoom) {
|
|
4945
|
+
this.warnedFlowRoom = true;
|
|
4946
|
+
console.warn(
|
|
4947
|
+
"Renderer: a material's emissive scroll and mask are refused on this device. They take " +
|
|
4948
|
+
'two fragment uniform vectors and the lit shader already uses all ' +
|
|
4949
|
+
`${this.fragmentVectorLimit()} this GPU offers; the glow is drawn still and unmasked. ` +
|
|
4950
|
+
'A lower `maxLights` makes the room, or WebGPU has it.',
|
|
4951
|
+
);
|
|
4952
|
+
}
|
|
4953
|
+
return this.flowRoom;
|
|
4954
|
+
}
|
|
4955
|
+
|
|
4719
4956
|
/**
|
|
4720
4957
|
* Whether a material's projection can be drawn: none asked, or the lit stage has the room for its
|
|
4721
4958
|
* one vector. **Refused, said once, where it has not**, as the overlay is, and the material's
|
|
@@ -7215,6 +7452,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7215
7452
|
layered: asIf.LAYERED ?? this.litOn.LAYERED,
|
|
7216
7453
|
layerLooks: asIf.LAYER_LOOKS ?? this.litOn.LAYER_LOOKS,
|
|
7217
7454
|
surfaceDetail: asIf.SURFACE_DETAIL ?? this.litOn.SURFACE_DETAIL,
|
|
7455
|
+
emissiveFlow: asIf.EMISSIVE_FLOW ?? this.litOn.EMISSIVE_FLOW,
|
|
7456
|
+
iridescentOverlay: asIf.IRIDESCENT_OVERLAY ?? this.litOn.IRIDESCENT_OVERLAY,
|
|
7457
|
+
baseDecals: asIf.BASE_DECALS ?? this.litOn.BASE_DECALS,
|
|
7218
7458
|
maxLights: budget.maxLights,
|
|
7219
7459
|
maxAreaLights: budget.maxAreaLights,
|
|
7220
7460
|
});
|
|
@@ -8637,6 +8877,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8637
8877
|
if (this.contextLost) return;
|
|
8638
8878
|
/* One frame of the jitter's sequence however many times the frame binds its camera. */
|
|
8639
8879
|
this.temporalHistory.nextFrame();
|
|
8880
|
+
/* A frame states its movers afresh. See `motionTarget.ts`. */
|
|
8881
|
+
this.moverFrame += 1;
|
|
8882
|
+
this.inFrame = true;
|
|
8883
|
+
this.motionPass.reset();
|
|
8640
8884
|
this.skinScattered = false;
|
|
8641
8885
|
this.skinScatter?.reset();
|
|
8642
8886
|
if (this.litAsked) {
|
|
@@ -8727,6 +8971,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8727
8971
|
if (this.viewModelActive) this.endViewModel();
|
|
8728
8972
|
/* Skin not yet spread, because nothing blended came after it, is spread now. */
|
|
8729
8973
|
this.scatterSkin();
|
|
8974
|
+
/* A draw after the frame, an overlay over the presented picture, is no mover of it. */
|
|
8975
|
+
this.inFrame = false;
|
|
8730
8976
|
|
|
8731
8977
|
/*
|
|
8732
8978
|
* Camera motion blur, built from the camera the frame was *rendered* with.
|
|
@@ -8748,6 +8994,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8748
8994
|
blend: number;
|
|
8749
8995
|
periodStart: boolean;
|
|
8750
8996
|
depthToViewZ: Float32Array;
|
|
8997
|
+
motion: WebGLTexture | null;
|
|
8751
8998
|
}
|
|
8752
8999
|
| undefined;
|
|
8753
9000
|
/*
|
|
@@ -8757,6 +9004,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8757
9004
|
* reproject through a `previousViewProj` that had never been written.
|
|
8758
9005
|
*/
|
|
8759
9006
|
const wantsReprojection = blur > 0 || this.temporalJittering;
|
|
9007
|
+
/* The movers' own motion, drawn while last frame's view is still the one kept: motionTarget.ts. */
|
|
9008
|
+
const moved =
|
|
9009
|
+
this.temporalJittering && view !== null && this.hasPreviousView
|
|
9010
|
+
? this.drawMotionPass(view)
|
|
9011
|
+
: null;
|
|
8760
9012
|
if (wantsReprojection && view !== null) {
|
|
8761
9013
|
if (this.hasPreviousView) {
|
|
8762
9014
|
mat4.invert(this.reprojection, view);
|
|
@@ -8791,6 +9043,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8791
9043
|
blend: this.temporalHistoryUsable ? TEMPORAL_HISTORY_BLEND : 0,
|
|
8792
9044
|
periodStart: this.temporalHistory.periodStart,
|
|
8793
9045
|
depthToViewZ: this.taaDepthToViewZ,
|
|
9046
|
+
motion: moved,
|
|
8794
9047
|
};
|
|
8795
9048
|
}
|
|
8796
9049
|
|
|
@@ -9392,6 +9645,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9392
9645
|
this.layerFloats.fill(0);
|
|
9393
9646
|
this.lookFloats.fill(0);
|
|
9394
9647
|
this.detailFloats.fill(0);
|
|
9648
|
+
/* Nor a glow that moves or is masked. */
|
|
9649
|
+
this.flowFloats.fill(0);
|
|
9650
|
+
/* Nor an iridescent overlay. */
|
|
9651
|
+
this.iridescentFloats.fill(0);
|
|
9395
9652
|
this.overlayMaps = null;
|
|
9396
9653
|
this.overlayTexture = null;
|
|
9397
9654
|
/* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
|
|
@@ -9570,6 +9827,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9570
9827
|
gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
|
|
9571
9828
|
gl.uniform4fv(u['uLayerLooks'] ?? null, this.lookFloats);
|
|
9572
9829
|
gl.uniform4fv(u['uDetail'] ?? null, this.detailFloats);
|
|
9830
|
+
gl.uniform4fv(u['uEmissiveFlow'] ?? null, this.flowFloats);
|
|
9831
|
+
gl.uniform4fv(u['uIridescence'] ?? null, this.iridescentFloats);
|
|
9832
|
+
/* The frame's decals as last laid, held across passes, and every surface receiving them. */
|
|
9833
|
+
gl.uniform4fv(u['uBaseDecals'] ?? null, this.baseDecalFloats);
|
|
9834
|
+
gl.uniform4f(u['uDecalReceiver'] ?? null, 0, 0, 0, 0);
|
|
9573
9835
|
/* Neither refracting nor glass, which is every draw that does not say otherwise. See
|
|
9574
9836
|
`bindSeeThrough` for the four numbers in the one vector. */
|
|
9575
9837
|
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
@@ -9991,12 +10253,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9991
10253
|
*/
|
|
9992
10254
|
tint: Vec3 | null = null,
|
|
9993
10255
|
/**
|
|
9994
|
-
* Where the mesh was last frame
|
|
9995
|
-
*
|
|
9996
|
-
*
|
|
9997
|
-
*
|
|
9998
|
-
* here, so there is no motion target for this to be written into, and a mover passed here is
|
|
9999
|
-
* neither read nor advanced.
|
|
10256
|
+
* Where the mesh was last frame, a `Mover` or a matrix, which the temporal resolve follows under
|
|
10257
|
+
* `temporalAa`: a rigid, skinned or rewritten mesh is drawn again into the motion target at the
|
|
10258
|
+
* end of the frame, so its pixels reproject by its own motion and not the camera's alone. See
|
|
10259
|
+
* `motionTarget.ts`; a mover is advanced only where the resolve reads it.
|
|
10000
10260
|
*/
|
|
10001
10261
|
previousModel: ReadonlyMat4 | Mover | null = null,
|
|
10002
10262
|
): void {
|
|
@@ -10035,6 +10295,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
10035
10295
|
if (this.quality.cullDraws && this.occluded(mesh.bounds, model)) return;
|
|
10036
10296
|
this.drawBudget.ask();
|
|
10037
10297
|
this.takeMaterial();
|
|
10298
|
+
if (this.followsMotion()) this.recordDrawMotion(mesh, model, previousModel);
|
|
10038
10299
|
/*
|
|
10039
10300
|
* Skin under the screen-space blur draws its halves: the frame's, then at once its diffuse's
|
|
10040
10301
|
* light and its colour, each into the blur's own target.
|
|
@@ -10056,6 +10317,147 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
10056
10317
|
}
|
|
10057
10318
|
}
|
|
10058
10319
|
|
|
10320
|
+
/**
|
|
10321
|
+
* The frame's movers drawn into the motion target against its depth, rasterised through the
|
|
10322
|
+
* jittered matrix every geometry upload took (`viewProjFor`) and moved between the unjittered
|
|
10323
|
+
* ones; null where nothing moved or the frame is multisampled, which keeps the camera's motion
|
|
10324
|
+
* and says so once.
|
|
10325
|
+
*/
|
|
10326
|
+
private drawMotionPass(view: ReadonlyMat4): WebGLTexture | null {
|
|
10327
|
+
const target = this.sceneTarget;
|
|
10328
|
+
if (target === null || this.motionPass.recorded === 0) return null;
|
|
10329
|
+
if (target.sampleCount > 1) {
|
|
10330
|
+
if (!this.warnedMotionUnkept) {
|
|
10331
|
+
this.warnedMotionUnkept = true;
|
|
10332
|
+
console.warn(
|
|
10333
|
+
"Renderer: WebGL2's temporal resolve follows the camera's motion alone in a multisampled " +
|
|
10334
|
+
"frame; a mover's own motion is drawn against single-sampled depth.",
|
|
10335
|
+
);
|
|
10336
|
+
}
|
|
10337
|
+
return null;
|
|
10338
|
+
}
|
|
10339
|
+
const depth = target.depthAttachment();
|
|
10340
|
+
if (depth === null) return null;
|
|
10341
|
+
jitterProjection(
|
|
10342
|
+
this.motionJittered,
|
|
10343
|
+
this.sceneMatrix(view),
|
|
10344
|
+
this.temporalJitterX,
|
|
10345
|
+
this.temporalJitterY,
|
|
10346
|
+
Math.max(1, this.gl.drawingBufferWidth),
|
|
10347
|
+
Math.max(1, this.gl.drawingBufferHeight),
|
|
10348
|
+
);
|
|
10349
|
+
return this.motionPass.draw(
|
|
10350
|
+
this.gl,
|
|
10351
|
+
depth,
|
|
10352
|
+
target.frameWidth,
|
|
10353
|
+
target.frameHeight,
|
|
10354
|
+
this.reversedDepth,
|
|
10355
|
+
this.motionJittered,
|
|
10356
|
+
view as Float32Array,
|
|
10357
|
+
this.previousViewProj as Float32Array,
|
|
10358
|
+
);
|
|
10359
|
+
}
|
|
10360
|
+
|
|
10361
|
+
/**
|
|
10362
|
+
* Whether a draw now is one the temporal resolve follows: the profile resolves temporally, this is
|
|
10363
|
+
* the frame's own picture between `beginFrame` and `endFrame`, and not a mirror's, a probe's, a
|
|
10364
|
+
* capture's, a shadow's or the view model's. See `motionTarget.ts`.
|
|
10365
|
+
*/
|
|
10366
|
+
private followsMotion(): boolean {
|
|
10367
|
+
return (
|
|
10368
|
+
this.inFrame &&
|
|
10369
|
+
this.quality.temporalAa &&
|
|
10370
|
+
this.sceneTarget !== null &&
|
|
10371
|
+
!this.reflectionPassActive &&
|
|
10372
|
+
!this.probePassActive &&
|
|
10373
|
+
!this.capturePassActive &&
|
|
10374
|
+
!this.shadowPassActive &&
|
|
10375
|
+
!this.viewModelActive
|
|
10376
|
+
);
|
|
10377
|
+
}
|
|
10378
|
+
|
|
10379
|
+
/**
|
|
10380
|
+
* A draw's motion for the resolve, decided as WebGPU's `recordDrawMotion` decides it: a mesh
|
|
10381
|
+
* rewritten this frame by its last positions, with or without a mover; a skinned mover by its last
|
|
10382
|
+
* pose; a rigid one by its last model. A cloth draw keeps the camera's motion, said once.
|
|
10383
|
+
*/
|
|
10384
|
+
private recordDrawMotion(
|
|
10385
|
+
mesh: Mesh,
|
|
10386
|
+
model: ReadonlyMat4,
|
|
10387
|
+
previous: ReadonlyMat4 | Mover | null,
|
|
10388
|
+
): void {
|
|
10389
|
+
const rewritten = mesh.keptPrevious && mesh.changedFrame === this.moverFrame;
|
|
10390
|
+
if (this.cloth !== null && this.skinPaletteSet && mesh.isSkinned) {
|
|
10391
|
+
if (previous !== null && !this.warnedMotionUnkept) {
|
|
10392
|
+
this.warnedMotionUnkept = true;
|
|
10393
|
+
console.warn(
|
|
10394
|
+
"Renderer: a cloth draw's own motion is not followed by WebGL2's temporal resolve; it " +
|
|
10395
|
+
"takes the camera's. WebGPU's reconstruction follows it.",
|
|
10396
|
+
);
|
|
10397
|
+
}
|
|
10398
|
+
return;
|
|
10399
|
+
}
|
|
10400
|
+
if (previous === null) {
|
|
10401
|
+
if (rewritten) this.motionPass.recordDynamic(mesh, model, model);
|
|
10402
|
+
return;
|
|
10403
|
+
}
|
|
10404
|
+
if (!isMover(previous)) {
|
|
10405
|
+
if (rewritten) this.motionPass.recordDynamic(mesh, model, previous);
|
|
10406
|
+
else this.motionPass.recordRigid(mesh, model, previous);
|
|
10407
|
+
return;
|
|
10408
|
+
}
|
|
10409
|
+
const skinned = !rewritten && this.skinPaletteSet && mesh.isSkinned;
|
|
10410
|
+
const verdict = advanceMover(
|
|
10411
|
+
previous,
|
|
10412
|
+
this.moverRenderer,
|
|
10413
|
+
this.moverFrame,
|
|
10414
|
+
model,
|
|
10415
|
+
skinned ? this.paletteData : null,
|
|
10416
|
+
);
|
|
10417
|
+
if (verdict === MOVER_TWICE) {
|
|
10418
|
+
if (!this.warnedMoverTwice) {
|
|
10419
|
+
this.warnedMoverTwice = true;
|
|
10420
|
+
console.warn(
|
|
10421
|
+
'Renderer: a Mover was drawn twice in one frame. A mover is one object; the second draw ' +
|
|
10422
|
+
'records no motion. Give each moving object a mover of its own.',
|
|
10423
|
+
);
|
|
10424
|
+
}
|
|
10425
|
+
return;
|
|
10426
|
+
}
|
|
10427
|
+
if (rewritten) {
|
|
10428
|
+
this.motionPass.recordDynamic(
|
|
10429
|
+
mesh,
|
|
10430
|
+
model,
|
|
10431
|
+
verdict === MOVER_MOTION ? previous.previousModel : model,
|
|
10432
|
+
);
|
|
10433
|
+
return;
|
|
10434
|
+
}
|
|
10435
|
+
if (verdict !== MOVER_MOTION) return;
|
|
10436
|
+
if (skinned) {
|
|
10437
|
+
this.motionPass.recordSkinned(
|
|
10438
|
+
mesh,
|
|
10439
|
+
model,
|
|
10440
|
+
previous.previousModel,
|
|
10441
|
+
/* Each exactly its rig's length, as `advanceMover` keeps them, so neither is sliced. */
|
|
10442
|
+
previous.palette,
|
|
10443
|
+
previous.previousPalette,
|
|
10444
|
+
);
|
|
10445
|
+
} else {
|
|
10446
|
+
this.motionPass.recordRigid(mesh, model, previous.previousModel);
|
|
10447
|
+
}
|
|
10448
|
+
}
|
|
10449
|
+
|
|
10450
|
+
/**
|
|
10451
|
+
* Keep a dynamic mesh's positions before its frame's first rewrite, where the temporal resolve
|
|
10452
|
+
* follows motion: a rewrite before `beginFrame` belongs to the next frame (`frameOfChange`).
|
|
10453
|
+
*/
|
|
10454
|
+
private keepPositionsBefore(mesh: Mesh): void {
|
|
10455
|
+
if (!(this.quality.temporalAa && this.sceneTarget !== null)) return;
|
|
10456
|
+
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
10457
|
+
if (keepsPrevious(mesh.changedFrame, frame)) mesh.keepPrevious(this.gl);
|
|
10458
|
+
mesh.changedFrame = frame;
|
|
10459
|
+
}
|
|
10460
|
+
|
|
10059
10461
|
/**
|
|
10060
10462
|
* Whether a skin draw now draws its two halves: the profile asked, the frame has not spread its
|
|
10061
10463
|
* skin yet, this is the frame's own picture — not a mirror's or a probe's, nor an overlay after
|