@driftengine/core 4.13.5 → 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 +62 -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
package/dist/render/pointSkin.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { MAX_JOINTS } from './skinPalette.js';
|
|
2
2
|
/** Refuse a skin whose arrays disagree with a mesh of `vertices`, or that names a joint past the rest. */
|
|
3
3
|
export function validatePointSkin(data, vertices) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
checkArrays(data, vertices, 'the mesh has');
|
|
5
|
+
checkJoints(data, 0);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Refuse a rewrite of vertices `first` onward, as many as `data` holds, that does not fit a mesh of
|
|
9
|
+
* `vertices`: arrays that disagree with each other, a range that is not whole vertices inside the
|
|
10
|
+
* mesh, or a joint past the rest. Named by the mesh's own vertex numbers.
|
|
11
|
+
*/
|
|
12
|
+
export function validatePointSkinRange(data, first, vertices) {
|
|
13
|
+
const count = data.weights.length / 4;
|
|
14
|
+
checkArrays(data, count, 'the weights hold');
|
|
15
|
+
if (!(Number.isInteger(first) && first >= 0 && first + count <= vertices)) {
|
|
16
|
+
throw new Error(`point skin: a rewrite of ${count} vertices from vertex ${first} for a mesh of ${vertices}`);
|
|
17
|
+
}
|
|
18
|
+
checkJoints(data, first);
|
|
19
|
+
}
|
|
20
|
+
function checkArrays(data, vertices, against) {
|
|
7
21
|
for (const [name, values, width] of [
|
|
8
22
|
['joints', data.joints, 4],
|
|
9
23
|
['weights', data.weights, 4],
|
|
@@ -11,13 +25,17 @@ export function validatePointSkin(data, vertices) {
|
|
|
11
25
|
['normals', data.normals, 3],
|
|
12
26
|
]) {
|
|
13
27
|
if (values.length !== vertices * width) {
|
|
14
|
-
|
|
28
|
+
throw new Error(`point skin: ${name} holds ${values.length / width} vertices and ${against} ${vertices}`);
|
|
15
29
|
}
|
|
16
30
|
}
|
|
31
|
+
}
|
|
32
|
+
/** Every joint a whole number below `MAX_JOINTS`; a vertex named as `first` plus its place. */
|
|
33
|
+
function checkJoints(data, first) {
|
|
17
34
|
for (let at = 0; at < data.joints.length; at++) {
|
|
18
35
|
const joint = data.joints[at];
|
|
19
36
|
if (!(Number.isInteger(joint) && joint >= 0 && joint < MAX_JOINTS)) {
|
|
20
|
-
|
|
37
|
+
throw new Error(`point skin: vertex ${first + Math.floor(at / 4)} names joint ${joint}; a joint is 0 to ` +
|
|
38
|
+
`${MAX_JOINTS - 1}`);
|
|
21
39
|
}
|
|
22
40
|
}
|
|
23
41
|
}
|
|
@@ -54,6 +72,33 @@ export function reachOf(data) {
|
|
|
54
72
|
sums: vertices === 0 ? [1, 1] : [least, most],
|
|
55
73
|
};
|
|
56
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* A skin's reach after part of it is rewritten with `data`: the farther of the old reach and the
|
|
77
|
+
* new at each joint, the joints the two name between them, and the widest span of weight sums.
|
|
78
|
+
*
|
|
79
|
+
* **It widens and never narrows**, because the vertices the rewrite replaced may have been the
|
|
80
|
+
* ones that set a joint's reach, and finding out would mean keeping every vertex on the CPU and
|
|
81
|
+
* reading them all again. A skin that changes shape back and forth settles at the union of its
|
|
82
|
+
* shapes, which is still a box no posed vertex leaves, and culls a little less than it could. What
|
|
83
|
+
* would make that wrong is a skin rewritten into ever larger shapes and never back; making it
|
|
84
|
+
* again with `createPointSkin` measures it afresh.
|
|
85
|
+
*/
|
|
86
|
+
export function widenReach(reach, data) {
|
|
87
|
+
const added = reachOf(data);
|
|
88
|
+
const joints = Math.max(reach.joints, added.joints);
|
|
89
|
+
const widened = new Float32Array(joints).fill(-1);
|
|
90
|
+
for (let j = 0; j < joints; j++) {
|
|
91
|
+
widened[j] = Math.max(j < reach.joints ? reach.reach[j] : -1, j < added.joints ? added.reach[j] : -1);
|
|
92
|
+
}
|
|
93
|
+
const vertices = data.weights.length / 4;
|
|
94
|
+
return {
|
|
95
|
+
joints,
|
|
96
|
+
reach: widened,
|
|
97
|
+
sums: vertices === 0
|
|
98
|
+
? reach.sums
|
|
99
|
+
: [Math.min(reach.sums[0], added.sums[0]), Math.max(reach.sums[1], added.sums[1])],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
57
102
|
/**
|
|
58
103
|
* The bounds of a pose, into the mesh's own: `matrices` holds each joint's transform into the
|
|
59
104
|
* mesh's model space, sixteen floats, column-major. Allocates nothing.
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* draw, last frame's palette.
|
|
9
9
|
*
|
|
10
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.
|
|
12
|
-
*
|
|
11
|
+
* it beside the object it belongs to. A reconstructing WebGPU renderer reads and writes it, and so
|
|
12
|
+
* does a WebGL2 one resolving temporally (`backend/webgl2/motionTarget.ts`); it costs nothing
|
|
13
|
+
* anywhere else. The fields past `kind` are the renderer's and a caller leaves them
|
|
13
14
|
* alone.
|
|
14
15
|
*
|
|
15
16
|
* **What would make this wrong** is a consumer whose objects are rebuilt every frame with no state
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* draw, last frame's palette.
|
|
9
9
|
*
|
|
10
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.
|
|
12
|
-
*
|
|
11
|
+
* it beside the object it belongs to. A reconstructing WebGPU renderer reads and writes it, and so
|
|
12
|
+
* does a WebGL2 one resolving temporally (`backend/webgl2/motionTarget.ts`); it costs nothing
|
|
13
|
+
* anywhere else. The fields past `kind` are the renderer's and a caller leaves them
|
|
13
14
|
* alone.
|
|
14
15
|
*
|
|
15
16
|
* **What would make this wrong** is a consumer whose objects are rebuilt every frame with no state
|
|
@@ -341,6 +341,8 @@ export declare class SceneTarget {
|
|
|
341
341
|
readonly blend: number;
|
|
342
342
|
readonly periodStart: boolean;
|
|
343
343
|
readonly depthToViewZ: Float32Array;
|
|
344
|
+
/** Where the frame's movers stood last frame, or absent for the camera's motion alone. */
|
|
345
|
+
readonly motion?: WebGLTexture | null;
|
|
344
346
|
}): void;
|
|
345
347
|
dispose(): void;
|
|
346
348
|
}
|
|
@@ -789,7 +789,7 @@ export class SceneTarget {
|
|
|
789
789
|
let scene = this.texture;
|
|
790
790
|
if (temporal !== undefined && this.texture !== null && this.depth !== null) {
|
|
791
791
|
this.temporal ??= new TemporalPass(gl, this.floatColor);
|
|
792
|
-
scene = this.temporal.temporalResolve(this.texture, this.depth, temporal.reprojection, this.width, this.height, temporal.blend, temporal.periodStart, temporal.depthToViewZ);
|
|
792
|
+
scene = this.temporal.temporalResolve(this.texture, this.depth, temporal.reprojection, this.width, this.height, temporal.blend, temporal.periodStart, temporal.depthToViewZ, temporal.motion ?? null);
|
|
793
793
|
}
|
|
794
794
|
let bloomTexture = null;
|
|
795
795
|
if (bloom !== undefined && scene !== null) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decals laid into a surface's base colour before it is lit: `setBaseDecals`, packed into
|
|
3
|
+
* `uBaseDecals` by `baseDecals.ts`, where what they are for and what they give up is said, and the
|
|
4
|
+
* material's own refusal in `uDecalReceiver` (`SurfaceMaterial.receivesDecals`).
|
|
5
|
+
*
|
|
6
|
+
* **Each decal is a test and a fetch**: the world point taken into the box by three rows, outside
|
|
7
|
+
* it or turned too far from the projection and nothing, else the image read across the box's face
|
|
8
|
+
* from its square in the fixture atlas, its colour decoded from sRGB and laid over the base colour
|
|
9
|
+
* by its alpha and the decal's opacity. The geometric normal decides the facing, so a normal map's
|
|
10
|
+
* bump does not cut a mark. The loop is over a count in a uniform and the fetch is `textureLod`, as
|
|
11
|
+
* every read of the atlas is, so neither needs uniform control flow: AGENTS.md 2026-08-07.
|
|
12
|
+
*
|
|
13
|
+
* **A lit switch, `BASE_DECALS`**, off until a frame first lays one, declared after
|
|
14
|
+
* `IRIDESCENT_OVERLAY` so each switch before it keeps its id. Compiled out, its thirty-four vectors
|
|
15
|
+
* are not declared and the call is a constant.
|
|
16
|
+
*/
|
|
17
|
+
export declare function baseDecalsGlsl(on: boolean): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decals laid into a surface's base colour before it is lit: `setBaseDecals`, packed into
|
|
3
|
+
* `uBaseDecals` by `baseDecals.ts`, where what they are for and what they give up is said, and the
|
|
4
|
+
* material's own refusal in `uDecalReceiver` (`SurfaceMaterial.receivesDecals`).
|
|
5
|
+
*
|
|
6
|
+
* **Each decal is a test and a fetch**: the world point taken into the box by three rows, outside
|
|
7
|
+
* it or turned too far from the projection and nothing, else the image read across the box's face
|
|
8
|
+
* from its square in the fixture atlas, its colour decoded from sRGB and laid over the base colour
|
|
9
|
+
* by its alpha and the decal's opacity. The geometric normal decides the facing, so a normal map's
|
|
10
|
+
* bump does not cut a mark. The loop is over a count in a uniform and the fetch is `textureLod`, as
|
|
11
|
+
* every read of the atlas is, so neither needs uniform control flow: AGENTS.md 2026-08-07.
|
|
12
|
+
*
|
|
13
|
+
* **A lit switch, `BASE_DECALS`**, off until a frame first lays one, declared after
|
|
14
|
+
* `IRIDESCENT_OVERLAY` so each switch before it keeps its id. Compiled out, its thirty-four vectors
|
|
15
|
+
* are not declared and the call is a constant.
|
|
16
|
+
*/
|
|
17
|
+
export function baseDecalsGlsl(on) {
|
|
18
|
+
if (!on) {
|
|
19
|
+
return /* glsl */ `
|
|
20
|
+
const bool BASE_DECALS = false; // wgsl:override
|
|
21
|
+
vec3 baseDecals(vec3 base, vec3 world, vec3 geometric) { return base; }
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
24
|
+
return /* glsl */ `
|
|
25
|
+
/* Whether this program lays decals into base colours. Off but where a frame has laid one. */
|
|
26
|
+
const bool BASE_DECALS = true; // wgsl:override
|
|
27
|
+
/*
|
|
28
|
+
* A header, how many decals, the atlas row their band starts at and its squares' side (0); then
|
|
29
|
+
* four vectors a decal, eight decals: the three rows taking a world point into its box, x across,
|
|
30
|
+
* y up the image, z along the projection, and its image, opacity and facing cosine.
|
|
31
|
+
*/
|
|
32
|
+
uniform vec4 uBaseDecals[33]; // wgsl:material
|
|
33
|
+
/* x 1 where the material refuses decals, so an unwritten zero receives them. */
|
|
34
|
+
uniform vec4 uDecalReceiver; // wgsl:material
|
|
35
|
+
|
|
36
|
+
vec3 decalLinear(vec3 c) {
|
|
37
|
+
return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
vec3 baseDecals(vec3 base, vec3 world, vec3 geometric) {
|
|
41
|
+
if (uDecalReceiver.x > 0.5) return base;
|
|
42
|
+
int count = int(uBaseDecals[0].x + 0.5);
|
|
43
|
+
vec2 atlas = vec2(textureSize(uFixtureAtlas, 0));
|
|
44
|
+
float span = uBaseDecals[0].z;
|
|
45
|
+
/* Half a texel of a square, so a bilinear read at its edge cannot reach the next one. */
|
|
46
|
+
float inset = 0.5 / span;
|
|
47
|
+
for (int i = 0; i < 8; i++) {
|
|
48
|
+
if (i >= count) break;
|
|
49
|
+
vec4 across = uBaseDecals[1 + i * 4];
|
|
50
|
+
vec4 up = uBaseDecals[2 + i * 4];
|
|
51
|
+
vec4 along = uBaseDecals[3 + i * 4];
|
|
52
|
+
vec4 laid = uBaseDecals[4 + i * 4];
|
|
53
|
+
vec3 local = vec3(
|
|
54
|
+
dot(across.xyz, world) + across.w,
|
|
55
|
+
dot(up.xyz, world) + up.w,
|
|
56
|
+
dot(along.xyz, world) + along.w
|
|
57
|
+
);
|
|
58
|
+
if (any(greaterThan(abs(local), vec3(1.0)))) continue;
|
|
59
|
+
if (dot(geometric, -normalize(along.xyz)) < laid.z) continue;
|
|
60
|
+
vec2 face = vec2(local.x, -local.y) * 0.5 + 0.5;
|
|
61
|
+
vec2 at = vec2(
|
|
62
|
+
(laid.x + clamp(face.x, inset, 1.0 - inset)) * span / atlas.x,
|
|
63
|
+
(uBaseDecals[0].y + clamp(face.y, inset, 1.0 - inset) * span) / atlas.y
|
|
64
|
+
);
|
|
65
|
+
vec4 texel = textureLod(uFixtureAtlas, at, 0.0);
|
|
66
|
+
base = mix(base, decalLinear(texel.rgb), clamp(texel.a * laid.y, 0.0, 1.0));
|
|
67
|
+
}
|
|
68
|
+
return base;
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's glow moved and masked: `SurfaceMaterial.emissiveScroll` and `emissiveMaskLayer`,
|
|
3
|
+
* packed into `uEmissiveFlow` by `emissiveFlow.ts`, where what each gives up is said.
|
|
4
|
+
*
|
|
5
|
+
* **The scroll** is added to wherever the emissive map is read, the mesh's coordinates, a layer's
|
|
6
|
+
* place or a projection's planes, as its own repeats a second times the surface clock, wrapped to
|
|
7
|
+
* one repeat. **The mask** is the alpha of a layer of the emissive map's own array at the mesh's
|
|
8
|
+
* first coordinates as they are: the vertex stage has already laid the material's repeat and offset
|
|
9
|
+
* on `vUv`, so the second vector holds their inverse, which gives the mesh's own back.
|
|
10
|
+
*
|
|
11
|
+
* **A lit switch, `EMISSIVE_FLOW`**, off until a material first asks, declared after
|
|
12
|
+
* `SURFACE_DETAIL` so each switch before it keeps its id. Compiled out, its two vectors are not
|
|
13
|
+
* declared and the shift and the mask are constants.
|
|
14
|
+
*/
|
|
15
|
+
export declare function emissiveFlowGlsl(on: boolean): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's glow moved and masked: `SurfaceMaterial.emissiveScroll` and `emissiveMaskLayer`,
|
|
3
|
+
* packed into `uEmissiveFlow` by `emissiveFlow.ts`, where what each gives up is said.
|
|
4
|
+
*
|
|
5
|
+
* **The scroll** is added to wherever the emissive map is read, the mesh's coordinates, a layer's
|
|
6
|
+
* place or a projection's planes, as its own repeats a second times the surface clock, wrapped to
|
|
7
|
+
* one repeat. **The mask** is the alpha of a layer of the emissive map's own array at the mesh's
|
|
8
|
+
* first coordinates as they are: the vertex stage has already laid the material's repeat and offset
|
|
9
|
+
* on `vUv`, so the second vector holds their inverse, which gives the mesh's own back.
|
|
10
|
+
*
|
|
11
|
+
* **A lit switch, `EMISSIVE_FLOW`**, off until a material first asks, declared after
|
|
12
|
+
* `SURFACE_DETAIL` so each switch before it keeps its id. Compiled out, its two vectors are not
|
|
13
|
+
* declared and the shift and the mask are constants.
|
|
14
|
+
*/
|
|
15
|
+
export function emissiveFlowGlsl(on) {
|
|
16
|
+
if (!on) {
|
|
17
|
+
return /* glsl */ `
|
|
18
|
+
const bool EMISSIVE_FLOW = false; // wgsl:override
|
|
19
|
+
vec2 emissiveShift() { return vec2(0.0); }
|
|
20
|
+
float emissiveMask() { return 1.0; }
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
return /* glsl */ `
|
|
24
|
+
/* Whether this program moves and masks a material's glow. Off but where one has asked. */
|
|
25
|
+
const bool EMISSIVE_FLOW = true; // wgsl:override
|
|
26
|
+
/*
|
|
27
|
+
* Two vectors, as emissiveFlow.ts packs them: the scroll in repeats a second on u and v (x y) and
|
|
28
|
+
* the mask's layer of the emissive array plus one, 0 for none (z); then what takes the material's
|
|
29
|
+
* coordinates back to the mesh's own, a scale (x y) and an offset (z w).
|
|
30
|
+
*/
|
|
31
|
+
uniform vec4 uEmissiveFlow[2]; // wgsl:material
|
|
32
|
+
|
|
33
|
+
/* The emissive map's coordinates moved by the clock, wrapped to one repeat for precision. */
|
|
34
|
+
vec2 emissiveShift() {
|
|
35
|
+
return fract(uEmissiveFlow[0].xy * uSurfaceScene.x);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* The mask's alpha at the mesh's own coordinates, under the branch on a bound emissive map. */
|
|
39
|
+
float emissiveMask() {
|
|
40
|
+
if (uEmissiveFlow[0].z < 0.5) return 1.0;
|
|
41
|
+
vec2 own = vUv.xy * uEmissiveFlow[1].xy + uEmissiveFlow[1].zw;
|
|
42
|
+
return texture(uEmissiveMap, vec3(own, uEmissiveFlow[0].z - 1.0)).a;
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
@@ -8,7 +8,7 @@ export declare function glassTintGlsl(glassShadows: boolean): string;
|
|
|
8
8
|
* The lit stage's switches, by the name of the constant each is: the decision both backends make
|
|
9
9
|
* about which optional code a lit program carries. See `litSwitchesGlsl`.
|
|
10
10
|
*/
|
|
11
|
-
export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT' | 'PHYSICAL_SPECULAR' | 'SURFACE_OVERLAY' | 'REFLECTION_MAPS' | 'WORLD_UVS' | 'MOVING_SUN' | 'LAYERED' | 'LAYER_LOOKS' | 'SURFACE_DETAIL';
|
|
11
|
+
export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT' | 'PHYSICAL_SPECULAR' | 'SURFACE_OVERLAY' | 'REFLECTION_MAPS' | 'WORLD_UVS' | 'MOVING_SUN' | 'LAYERED' | 'LAYER_LOOKS' | 'SURFACE_DETAIL' | 'EMISSIVE_FLOW' | 'IRIDESCENT_OVERLAY' | 'BASE_DECALS';
|
|
12
12
|
/** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
|
|
13
13
|
export declare function noLitSwitches(): Record<LitSwitch, boolean>;
|
|
14
14
|
/** Which of the lit stage's optional features a build reads. See `litSwitchesGlsl`. */
|
|
@@ -150,6 +150,22 @@ export interface FlatShaderOptions {
|
|
|
150
150
|
* (\`SurfaceMaterial.detail\`). True when absent. See \`detailGlsl\`.
|
|
151
151
|
*/
|
|
152
152
|
readonly surfaceDetail?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Whether it moves and masks a material's glow, `uEmissiveFlow` and all
|
|
155
|
+
* (`SurfaceMaterial.emissiveScroll` and `emissiveMaskLayer`). True when absent. See
|
|
156
|
+
* `emissiveFlowGlsl`.
|
|
157
|
+
*/
|
|
158
|
+
readonly emissiveFlow?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Whether it lays a material's iridescent overlay on the base colour, `uIridescence` and all
|
|
161
|
+
* (`SurfaceMaterial.iridescentOverlay`). True when absent. See `iridescentOverlayGlsl`.
|
|
162
|
+
*/
|
|
163
|
+
readonly iridescentOverlay?: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Whether it lays the frame's decals into base colours, `uBaseDecals` and all
|
|
166
|
+
* (`setBaseDecals`). True when absent. See `baseDecalsGlsl`.
|
|
167
|
+
*/
|
|
168
|
+
readonly baseDecals?: boolean;
|
|
153
169
|
/**
|
|
154
170
|
* How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
|
|
155
171
|
*
|
|
@@ -25,6 +25,9 @@ import { reflectionSurfaceGlsl } from './reflectionSurface.js';
|
|
|
25
25
|
import { worldUvGlsl } from './worldUv.js';
|
|
26
26
|
import { movingSunGlsl } from './movingSun.js';
|
|
27
27
|
import { detailGlsl } from './detail.js';
|
|
28
|
+
import { emissiveFlowGlsl } from './emissiveFlow.js';
|
|
29
|
+
import { iridescentOverlayGlsl } from './iridescentOverlay.js';
|
|
30
|
+
import { baseDecalsGlsl } from './baseDecals.js';
|
|
28
31
|
import { layeredGlsl } from './layered.js';
|
|
29
32
|
import { MAP_READS_GLSL } from './mapReads.js';
|
|
30
33
|
import { CHANNEL_BEND, LIT_CHANNEL_ATTRIBUTE } from '../vertexChannel.js';
|
|
@@ -517,6 +520,12 @@ export function flatFrag(options) {
|
|
|
517
520
|
layeredGlsl(options.layered ?? true, options.layerLooks ?? true),
|
|
518
521
|
/* After the layers, so its switch takes the next id; main calls it. See detail.ts. */
|
|
519
522
|
detailGlsl(options.surfaceDetail ?? true),
|
|
523
|
+
/* After the detail, so its switch takes the next id; the map reads call it. emissiveFlow.ts. */
|
|
524
|
+
emissiveFlowGlsl(options.emissiveFlow ?? true),
|
|
525
|
+
/* After the glow's flow, so its switch takes the next id; main calls it. iridescentOverlay.ts. */
|
|
526
|
+
iridescentOverlayGlsl(options.iridescentOverlay ?? true),
|
|
527
|
+
/* After the overlay, so its switch takes the next id; main calls it. baseDecals.ts. */
|
|
528
|
+
baseDecalsGlsl(options.baseDecals ?? true),
|
|
520
529
|
/* Where each map is read, after both of the ways it can be read otherwise. See mapReads.ts. */
|
|
521
530
|
MAP_READS_GLSL,
|
|
522
531
|
/* After the frame and the projection it calls, unconditional for the same reason as the
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A surface's iridescent overlay on its base colour, before it is lit:
|
|
3
|
+
* `SurfaceMaterial.iridescentOverlay`, packed into `uIridescence` by `iridescentOverlay.ts`, whose
|
|
4
|
+
* `iridescentBase` is this arithmetic on the CPU and says what each term is and what it gives up.
|
|
5
|
+
*
|
|
6
|
+
* **Called once the normal is mapped and turned to the viewer**, so the view term reads the surface
|
|
7
|
+
* a pixel shows, and before any light is added, so the lights below read the colour it makes. The
|
|
8
|
+
* mask is read in uniform control flow, under the lighting branch and this switch's, so its implicit
|
|
9
|
+
* level is legal: AGENTS.md 2026-08-07.
|
|
10
|
+
*
|
|
11
|
+
* **A lit switch, `IRIDESCENT_OVERLAY`**, off until a material first asks, declared after
|
|
12
|
+
* `EMISSIVE_FLOW` so each switch before it keeps its id. Compiled out, its thirty-six vectors are
|
|
13
|
+
* not declared and the call is a constant.
|
|
14
|
+
*/
|
|
15
|
+
export declare function iridescentOverlayGlsl(on: boolean): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A surface's iridescent overlay on its base colour, before it is lit:
|
|
3
|
+
* `SurfaceMaterial.iridescentOverlay`, packed into `uIridescence` by `iridescentOverlay.ts`, whose
|
|
4
|
+
* `iridescentBase` is this arithmetic on the CPU and says what each term is and what it gives up.
|
|
5
|
+
*
|
|
6
|
+
* **Called once the normal is mapped and turned to the viewer**, so the view term reads the surface
|
|
7
|
+
* a pixel shows, and before any light is added, so the lights below read the colour it makes. The
|
|
8
|
+
* mask is read in uniform control flow, under the lighting branch and this switch's, so its implicit
|
|
9
|
+
* level is legal: AGENTS.md 2026-08-07.
|
|
10
|
+
*
|
|
11
|
+
* **A lit switch, `IRIDESCENT_OVERLAY`**, off until a material first asks, declared after
|
|
12
|
+
* `EMISSIVE_FLOW` so each switch before it keeps its id. Compiled out, its thirty-six vectors are
|
|
13
|
+
* not declared and the call is a constant.
|
|
14
|
+
*/
|
|
15
|
+
export function iridescentOverlayGlsl(on) {
|
|
16
|
+
if (!on) {
|
|
17
|
+
return /* glsl */ `
|
|
18
|
+
const bool IRIDESCENT_OVERLAY = false; // wgsl:override
|
|
19
|
+
bool iridescent() { return false; }
|
|
20
|
+
vec3 iridescentBase(vec3 base, vec3 n, vec2 uv) { return base; }
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
return /* glsl */ `
|
|
24
|
+
/* Whether this program lays an iridescent overlay. Off but where a material has asked. */
|
|
25
|
+
const bool IRIDESCENT_OVERLAY = true; // wgsl:override
|
|
26
|
+
/*
|
|
27
|
+
* Thirty-six vectors, as iridescentOverlay.ts packs them: the direction toward the sheen and the
|
|
28
|
+
* blend (0); the contrast, the tiling, the weight and 1 for on (1); the mask's array, 0 none, 1 ORM,
|
|
29
|
+
* 2 model, 3 albedo, 4 emissive, its layer and its channel (2); what takes the material's
|
|
30
|
+
* coordinates back to the mesh's own (3); then the ramp, thirty-two colours.
|
|
31
|
+
*/
|
|
32
|
+
uniform vec4 uIridescence[36]; // wgsl:material
|
|
33
|
+
|
|
34
|
+
bool iridescent() { return uIridescence[1].w > 0.5; }
|
|
35
|
+
|
|
36
|
+
/* The mask's channel at the material's coordinates; 1 where it names no array. */
|
|
37
|
+
float iridescentMask(vec2 uv) {
|
|
38
|
+
float source = uIridescence[2].x;
|
|
39
|
+
if (source < 0.5) return 1.0;
|
|
40
|
+
vec3 at = vec3(uv, uIridescence[2].y);
|
|
41
|
+
vec4 texel = source < 1.5 ? texture(uOrmMap, at)
|
|
42
|
+
: source < 2.5 ? texture(uModelMap, at)
|
|
43
|
+
: source < 3.5 ? texture(uAlbedo, at)
|
|
44
|
+
: texture(uEmissiveMap, at);
|
|
45
|
+
return texel[int(uIridescence[2].z + 0.5)];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
vec3 iridescentBase(vec3 base, vec3 n, vec2 uv) {
|
|
49
|
+
vec3 toEye = normalize(uCameraPos - vWorldPos);
|
|
50
|
+
vec3 halfway = toEye + uIridescence[0].xyz;
|
|
51
|
+
float span = length(halfway);
|
|
52
|
+
float nh = span > 0.0 ? dot(n, halfway / span) : 0.0;
|
|
53
|
+
float sheen = nh > 0.0 ? clamp(pow(nh, uIridescence[1].x), 0.0, 1.0) : 0.0;
|
|
54
|
+
float nv = dot(n, toEye);
|
|
55
|
+
vec2 own = vUv.xy * uIridescence[3].xy + uIridescence[3].zw;
|
|
56
|
+
float x = nv + own.x + uIridescence[0].w * (sheen - nv);
|
|
57
|
+
float t = clamp(fract(x * uIridescence[1].y), 0.0, 1.0);
|
|
58
|
+
float at = t * 31.0;
|
|
59
|
+
int below = min(int(floor(at)), 30);
|
|
60
|
+
vec3 o = mix(uIridescence[4 + below].rgb, uIridescence[5 + below].rgb, at - float(below));
|
|
61
|
+
vec3 blended = mix(2.0 * base * o, 1.0 - 2.0 * (1.0 - base) * (1.0 - o), step(0.5, base));
|
|
62
|
+
return base + (blended - base) * (x * iridescentMask(uv) * uIridescence[1].z);
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
@@ -40,7 +40,7 @@ vec3 layeredTexel() { return vec3(0.0, 0.0, 1.0); }
|
|
|
40
40
|
bool layeredMeshNormal() { return false; }
|
|
41
41
|
vec3 layeredMeshTexel() { return vec3(0.0, 0.0, 1.0); }
|
|
42
42
|
bool layeredGlows() { return false; }
|
|
43
|
-
vec3 layeredEmissive() { return vec3(0.0); }
|
|
43
|
+
vec3 layeredEmissive(vec2 shift) { return vec3(0.0); }
|
|
44
44
|
float layeredAmbient() { return 1.0; }
|
|
45
45
|
float layeredSpecular(float own) { return own; }
|
|
46
46
|
`;
|
|
@@ -253,10 +253,10 @@ bool layeredGlows() {
|
|
|
253
253
|
return uLayers[1].z > -0.5;
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
/* The emissive map at its layer's place, glowing by that layer's share. */
|
|
257
|
-
vec3 layeredEmissive() {
|
|
256
|
+
/* The emissive map at its layer's place, moved by its scroll, glowing by that layer's share. */
|
|
257
|
+
vec3 layeredEmissive(vec2 shift) {
|
|
258
258
|
int glow = int(uLayers[1].z + 0.5);
|
|
259
|
-
return texture(uEmissiveMap, layerAt(glow)).rgb * layerShare(glow);
|
|
259
|
+
return texture(uEmissiveMap, layerAt(glow) + vec3(shift, 0.0)).rgb * layerShare(glow);
|
|
260
260
|
}
|
|
261
261
|
`;
|
|
262
262
|
}
|