@driftengine/core 4.13.1 → 4.13.3
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 +42 -0
- package/README.md +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/render/backend/api.d.ts +63 -8
- package/dist/render/backend/webgl2/pointSkinFeedback.d.ts +23 -0
- package/dist/render/backend/webgl2/pointSkinFeedback.js +159 -0
- package/dist/render/backend/webgl2/renderer.d.ts +33 -0
- package/dist/render/backend/webgl2/renderer.js +88 -4
- package/dist/render/backend/webgpu/buffers.d.ts +10 -2
- package/dist/render/backend/webgpu/buffers.js +66 -16
- package/dist/render/backend/webgpu/clothTextures.d.ts +6 -3
- package/dist/render/backend/webgpu/clothTextures.js +14 -11
- package/dist/render/backend/webgpu/drawCommand.js +3 -3
- package/dist/render/backend/webgpu/gpuClothSet.js +1 -0
- package/dist/render/backend/webgpu/groupShelf.d.ts +31 -0
- package/dist/render/backend/webgpu/groupShelf.js +54 -0
- package/dist/render/backend/webgpu/pointSkinPass.d.ts +27 -0
- package/dist/render/backend/webgpu/pointSkinPass.js +104 -0
- package/dist/render/backend/webgpu/renderer.d.ts +40 -7
- package/dist/render/backend/webgpu/renderer.js +151 -48
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +7 -2
- package/dist/render/backend/webgpu/shaders/pointSkin.wgsl.d.ts +13 -0
- package/dist/render/backend/webgpu/shaders/pointSkin.wgsl.js +57 -0
- package/dist/render/backend/webgpu/vertexFormats.js +1 -1
- package/dist/render/lightBudget.d.ts +6 -0
- package/dist/render/lightBudget.js +6 -0
- package/dist/render/mesh.d.ts +19 -5
- package/dist/render/mesh.js +39 -8
- package/dist/render/pointSkin.d.ts +55 -0
- package/dist/render/pointSkin.js +121 -0
- package/dist/render/rendererHarness.js +6 -0
- package/dist/render/shader.d.ts +3 -1
- package/dist/render/shader.js +6 -1
- package/dist/render/shaders/flat/detail.d.ts +21 -0
- package/dist/render/shaders/flat/detail.js +74 -0
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +1 -0
- package/dist/render/shaders/flat/index.d.ts +5 -0
- package/dist/render/shaders/flat/index.js +3 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +2 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +144 -16
- package/dist/render/shaders/generated/flat.wgsl.js +183 -55
- package/dist/render/surfaceDetail.d.ts +79 -0
- package/dist/render/surfaceDetail.js +86 -0
- package/dist/render/surfaceTexture.d.ts +9 -0
- package/dist/render/vertexPacking.d.ts +16 -4
- package/dist/render/vertexPacking.js +10 -3
- package/package.json +6 -6
- package/src/index.ts +4 -0
- package/src/render/backend/api.ts +70 -7
- package/src/render/backend/webgl2/pointSkinFeedback.ts +173 -0
- package/src/render/backend/webgl2/renderer.ts +121 -4
- package/src/render/backend/webgpu/buffers.ts +90 -16
- package/src/render/backend/webgpu/clothTextures.ts +14 -11
- package/src/render/backend/webgpu/drawCommand.ts +3 -3
- package/src/render/backend/webgpu/gpuClothSet.ts +1 -0
- package/src/render/backend/webgpu/groupShelf.ts +74 -0
- package/src/render/backend/webgpu/pointSkinPass.ts +115 -0
- package/src/render/backend/webgpu/renderer.ts +192 -83
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +7 -2
- package/src/render/backend/webgpu/shaders/pointSkin.wgsl.ts +58 -0
- package/src/render/backend/webgpu/vertexFormats.ts +1 -1
- package/src/render/lightBudget.ts +7 -0
- package/src/render/mesh.ts +49 -7
- package/src/render/pointSkin.ts +178 -0
- package/src/render/rendererHarness.ts +6 -0
- package/src/render/shader.ts +5 -0
- package/src/render/shaders/flat/detail.ts +74 -0
- package/src/render/shaders/flat/glassTint.ts +3 -1
- package/src/render/shaders/flat/index.ts +8 -0
- package/src/render/shaders/flat/main.ts +2 -0
- package/src/render/shaders/generated/flat.wgsl.ts +183 -55
- package/src/render/surfaceDetail.ts +136 -0
- package/src/render/surfaceTexture.ts +9 -0
- package/src/render/vertexPacking.ts +25 -5
package/dist/render/mesh.js
CHANGED
|
@@ -92,6 +92,12 @@ export class Mesh {
|
|
|
92
92
|
* this — two references, and only where a consumer asked.
|
|
93
93
|
*/
|
|
94
94
|
deformable = null;
|
|
95
|
+
/** A dynamic mesh's position and normal buffers, for a pose written on the device; else null. */
|
|
96
|
+
get streams() {
|
|
97
|
+
return this.deformable;
|
|
98
|
+
}
|
|
99
|
+
/** The colour buffer, kept so `updateColors` can rewrite it. Null unless `dynamicColors`. */
|
|
100
|
+
recolourable = null;
|
|
95
101
|
/** How many floats the position buffer holds, so an update of the wrong size is refused. */
|
|
96
102
|
positionFloats;
|
|
97
103
|
/**
|
|
@@ -191,8 +197,9 @@ export class Mesh {
|
|
|
191
197
|
* and the default is the fast path: filling a buffer with `bufferData` in one call skips the
|
|
192
198
|
* zero-fill that `bufferData(size)` then `bufferSubData` pays for, and every mesh in the
|
|
193
199
|
* engine that does not need to be spread should keep skipping it.
|
|
200
|
+
* @param dynamicColors Colours as floats in a buffer kept for `updateColors`.
|
|
194
201
|
*/
|
|
195
|
-
constructor(gl, data, dynamic = false, spread = false) {
|
|
202
|
+
constructor(gl, data, dynamic = false, spread = false, dynamicColors = false) {
|
|
196
203
|
validateMeshData(data);
|
|
197
204
|
this.spread = spread;
|
|
198
205
|
this.hasUvs = data.uvs !== undefined;
|
|
@@ -212,12 +219,17 @@ export class Mesh {
|
|
|
212
219
|
*/
|
|
213
220
|
const usage = dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW;
|
|
214
221
|
/* How each attribute travels: sixteen-bit fixed point, or one value as a constant. */
|
|
215
|
-
const packing = vertexPackingOf(data, dynamic);
|
|
222
|
+
const packing = vertexPackingOf(data, dynamic, dynamicColors);
|
|
216
223
|
const positions = this.attachAttribute(gl, ATTR_POSITION, data.positions, 3, usage);
|
|
217
224
|
const normals = this.attachAttribute(gl, ATTR_NORMAL, data.normals, 3, usage, packing.normals);
|
|
218
225
|
if (dynamic)
|
|
219
226
|
this.deformable = { positions, normals };
|
|
220
|
-
|
|
227
|
+
if (packing.colors === 'stream') {
|
|
228
|
+
this.recolourable = this.attachAttribute(gl, ATTR_COLOR, data.colors, 3, gl.DYNAMIC_DRAW);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
this.attachShared(gl, ATTR_COLOR, data.colors, 3, packing.colors);
|
|
232
|
+
}
|
|
221
233
|
this.attachShared(gl, ATTR_EMISSIVE, data.emissive, 1, packing.emissive);
|
|
222
234
|
/*
|
|
223
235
|
* Absent means "not shiny", supplied as a constant rather than as a buffer of
|
|
@@ -418,9 +430,9 @@ export class Mesh {
|
|
|
418
430
|
/**
|
|
419
431
|
* Rewrite this mesh's positions, and its normals where the caller has them.
|
|
420
432
|
*
|
|
421
|
-
* **Only the two attributes that move.**
|
|
422
|
-
*
|
|
423
|
-
*
|
|
433
|
+
* **Only the two attributes that move.** Emissive and the rest describe what a surface *is* and
|
|
434
|
+
* do not change because it bent; rewriting them would be uploading unchanged bytes every frame.
|
|
435
|
+
* A caller whose colours change has `updateColors`, on a mesh made `dynamicColors`.
|
|
424
436
|
*
|
|
425
437
|
* **The bounds are recomputed**, because everything that decides whether this is on screen
|
|
426
438
|
* starts from them — a cloth that blew sideways out of its original box would be culled while
|
|
@@ -445,6 +457,25 @@ export class Mesh {
|
|
|
445
457
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, normals);
|
|
446
458
|
}
|
|
447
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Rewrite this mesh's colours, three floats a vertex, into the buffer they have to themselves.
|
|
462
|
+
*
|
|
463
|
+
* Nothing else moves, the bounds included: a colour does not change where a vertex is.
|
|
464
|
+
*/
|
|
465
|
+
updateColors(gl, colors) {
|
|
466
|
+
if (this.recolourable === null) {
|
|
467
|
+
throw new Error('this mesh was not created with { dynamicColors: true }, so its colours are packed or are ' +
|
|
468
|
+
'one value with no buffer at all, and cannot be rewritten. Say so at `createMesh`: the ' +
|
|
469
|
+
'flag gives the colours a buffer of their own.');
|
|
470
|
+
}
|
|
471
|
+
if (colors.length !== this.positionFloats) {
|
|
472
|
+
throw new Error(`this mesh has ${this.positionFloats / 3} vertices and the colours have ` +
|
|
473
|
+
`${colors.length / 3}. A mesh's vertex count is fixed at creation: the index buffer, ` +
|
|
474
|
+
'the pipeline and every other attribute are sized against it.');
|
|
475
|
+
}
|
|
476
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.recolourable);
|
|
477
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, colors);
|
|
478
|
+
}
|
|
448
479
|
/**
|
|
449
480
|
* One attribute's buffer, as floats or in its sixteen-bit fixed point (`vertexPacking.ts`), which
|
|
450
481
|
* WebGL2 reads back through a normalised `SHORT` or `UNSIGNED_SHORT`: three lanes wide where
|
|
@@ -537,8 +568,8 @@ export class Mesh {
|
|
|
537
568
|
* the allocations. The bounds are the deliberate one — a streamer places and culls a region from
|
|
538
569
|
* them before a byte of it has landed.
|
|
539
570
|
*/
|
|
540
|
-
export function createMeshIncremental(gl, data, dynamic = false) {
|
|
541
|
-
const mesh = new Mesh(gl, data, dynamic, true);
|
|
571
|
+
export function createMeshIncremental(gl, data, dynamic = false, dynamicColors = false) {
|
|
572
|
+
const mesh = new Mesh(gl, data, dynamic, true, dynamicColors);
|
|
542
573
|
return { mesh, upload: mesh.uploads(gl) };
|
|
543
574
|
}
|
|
544
575
|
/** Texture coordinates and layers interleaved as (u, v, layer), one row a vertex. */
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mesh posed on the device from weighted points in its joints' own spaces: the data, its checks,
|
|
3
|
+
* and the bounds a pose is culled by. Each backend's binding poses the vertices.
|
|
4
|
+
*
|
|
5
|
+
* **What it is for.** Some formats store a vertex as up to four points, each in the space of a
|
|
6
|
+
* joint, with a weight each and a normal in the space of the first: posed, the vertex is the
|
|
7
|
+
* weighted sum of each point moved by its joint. That is not ordinary skinning, which moves one
|
|
8
|
+
* bind-pose position by a blend of joints, so a rig of that kind had to be posed on the main thread
|
|
9
|
+
* and its positions uploaded every frame. Reported at 38% of the main thread's drawing time.
|
|
10
|
+
* Posed on the device into a dynamic mesh's own buffers, the main thread hands over the joints.
|
|
11
|
+
*
|
|
12
|
+
* **Into a dynamic mesh rather than in the vertex stage**, because a skinned vertex stage has no
|
|
13
|
+
* attribute left for four more points, and every pass that draws a mesh would need a variant that
|
|
14
|
+
* reads them: lit, cast, glass, motion. Posed once into the mesh's positions and normals, it is an
|
|
15
|
+
* ordinary dynamic mesh to every one of them, motion vectors included. What it costs is the mesh's
|
|
16
|
+
* two buffers, written a pose, and a pass that runs before the draws.
|
|
17
|
+
*
|
|
18
|
+
* **The bounds are a box of spheres, not the posed vertices**, which the CPU never sees. Each joint
|
|
19
|
+
* reaches as far as the farthest point a weighted influence holds in its space, scaled by its
|
|
20
|
+
* largest axis; a weighted sum of points inside those spheres lies inside their box, scaled by the
|
|
21
|
+
* least and greatest sums of a vertex's weights. Conservative, so it culls nothing visible; loose
|
|
22
|
+
* by the joints' reach where a mesh is long and thin, which culls a little less than it could.
|
|
23
|
+
*/
|
|
24
|
+
import type { Bounds } from '../math/bounds.ts';
|
|
25
|
+
/** A point skin, one entry a vertex unless said otherwise. */
|
|
26
|
+
export interface PointSkinData {
|
|
27
|
+
/** Four joint indices a vertex, as floats, as `MeshData.joints` is. */
|
|
28
|
+
readonly joints: Float32Array;
|
|
29
|
+
/** Four weights a vertex. An influence of weight 0 is skipped, whatever its joint and point. */
|
|
30
|
+
readonly weights: Float32Array;
|
|
31
|
+
/** Four points a vertex, three floats each: influence `k`'s point in joint `k`'s own space. */
|
|
32
|
+
readonly points: Float32Array;
|
|
33
|
+
/** One normal a vertex, in the space of its first joint. */
|
|
34
|
+
readonly normals: Float32Array;
|
|
35
|
+
}
|
|
36
|
+
/** How far a skin reaches from each joint, and what its weights sum to: what a pose's bounds need. */
|
|
37
|
+
export interface PointSkinReach {
|
|
38
|
+
/** Joints a pose must hand over: one past the highest any vertex names and uses. */
|
|
39
|
+
readonly joints: number;
|
|
40
|
+
/** For each joint, the farthest point a weighted influence holds in its space; -1 where none. */
|
|
41
|
+
readonly reach: Float32Array;
|
|
42
|
+
/** The least and the greatest sum of one vertex's weights. */
|
|
43
|
+
readonly sums: readonly [number, number];
|
|
44
|
+
}
|
|
45
|
+
/** Refuse a skin whose arrays disagree with a mesh of `vertices`, or that names a joint past the rest. */
|
|
46
|
+
export declare function validatePointSkin(data: PointSkinData, vertices: number): void;
|
|
47
|
+
/** Measure a skin's reach once, at creation. */
|
|
48
|
+
export declare function reachOf(data: PointSkinData): PointSkinReach;
|
|
49
|
+
/**
|
|
50
|
+
* The bounds of a pose, into the mesh's own: `matrices` holds each joint's transform into the
|
|
51
|
+
* mesh's model space, sixteen floats, column-major. Allocates nothing.
|
|
52
|
+
*/
|
|
53
|
+
export declare function poseBounds(matrices: Float32Array, skin: PointSkinReach, out: Bounds): void;
|
|
54
|
+
/** Refuse a pose that hands over fewer joints than the skin names. */
|
|
55
|
+
export declare function validatePose(matrices: Float32Array, skin: PointSkinReach): void;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { MAX_JOINTS } from './skinPalette.js';
|
|
2
|
+
/** Refuse a skin whose arrays disagree with a mesh of `vertices`, or that names a joint past the rest. */
|
|
3
|
+
export function validatePointSkin(data, vertices) {
|
|
4
|
+
const fail = (what) => {
|
|
5
|
+
throw new Error(`point skin: ${what}`);
|
|
6
|
+
};
|
|
7
|
+
for (const [name, values, width] of [
|
|
8
|
+
['joints', data.joints, 4],
|
|
9
|
+
['weights', data.weights, 4],
|
|
10
|
+
['points', data.points, 12],
|
|
11
|
+
['normals', data.normals, 3],
|
|
12
|
+
]) {
|
|
13
|
+
if (values.length !== vertices * width) {
|
|
14
|
+
fail(`${name} holds ${values.length / width} vertices and the mesh has ${vertices}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
for (let at = 0; at < data.joints.length; at++) {
|
|
18
|
+
const joint = data.joints[at];
|
|
19
|
+
if (!(Number.isInteger(joint) && joint >= 0 && joint < MAX_JOINTS)) {
|
|
20
|
+
fail(`vertex ${Math.floor(at / 4)} names joint ${joint}; a joint is 0 to ${MAX_JOINTS - 1}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Measure a skin's reach once, at creation. */
|
|
25
|
+
export function reachOf(data) {
|
|
26
|
+
const reach = new Float32Array(MAX_JOINTS).fill(-1);
|
|
27
|
+
let joints = 0;
|
|
28
|
+
let least = Infinity;
|
|
29
|
+
let most = -Infinity;
|
|
30
|
+
const vertices = data.weights.length / 4;
|
|
31
|
+
for (let v = 0; v < vertices; v++) {
|
|
32
|
+
/* The first joint carries the normal, so it is named whatever its weight. */
|
|
33
|
+
joints = Math.max(joints, data.joints[v * 4] + 1);
|
|
34
|
+
let sum = 0;
|
|
35
|
+
for (let k = 0; k < 4; k++) {
|
|
36
|
+
const weight = data.weights[v * 4 + k];
|
|
37
|
+
if (weight === 0)
|
|
38
|
+
continue;
|
|
39
|
+
sum += weight;
|
|
40
|
+
const joint = data.joints[v * 4 + k];
|
|
41
|
+
joints = Math.max(joints, joint + 1);
|
|
42
|
+
const at = (v * 4 + k) * 3;
|
|
43
|
+
const x = data.points[at];
|
|
44
|
+
const y = data.points[at + 1];
|
|
45
|
+
const z = data.points[at + 2];
|
|
46
|
+
reach[joint] = Math.max(reach[joint], Math.sqrt(x * x + y * y + z * z));
|
|
47
|
+
}
|
|
48
|
+
least = Math.min(least, sum);
|
|
49
|
+
most = Math.max(most, sum);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
joints,
|
|
53
|
+
reach: reach.slice(0, joints),
|
|
54
|
+
sums: vertices === 0 ? [1, 1] : [least, most],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The bounds of a pose, into the mesh's own: `matrices` holds each joint's transform into the
|
|
59
|
+
* mesh's model space, sixteen floats, column-major. Allocates nothing.
|
|
60
|
+
*/
|
|
61
|
+
export function poseBounds(matrices, skin, out) {
|
|
62
|
+
let minX = Infinity;
|
|
63
|
+
let minY = Infinity;
|
|
64
|
+
let minZ = Infinity;
|
|
65
|
+
let maxX = -Infinity;
|
|
66
|
+
let maxY = -Infinity;
|
|
67
|
+
let maxZ = -Infinity;
|
|
68
|
+
for (let j = 0; j < skin.joints; j++) {
|
|
69
|
+
const reach = skin.reach[j];
|
|
70
|
+
if (reach < 0)
|
|
71
|
+
continue;
|
|
72
|
+
const m = j * 16;
|
|
73
|
+
const scale = Math.sqrt(Math.max(columnLength2(matrices, m), columnLength2(matrices, m + 4), columnLength2(matrices, m + 8)));
|
|
74
|
+
const r = reach * scale;
|
|
75
|
+
const x = matrices[m + 12];
|
|
76
|
+
const y = matrices[m + 13];
|
|
77
|
+
const z = matrices[m + 14];
|
|
78
|
+
minX = Math.min(minX, x - r);
|
|
79
|
+
minY = Math.min(minY, y - r);
|
|
80
|
+
minZ = Math.min(minZ, z - r);
|
|
81
|
+
maxX = Math.max(maxX, x + r);
|
|
82
|
+
maxY = Math.max(maxY, y + r);
|
|
83
|
+
maxZ = Math.max(maxZ, z + r);
|
|
84
|
+
}
|
|
85
|
+
if (minX > maxX) {
|
|
86
|
+
out.min.fill(0);
|
|
87
|
+
out.max.fill(0);
|
|
88
|
+
out.centre.fill(0);
|
|
89
|
+
out.radius = 0;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
/* A sum of weights other than one scales the box about the origin, by the least and the most. */
|
|
93
|
+
const [least, most] = skin.sums;
|
|
94
|
+
out.min[0] = Math.min(least * minX, most * minX);
|
|
95
|
+
out.min[1] = Math.min(least * minY, most * minY);
|
|
96
|
+
out.min[2] = Math.min(least * minZ, most * minZ);
|
|
97
|
+
out.max[0] = Math.max(least * maxX, most * maxX);
|
|
98
|
+
out.max[1] = Math.max(least * maxY, most * maxY);
|
|
99
|
+
out.max[2] = Math.max(least * maxZ, most * maxZ);
|
|
100
|
+
let radius2 = 0;
|
|
101
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
102
|
+
const centre = (out.min[axis] + out.max[axis]) / 2;
|
|
103
|
+
out.centre[axis] = centre;
|
|
104
|
+
const half = out.max[axis] - centre;
|
|
105
|
+
radius2 += half * half;
|
|
106
|
+
}
|
|
107
|
+
out.radius = Math.sqrt(radius2);
|
|
108
|
+
}
|
|
109
|
+
/** The squared length of one column's first three components. */
|
|
110
|
+
function columnLength2(m, at) {
|
|
111
|
+
const x = m[at];
|
|
112
|
+
const y = m[at + 1];
|
|
113
|
+
const z = m[at + 2];
|
|
114
|
+
return x * x + y * y + z * z;
|
|
115
|
+
}
|
|
116
|
+
/** Refuse a pose that hands over fewer joints than the skin names. */
|
|
117
|
+
export function validatePose(matrices, skin) {
|
|
118
|
+
if (matrices.length % 16 !== 0 || matrices.length / 16 < skin.joints) {
|
|
119
|
+
throw new Error(`point skin: a pose of ${matrices.length / 16} joints for a skin that names ${skin.joints}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -77,6 +77,12 @@ export function recordingGl(options = {}) {
|
|
|
77
77
|
ONE: 1,
|
|
78
78
|
ONE_MINUS_SRC_ALPHA: 0x0303,
|
|
79
79
|
TRIANGLES: 0x0004,
|
|
80
|
+
/* Transform feedback's, so a point skin's pose can be seen to discard, capture and undo both. */
|
|
81
|
+
POINTS: 0x0000,
|
|
82
|
+
RASTERIZER_DISCARD: 0x8c89,
|
|
83
|
+
TRANSFORM_FEEDBACK: 0x8e22,
|
|
84
|
+
TRANSFORM_FEEDBACK_BUFFER: 0x8c8e,
|
|
85
|
+
SEPARATE_ATTRIBS: 0x8c8d,
|
|
80
86
|
/* The winding a pass names, so a test can tell which one it chose. */
|
|
81
87
|
CW: 0x0900,
|
|
82
88
|
CCW: 0x0901,
|
package/dist/render/shader.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Shader compilation with fail-fast diagnostics (init-time only, never per-frame). */
|
|
2
|
-
export declare function compileProgram(gl: WebGL2RenderingContext, vertexSource: string, fragmentSource: string, label: string
|
|
2
|
+
export declare function compileProgram(gl: WebGL2RenderingContext, vertexSource: string, fragmentSource: string, label: string,
|
|
3
|
+
/** Vertex outputs captured by transform feedback, one buffer each; named before the link. */
|
|
4
|
+
feedback?: readonly string[]): WebGLProgram;
|
|
3
5
|
/**
|
|
4
6
|
* A program compiled and linked without asking whether that worked, since asking waits for the
|
|
5
7
|
* driver to finish: for a context offering `KHR_parallel_shader_compile`, whose
|
package/dist/render/shader.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Shader compilation with fail-fast diagnostics (init-time only, never per-frame). */
|
|
2
|
-
export function compileProgram(gl, vertexSource, fragmentSource, label
|
|
2
|
+
export function compileProgram(gl, vertexSource, fragmentSource, label,
|
|
3
|
+
/** Vertex outputs captured by transform feedback, one buffer each; named before the link. */
|
|
4
|
+
feedback) {
|
|
3
5
|
const vs = compileStage(gl, gl.VERTEX_SHADER, vertexSource, `${label}.vert`);
|
|
4
6
|
const fs = compileStage(gl, gl.FRAGMENT_SHADER, fragmentSource, `${label}.frag`);
|
|
5
7
|
const program = gl.createProgram();
|
|
@@ -7,6 +9,9 @@ export function compileProgram(gl, vertexSource, fragmentSource, label) {
|
|
|
7
9
|
throw new Error(`[${label}] createProgram failed`);
|
|
8
10
|
gl.attachShader(program, vs);
|
|
9
11
|
gl.attachShader(program, fs);
|
|
12
|
+
if (feedback !== undefined) {
|
|
13
|
+
gl.transformFeedbackVaryings(program, feedback, gl.SEPARATE_ATTRIBS);
|
|
14
|
+
}
|
|
10
15
|
gl.linkProgram(program);
|
|
11
16
|
gl.deleteShader(vs);
|
|
12
17
|
gl.deleteShader(fs);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's tiled detail over whatever its maps made: `SurfaceMaterial.detail`, packed into
|
|
3
|
+
* `uDetail` by `surfaceDetail.ts`.
|
|
4
|
+
*
|
|
5
|
+
* **Each texel wears its own region's detail alone.** The region is read exactly from the ORM
|
|
6
|
+
* array's layer the material names, at the base coordinates, so an index is never blended with its
|
|
7
|
+
* neighbour's at a region's edge; then that region's normal map is read at its repeat and offset,
|
|
8
|
+
* turned through a frame built on the normal the base maps made — the construction a layered
|
|
9
|
+
* material lays its layers over a mesh normal with — and its occlusion multiplied into the
|
|
10
|
+
* surface's. A strength scales the detail's slopes as a layer's look does.
|
|
11
|
+
*
|
|
12
|
+
* **Read with explicit gradients**, the base coordinates' times the region's repeat, because the
|
|
13
|
+
* region is the pixel's own: a read whose level is implicit is legal only in uniform control flow
|
|
14
|
+
* (AGENTS.md 2026-08-07), and across a region's edge the repeated coordinates jump, which would
|
|
15
|
+
* read a level as coarse as the jump. The frame and the gradients are taken first, under the
|
|
16
|
+
* uniform branch that calls this.
|
|
17
|
+
*
|
|
18
|
+
* **A lit switch, `SURFACE_DETAIL`**, off until a material first asks, declared after `LAYERED` and
|
|
19
|
+
* `LAYER_LOOKS` so each keeps its id. Compiled out, its thirty-three vectors are not declared.
|
|
20
|
+
*/
|
|
21
|
+
export declare function detailGlsl(on: boolean): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's tiled detail over whatever its maps made: `SurfaceMaterial.detail`, packed into
|
|
3
|
+
* `uDetail` by `surfaceDetail.ts`.
|
|
4
|
+
*
|
|
5
|
+
* **Each texel wears its own region's detail alone.** The region is read exactly from the ORM
|
|
6
|
+
* array's layer the material names, at the base coordinates, so an index is never blended with its
|
|
7
|
+
* neighbour's at a region's edge; then that region's normal map is read at its repeat and offset,
|
|
8
|
+
* turned through a frame built on the normal the base maps made — the construction a layered
|
|
9
|
+
* material lays its layers over a mesh normal with — and its occlusion multiplied into the
|
|
10
|
+
* surface's. A strength scales the detail's slopes as a layer's look does.
|
|
11
|
+
*
|
|
12
|
+
* **Read with explicit gradients**, the base coordinates' times the region's repeat, because the
|
|
13
|
+
* region is the pixel's own: a read whose level is implicit is legal only in uniform control flow
|
|
14
|
+
* (AGENTS.md 2026-08-07), and across a region's edge the repeated coordinates jump, which would
|
|
15
|
+
* read a level as coarse as the jump. The frame and the gradients are taken first, under the
|
|
16
|
+
* uniform branch that calls this.
|
|
17
|
+
*
|
|
18
|
+
* **A lit switch, `SURFACE_DETAIL`**, off until a material first asks, declared after `LAYERED` and
|
|
19
|
+
* `LAYER_LOOKS` so each keeps its id. Compiled out, its thirty-three vectors are not declared.
|
|
20
|
+
*/
|
|
21
|
+
export function detailGlsl(on) {
|
|
22
|
+
if (!on) {
|
|
23
|
+
return /* glsl */ `
|
|
24
|
+
const bool SURFACE_DETAIL = false; // wgsl:override
|
|
25
|
+
bool detailed() { return false; }
|
|
26
|
+
void surfaceDetail(inout vec3 n, inout float occlusion, vec2 uv) {}
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
return /* glsl */ `
|
|
30
|
+
/* Whether this program lays a material's detail. Off but where one has asked: detail.ts. */
|
|
31
|
+
const bool SURFACE_DETAIL = true; // wgsl:override
|
|
32
|
+
/*
|
|
33
|
+
* Thirty-three vectors, as surfaceDetail.ts packs them: the region count (x) and the ORM array's
|
|
34
|
+
* layer holding the region map, or -1 for none (y); then two a region, the normal map's layer (x),
|
|
35
|
+
* the occlusion's or -1 (y), the normal strength (z) and the occlusion strength (w); its repeats
|
|
36
|
+
* across and down (x y) and its offset (z w).
|
|
37
|
+
*/
|
|
38
|
+
uniform vec4 uDetail[33]; // wgsl:material
|
|
39
|
+
|
|
40
|
+
bool detailed() { return uDetail[0].x > 0.0; }
|
|
41
|
+
|
|
42
|
+
/* The region a texel of the base maps is in, read exactly, held to the regions there are. */
|
|
43
|
+
int detailRegion(vec2 uv) {
|
|
44
|
+
int last = int(uDetail[0].x) - 1;
|
|
45
|
+
if (uDetail[0].y < 0.0) return 0;
|
|
46
|
+
ivec2 size = textureSize(uOrmMap, 0).xy;
|
|
47
|
+
ivec2 texel = clamp(ivec2(floor(fract(uv) * vec2(size))), ivec2(0), size - 1);
|
|
48
|
+
int region = int(texelFetch(uOrmMap, ivec3(texel, int(uDetail[0].y)), 0).r * 255.0 + 0.5);
|
|
49
|
+
return clamp(region, 0, last);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void surfaceDetail(inout vec3 n, inout float occlusion, vec2 uv) {
|
|
53
|
+
/* In uniform control flow, before anything that depends on the pixel's region. */
|
|
54
|
+
mat3 frame = tangentFrame(n, vWorldPos, uv, vTangent, vHasTangents);
|
|
55
|
+
vec2 dx = dFdx(uv);
|
|
56
|
+
vec2 dy = dFdy(uv);
|
|
57
|
+
int region = detailRegion(uv);
|
|
58
|
+
vec4 picks = uDetail[1 + region * 2];
|
|
59
|
+
vec4 place = uDetail[2 + region * 2];
|
|
60
|
+
vec2 at = uv * place.xy + place.zw;
|
|
61
|
+
vec2 gx = dx * place.xy;
|
|
62
|
+
vec2 gy = dy * place.xy;
|
|
63
|
+
vec3 d = textureGrad(uModelMap, vec3(at, picks.x), gx, gy).xyz * 2.0 - 1.0;
|
|
64
|
+
/* z rebuilt where none is stored, as the base map's is: normalMap.ts. */
|
|
65
|
+
d.z = d.z > 0.0 ? d.z : sqrt(max(0.0, 1.0 - dot(d.xy, d.xy)));
|
|
66
|
+
d = normalize(vec3(d.xy * picks.z, max(d.z, 1e-4)));
|
|
67
|
+
n = normalize(frame * d);
|
|
68
|
+
if (picks.y >= 0.0) {
|
|
69
|
+
float held = textureGrad(uModelMap, vec3(at, picks.y), gx, gy).r;
|
|
70
|
+
occlusion *= mix(1.0, held, picks.w);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
@@ -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';
|
|
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';
|
|
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`. */
|
|
@@ -145,6 +145,11 @@ export interface FlatShaderOptions {
|
|
|
145
145
|
* \`uLayerLooks\` and all. True when absent; nothing without \`layered\`. See \`layerLooksGlsl\`.
|
|
146
146
|
*/
|
|
147
147
|
readonly layerLooks?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Whether it lays a material's tiled detail over its maps, \`uDetail\` and all
|
|
150
|
+
* (\`SurfaceMaterial.detail\`). True when absent. See \`detailGlsl\`.
|
|
151
|
+
*/
|
|
152
|
+
readonly surfaceDetail?: boolean;
|
|
148
153
|
/**
|
|
149
154
|
* How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
|
|
150
155
|
*
|
|
@@ -24,6 +24,7 @@ import { overlayGlsl } from './overlay.js';
|
|
|
24
24
|
import { reflectionSurfaceGlsl } from './reflectionSurface.js';
|
|
25
25
|
import { worldUvGlsl } from './worldUv.js';
|
|
26
26
|
import { movingSunGlsl } from './movingSun.js';
|
|
27
|
+
import { detailGlsl } from './detail.js';
|
|
27
28
|
import { layeredGlsl } from './layered.js';
|
|
28
29
|
import { MAP_READS_GLSL } from './mapReads.js';
|
|
29
30
|
import { CHANNEL_BEND, LIT_CHANNEL_ATTRIBUTE } from '../vertexChannel.js';
|
|
@@ -514,6 +515,8 @@ export function flatFrag(options) {
|
|
|
514
515
|
worldUvGlsl(options.worldUvs ?? true),
|
|
515
516
|
/* After the projection, so its switch takes the next id; the normal map below calls it. */
|
|
516
517
|
layeredGlsl(options.layered ?? true, options.layerLooks ?? true),
|
|
518
|
+
/* After the layers, so its switch takes the next id; main calls it. See detail.ts. */
|
|
519
|
+
detailGlsl(options.surfaceDetail ?? true),
|
|
517
520
|
/* Where each map is read, after both of the ways it can be read otherwise. See mapReads.ts. */
|
|
518
521
|
MAP_READS_GLSL,
|
|
519
522
|
/* After the frame and the projection it calls, unconditional for the same reason as the
|