@driftengine/core 4.13.0 → 4.13.2
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 +2 -1
- 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 +21 -0
- package/dist/render/backend/webgl2/renderer.js +41 -2
- 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 +31 -7
- package/dist/render/backend/webgpu/renderer.js +118 -47
- 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/vertexPacking.d.ts +16 -4
- package/dist/render/vertexPacking.js +10 -3
- package/package.json +6 -6
- package/src/index.ts +2 -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 +63 -2
- 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 +150 -82
- 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/vertexPacking.ts +25 -5
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
import { MAX_JOINTS } from './skinPalette.ts';
|
|
26
|
+
|
|
27
|
+
/** A point skin, one entry a vertex unless said otherwise. */
|
|
28
|
+
export interface PointSkinData {
|
|
29
|
+
/** Four joint indices a vertex, as floats, as `MeshData.joints` is. */
|
|
30
|
+
readonly joints: Float32Array;
|
|
31
|
+
/** Four weights a vertex. An influence of weight 0 is skipped, whatever its joint and point. */
|
|
32
|
+
readonly weights: Float32Array;
|
|
33
|
+
/** Four points a vertex, three floats each: influence `k`'s point in joint `k`'s own space. */
|
|
34
|
+
readonly points: Float32Array;
|
|
35
|
+
/** One normal a vertex, in the space of its first joint. */
|
|
36
|
+
readonly normals: Float32Array;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** How far a skin reaches from each joint, and what its weights sum to: what a pose's bounds need. */
|
|
40
|
+
export interface PointSkinReach {
|
|
41
|
+
/** Joints a pose must hand over: one past the highest any vertex names and uses. */
|
|
42
|
+
readonly joints: number;
|
|
43
|
+
/** For each joint, the farthest point a weighted influence holds in its space; -1 where none. */
|
|
44
|
+
readonly reach: Float32Array;
|
|
45
|
+
/** The least and the greatest sum of one vertex's weights. */
|
|
46
|
+
readonly sums: readonly [number, number];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Refuse a skin whose arrays disagree with a mesh of `vertices`, or that names a joint past the rest. */
|
|
50
|
+
export function validatePointSkin(data: PointSkinData, vertices: number): void {
|
|
51
|
+
const fail = (what: string): never => {
|
|
52
|
+
throw new Error(`point skin: ${what}`);
|
|
53
|
+
};
|
|
54
|
+
for (const [name, values, width] of [
|
|
55
|
+
['joints', data.joints, 4],
|
|
56
|
+
['weights', data.weights, 4],
|
|
57
|
+
['points', data.points, 12],
|
|
58
|
+
['normals', data.normals, 3],
|
|
59
|
+
] as const) {
|
|
60
|
+
if (values.length !== vertices * width) {
|
|
61
|
+
fail(`${name} holds ${values.length / width} vertices and the mesh has ${vertices}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (let at = 0; at < data.joints.length; at++) {
|
|
65
|
+
const joint = data.joints[at] as number;
|
|
66
|
+
if (!(Number.isInteger(joint) && joint >= 0 && joint < MAX_JOINTS)) {
|
|
67
|
+
fail(`vertex ${Math.floor(at / 4)} names joint ${joint}; a joint is 0 to ${MAX_JOINTS - 1}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Measure a skin's reach once, at creation. */
|
|
73
|
+
export function reachOf(data: PointSkinData): PointSkinReach {
|
|
74
|
+
const reach = new Float32Array(MAX_JOINTS).fill(-1);
|
|
75
|
+
let joints = 0;
|
|
76
|
+
let least = Infinity;
|
|
77
|
+
let most = -Infinity;
|
|
78
|
+
const vertices = data.weights.length / 4;
|
|
79
|
+
for (let v = 0; v < vertices; v++) {
|
|
80
|
+
/* The first joint carries the normal, so it is named whatever its weight. */
|
|
81
|
+
joints = Math.max(joints, (data.joints[v * 4] as number) + 1);
|
|
82
|
+
let sum = 0;
|
|
83
|
+
for (let k = 0; k < 4; k++) {
|
|
84
|
+
const weight = data.weights[v * 4 + k] as number;
|
|
85
|
+
if (weight === 0) continue;
|
|
86
|
+
sum += weight;
|
|
87
|
+
const joint = data.joints[v * 4 + k] as number;
|
|
88
|
+
joints = Math.max(joints, joint + 1);
|
|
89
|
+
const at = (v * 4 + k) * 3;
|
|
90
|
+
const x = data.points[at] as number;
|
|
91
|
+
const y = data.points[at + 1] as number;
|
|
92
|
+
const z = data.points[at + 2] as number;
|
|
93
|
+
reach[joint] = Math.max(reach[joint] as number, Math.sqrt(x * x + y * y + z * z));
|
|
94
|
+
}
|
|
95
|
+
least = Math.min(least, sum);
|
|
96
|
+
most = Math.max(most, sum);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
joints,
|
|
100
|
+
reach: reach.slice(0, joints),
|
|
101
|
+
sums: vertices === 0 ? [1, 1] : [least, most],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The bounds of a pose, into the mesh's own: `matrices` holds each joint's transform into the
|
|
107
|
+
* mesh's model space, sixteen floats, column-major. Allocates nothing.
|
|
108
|
+
*/
|
|
109
|
+
export function poseBounds(matrices: Float32Array, skin: PointSkinReach, out: Bounds): void {
|
|
110
|
+
let minX = Infinity;
|
|
111
|
+
let minY = Infinity;
|
|
112
|
+
let minZ = Infinity;
|
|
113
|
+
let maxX = -Infinity;
|
|
114
|
+
let maxY = -Infinity;
|
|
115
|
+
let maxZ = -Infinity;
|
|
116
|
+
for (let j = 0; j < skin.joints; j++) {
|
|
117
|
+
const reach = skin.reach[j] as number;
|
|
118
|
+
if (reach < 0) continue;
|
|
119
|
+
const m = j * 16;
|
|
120
|
+
const scale = Math.sqrt(
|
|
121
|
+
Math.max(
|
|
122
|
+
columnLength2(matrices, m),
|
|
123
|
+
columnLength2(matrices, m + 4),
|
|
124
|
+
columnLength2(matrices, m + 8),
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
const r = reach * scale;
|
|
128
|
+
const x = matrices[m + 12] as number;
|
|
129
|
+
const y = matrices[m + 13] as number;
|
|
130
|
+
const z = matrices[m + 14] as number;
|
|
131
|
+
minX = Math.min(minX, x - r);
|
|
132
|
+
minY = Math.min(minY, y - r);
|
|
133
|
+
minZ = Math.min(minZ, z - r);
|
|
134
|
+
maxX = Math.max(maxX, x + r);
|
|
135
|
+
maxY = Math.max(maxY, y + r);
|
|
136
|
+
maxZ = Math.max(maxZ, z + r);
|
|
137
|
+
}
|
|
138
|
+
if (minX > maxX) {
|
|
139
|
+
out.min.fill(0);
|
|
140
|
+
out.max.fill(0);
|
|
141
|
+
out.centre.fill(0);
|
|
142
|
+
out.radius = 0;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
/* A sum of weights other than one scales the box about the origin, by the least and the most. */
|
|
146
|
+
const [least, most] = skin.sums;
|
|
147
|
+
out.min[0] = Math.min(least * minX, most * minX);
|
|
148
|
+
out.min[1] = Math.min(least * minY, most * minY);
|
|
149
|
+
out.min[2] = Math.min(least * minZ, most * minZ);
|
|
150
|
+
out.max[0] = Math.max(least * maxX, most * maxX);
|
|
151
|
+
out.max[1] = Math.max(least * maxY, most * maxY);
|
|
152
|
+
out.max[2] = Math.max(least * maxZ, most * maxZ);
|
|
153
|
+
let radius2 = 0;
|
|
154
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
155
|
+
const centre = ((out.min[axis] as number) + (out.max[axis] as number)) / 2;
|
|
156
|
+
out.centre[axis] = centre;
|
|
157
|
+
const half = (out.max[axis] as number) - centre;
|
|
158
|
+
radius2 += half * half;
|
|
159
|
+
}
|
|
160
|
+
out.radius = Math.sqrt(radius2);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The squared length of one column's first three components. */
|
|
164
|
+
function columnLength2(m: Float32Array, at: number): number {
|
|
165
|
+
const x = m[at] as number;
|
|
166
|
+
const y = m[at + 1] as number;
|
|
167
|
+
const z = m[at + 2] as number;
|
|
168
|
+
return x * x + y * y + z * z;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Refuse a pose that hands over fewer joints than the skin names. */
|
|
172
|
+
export function validatePose(matrices: Float32Array, skin: PointSkinReach): void {
|
|
173
|
+
if (matrices.length % 16 !== 0 || matrices.length / 16 < skin.joints) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`point skin: a pose of ${matrices.length / 16} joints for a skin that names ${skin.joints}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -125,6 +125,12 @@ export function recordingGl(
|
|
|
125
125
|
ONE: 1,
|
|
126
126
|
ONE_MINUS_SRC_ALPHA: 0x0303,
|
|
127
127
|
TRIANGLES: 0x0004,
|
|
128
|
+
/* Transform feedback's, so a point skin's pose can be seen to discard, capture and undo both. */
|
|
129
|
+
POINTS: 0x0000,
|
|
130
|
+
RASTERIZER_DISCARD: 0x8c89,
|
|
131
|
+
TRANSFORM_FEEDBACK: 0x8e22,
|
|
132
|
+
TRANSFORM_FEEDBACK_BUFFER: 0x8c8e,
|
|
133
|
+
SEPARATE_ATTRIBS: 0x8c8d,
|
|
128
134
|
/* The winding a pass names, so a test can tell which one it chose. */
|
|
129
135
|
CW: 0x0900,
|
|
130
136
|
CCW: 0x0901,
|
package/src/render/shader.ts
CHANGED
|
@@ -5,6 +5,8 @@ export function compileProgram(
|
|
|
5
5
|
vertexSource: string,
|
|
6
6
|
fragmentSource: string,
|
|
7
7
|
label: string,
|
|
8
|
+
/** Vertex outputs captured by transform feedback, one buffer each; named before the link. */
|
|
9
|
+
feedback?: readonly string[],
|
|
8
10
|
): WebGLProgram {
|
|
9
11
|
const vs = compileStage(gl, gl.VERTEX_SHADER, vertexSource, `${label}.vert`);
|
|
10
12
|
const fs = compileStage(gl, gl.FRAGMENT_SHADER, fragmentSource, `${label}.frag`);
|
|
@@ -14,6 +16,9 @@ export function compileProgram(
|
|
|
14
16
|
|
|
15
17
|
gl.attachShader(program, vs);
|
|
16
18
|
gl.attachShader(program, fs);
|
|
19
|
+
if (feedback !== undefined) {
|
|
20
|
+
gl.transformFeedbackVaryings(program, feedback as string[], gl.SEPARATE_ATTRIBS);
|
|
21
|
+
}
|
|
17
22
|
gl.linkProgram(program);
|
|
18
23
|
gl.deleteShader(vs);
|
|
19
24
|
gl.deleteShader(fs);
|
|
@@ -23,10 +23,19 @@ import type { MeshData } from '@driftengine/drft';
|
|
|
23
23
|
* on the CPU. What would make this wrong is a value outside its range, which the range check sends
|
|
24
24
|
* to floats rather than clamping, or a shader reading an attribute as something other than the
|
|
25
25
|
* value it carries.
|
|
26
|
+
*
|
|
27
|
+
* **A mesh whose colours are rewritten keeps them as floats in a buffer of their own** (`stream`),
|
|
28
|
+
* whatever they are: one value every vertex shares has no buffer to rewrite, sixteen bits would
|
|
29
|
+
* have to be packed on the CPU every rewrite, and colours interleaved with the rest of a vertex
|
|
30
|
+
* could only be rewritten by keeping the rows, which a mesh lets go once it is uploaded. It costs
|
|
31
|
+
* twelve bytes a vertex where the colours might have cost none, which is why a mesh asks for it.
|
|
26
32
|
*/
|
|
27
33
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Floats; sixteen-bit fixed point, signed or not; one value for every vertex; or floats in a
|
|
36
|
+
* buffer of their own, for an attribute a caller rewrites.
|
|
37
|
+
*/
|
|
38
|
+
export type AttributePacking = 'float' | 'snorm16' | 'unorm16' | 'constant' | 'stream';
|
|
30
39
|
|
|
31
40
|
/** The attributes this decides for. Every other one travels as floats. */
|
|
32
41
|
export const PACKED_ATTRIBUTES = [
|
|
@@ -42,12 +51,23 @@ export type PackedAttribute = (typeof PACKED_ATTRIBUTES)[number];
|
|
|
42
51
|
|
|
43
52
|
export type VertexPacking = Readonly<Record<PackedAttribute, AttributePacking>>;
|
|
44
53
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
54
|
+
/**
|
|
55
|
+
* The choice for this mesh. `dynamic` is a mesh whose positions and normals are rewritten, and
|
|
56
|
+
* `dynamicColors` one whose colours are.
|
|
57
|
+
*/
|
|
58
|
+
export function vertexPackingOf(
|
|
59
|
+
data: MeshData,
|
|
60
|
+
dynamic: boolean,
|
|
61
|
+
dynamicColors = false,
|
|
62
|
+
): VertexPacking {
|
|
47
63
|
return {
|
|
48
64
|
normals: dynamic ? 'float' : fixedOrFloat(data.normals, -1),
|
|
49
65
|
tangents: fixedOrFloat(data.tangents, -1),
|
|
50
|
-
colors:
|
|
66
|
+
colors: dynamicColors
|
|
67
|
+
? 'stream'
|
|
68
|
+
: shared(data.colors, 3)
|
|
69
|
+
? 'constant'
|
|
70
|
+
: fixedOrFloat(data.colors, 0),
|
|
51
71
|
emissive: shared(data.emissive, 1) ? 'constant' : 'float',
|
|
52
72
|
weights: fixedOrFloat(data.weights, 0),
|
|
53
73
|
weights2: fixedOrFloat(data.weights2, 0),
|