@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
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where every mover was last frame, on WebGL2: the motion its temporal resolve follows.
|
|
3
|
+
*
|
|
4
|
+
* **What it is for.** The resolve reprojects last frame's picture by the camera's motion, read
|
|
5
|
+
* back from the depth, which is exact for a world standing still and wrong for whatever moved: a
|
|
6
|
+
* head turning or a boot in a kick leaves a trail of its earlier pixels. WebGPU's reconstruction
|
|
7
|
+
* follows each mover's own motion through `shaders/recon/motion.wgsl.ts`; this is the same idea
|
|
8
|
+
* for the other backend. The frame's movers are recorded as they are drawn and drawn again at the
|
|
9
|
+
* end of the frame into a target of their own, each texel saying where the surface it shows stood
|
|
10
|
+
* last frame: the offset to it on screen, its distance from last frame's eye, and that it is a
|
|
11
|
+
* mover's. The resolve reads it where a mover's texel is and the camera's reprojection everywhere
|
|
12
|
+
* else.
|
|
13
|
+
*
|
|
14
|
+
* **Three kinds**, as `recordDrawMotion` on WebGPU decides them: a rigid mesh under last frame's
|
|
15
|
+
* model; a skinned one under last frame's palette as well; and a mesh rewritten in place, whose
|
|
16
|
+
* last frame's positions `Mesh.keepPrevious` copied before the first rewrite of the frame. A cloth
|
|
17
|
+
* draw and an instanced batch keep the camera's motion here, which the renderer says once.
|
|
18
|
+
*
|
|
19
|
+
* **Drawn against the frame's own depth**, attached and never written, with the test passing at its
|
|
20
|
+
* own surface and a nudge toward the eye to forgive the last bit of arithmetic: rasterised through
|
|
21
|
+
* the frame's jittered matrix so it covers the pixels the surface did, its motion taken between the
|
|
22
|
+
* unjittered ones. **What it gives up**: a mover hidden behind glass or seen in a mirror keeps the
|
|
23
|
+
* camera's motion; a multisampled frame keeps it everywhere; and a morph target's change of shape
|
|
24
|
+
* is not followed.
|
|
25
|
+
*/
|
|
26
|
+
import { MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT } from '../../lightBudget.ts';
|
|
27
|
+
import { compileProgram, uniformLocations } from '../../shader.ts';
|
|
28
|
+
import { SkinPaletteTexture } from './skinPaletteTexture.ts';
|
|
29
|
+
import type { Mesh } from '../../mesh.ts';
|
|
30
|
+
|
|
31
|
+
const RIGID = 0;
|
|
32
|
+
const SKINNED = 1;
|
|
33
|
+
const DYNAMIC = 2;
|
|
34
|
+
type Kind = typeof RIGID | typeof SKINNED | typeof DYNAMIC;
|
|
35
|
+
|
|
36
|
+
/** The vertex stage for one kind, skinned with eight influences where `eight` says. */
|
|
37
|
+
function vertexSource(kind: Kind, eight: boolean): string {
|
|
38
|
+
return /* glsl */ `#version 300 es
|
|
39
|
+
precision highp float;
|
|
40
|
+
precision highp int;
|
|
41
|
+
layout(location = 0) in vec3 aPosition;
|
|
42
|
+
layout(location = 1) in vec3 aWas;
|
|
43
|
+
layout(location = 11) in vec4 aJoints;
|
|
44
|
+
layout(location = 12) in vec4 aWeights;
|
|
45
|
+
layout(location = 14) in vec4 aJoints2;
|
|
46
|
+
layout(location = 15) in vec4 aWeights2;
|
|
47
|
+
uniform mat4 uModel;
|
|
48
|
+
uniform mat4 uWasModel;
|
|
49
|
+
uniform mat4 uJittered;
|
|
50
|
+
uniform mat4 uViewProj;
|
|
51
|
+
uniform mat4 uWasViewProj;
|
|
52
|
+
uniform highp sampler2D uPaletteNow;
|
|
53
|
+
uniform highp sampler2D uPaletteWas;
|
|
54
|
+
out vec4 vNow;
|
|
55
|
+
out vec4 vWas;
|
|
56
|
+
|
|
57
|
+
mat4 joint(highp sampler2D palette, float index) {
|
|
58
|
+
int x = int(index) * 4;
|
|
59
|
+
return mat4(
|
|
60
|
+
texelFetch(palette, ivec2(x, 0), 0),
|
|
61
|
+
texelFetch(palette, ivec2(x + 1, 0), 0),
|
|
62
|
+
texelFetch(palette, ivec2(x + 2, 0), 0),
|
|
63
|
+
texelFetch(palette, ivec2(x + 3, 0), 0));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* The same blend as skinning.ts, joint by joint, so a skinned mover lands where its draw did. */
|
|
67
|
+
mat4 skin(highp sampler2D palette) {
|
|
68
|
+
mat4 m = joint(palette, aJoints.x) * aWeights.x + joint(palette, aJoints.y) * aWeights.y
|
|
69
|
+
+ joint(palette, aJoints.z) * aWeights.z + joint(palette, aJoints.w) * aWeights.w;
|
|
70
|
+
${
|
|
71
|
+
eight
|
|
72
|
+
? `m += joint(palette, aJoints2.x) * aWeights2.x + joint(palette, aJoints2.y) * aWeights2.y
|
|
73
|
+
+ joint(palette, aJoints2.z) * aWeights2.z + joint(palette, aJoints2.w) * aWeights2.w;`
|
|
74
|
+
: ''
|
|
75
|
+
}
|
|
76
|
+
return m;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
void main() {
|
|
80
|
+
vec4 now = vec4(aPosition, 1.0);
|
|
81
|
+
vec4 was = now;
|
|
82
|
+
${
|
|
83
|
+
kind === SKINNED
|
|
84
|
+
? 'was = skin(uPaletteWas) * now;\n now = skin(uPaletteNow) * now;'
|
|
85
|
+
: kind === DYNAMIC
|
|
86
|
+
? 'was = vec4(aWas, 1.0);'
|
|
87
|
+
: ''
|
|
88
|
+
}
|
|
89
|
+
vec4 world = uModel * now;
|
|
90
|
+
gl_Position = uJittered * world;
|
|
91
|
+
vNow = uViewProj * world;
|
|
92
|
+
vWas = uWasViewProj * (uWasModel * was);
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const FRAGMENT = /* glsl */ `#version 300 es
|
|
98
|
+
precision highp float;
|
|
99
|
+
in vec4 vNow;
|
|
100
|
+
in vec4 vWas;
|
|
101
|
+
out vec4 motion;
|
|
102
|
+
/* The offset on screen to where this surface stood, its distance from last frame's eye, and 1. */
|
|
103
|
+
void main() {
|
|
104
|
+
vec2 now = vNow.xy / vNow.w * 0.5 + 0.5;
|
|
105
|
+
vec2 was = vWas.xy / max(vWas.w, 1e-6) * 0.5 + 0.5;
|
|
106
|
+
motion = vec4(was - now, vWas.w, 1.0);
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
interface Program {
|
|
111
|
+
readonly program: WebGLProgram;
|
|
112
|
+
readonly uniforms: Record<string, WebGLUniformLocation>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class GlMotionPass {
|
|
116
|
+
private readonly programs = new Map<number, Program>();
|
|
117
|
+
private texture: WebGLTexture | null = null;
|
|
118
|
+
private framebuffer: WebGLFramebuffer | null = null;
|
|
119
|
+
private width = 0;
|
|
120
|
+
private height = 0;
|
|
121
|
+
private depth: WebGLTexture | null = null;
|
|
122
|
+
/** This frame's palette and last frame's, uploaded record by record. */
|
|
123
|
+
private readonly paletteNow = new SkinPaletteTexture();
|
|
124
|
+
private readonly paletteWas = new SkinPaletteTexture();
|
|
125
|
+
|
|
126
|
+
/** The frame's movers: mesh, kind, two matrices, two palettes. Grown, never shrunk. */
|
|
127
|
+
private readonly meshes: Mesh[] = [];
|
|
128
|
+
private kinds = new Uint8Array(16);
|
|
129
|
+
private matrices = new Float32Array(16 * 32);
|
|
130
|
+
private readonly palettes: (Float32Array | null)[] = [];
|
|
131
|
+
private readonly wasPalettes: (Float32Array | null)[] = [];
|
|
132
|
+
private count = 0;
|
|
133
|
+
|
|
134
|
+
/** A frame states its movers afresh. */
|
|
135
|
+
reset(): void {
|
|
136
|
+
this.count = 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get recorded(): number {
|
|
140
|
+
return this.count;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** A rigid mover under last frame's model. */
|
|
144
|
+
recordRigid(mesh: Mesh, model: ArrayLike<number>, wasModel: ArrayLike<number>): void {
|
|
145
|
+
this.take(mesh, RIGID, model, wasModel, null, null);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** A skinned mover under last frame's model and palette. */
|
|
149
|
+
recordSkinned(
|
|
150
|
+
mesh: Mesh,
|
|
151
|
+
model: ArrayLike<number>,
|
|
152
|
+
wasModel: ArrayLike<number>,
|
|
153
|
+
palette: Float32Array,
|
|
154
|
+
wasPalette: Float32Array,
|
|
155
|
+
): void {
|
|
156
|
+
this.take(mesh, SKINNED, model, wasModel, palette, wasPalette);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** A mesh rewritten this frame, under last frame's positions and model. */
|
|
160
|
+
recordDynamic(mesh: Mesh, model: ArrayLike<number>, wasModel: ArrayLike<number>): void {
|
|
161
|
+
this.take(mesh, DYNAMIC, model, wasModel, null, null);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private take(
|
|
165
|
+
mesh: Mesh,
|
|
166
|
+
kind: Kind,
|
|
167
|
+
model: ArrayLike<number>,
|
|
168
|
+
wasModel: ArrayLike<number>,
|
|
169
|
+
palette: Float32Array | null,
|
|
170
|
+
wasPalette: Float32Array | null,
|
|
171
|
+
): void {
|
|
172
|
+
const at = this.count;
|
|
173
|
+
if (at >= this.kinds.length) {
|
|
174
|
+
const kinds = new Uint8Array(this.kinds.length * 2);
|
|
175
|
+
kinds.set(this.kinds);
|
|
176
|
+
this.kinds = kinds;
|
|
177
|
+
const matrices = new Float32Array(this.matrices.length * 2);
|
|
178
|
+
matrices.set(this.matrices);
|
|
179
|
+
this.matrices = matrices;
|
|
180
|
+
}
|
|
181
|
+
this.meshes[at] = mesh;
|
|
182
|
+
this.kinds[at] = kind;
|
|
183
|
+
this.matrices.set(model, at * 32);
|
|
184
|
+
this.matrices.set(wasModel, at * 32 + 16);
|
|
185
|
+
this.palettes[at] = palette;
|
|
186
|
+
this.wasPalettes[at] = wasPalette;
|
|
187
|
+
this.count = at + 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Draw the recorded movers into the motion target against `depth`, and return the target; null
|
|
192
|
+
* where nothing was recorded. Leaves the framebuffer, program and array unbound, the depth test on
|
|
193
|
+
* with the frame's compare and writes on, no offset, and the palette units holding its palettes.
|
|
194
|
+
*/
|
|
195
|
+
draw(
|
|
196
|
+
gl: WebGL2RenderingContext,
|
|
197
|
+
depth: WebGLTexture,
|
|
198
|
+
width: number,
|
|
199
|
+
height: number,
|
|
200
|
+
reversedDepth: boolean,
|
|
201
|
+
jittered: Float32Array,
|
|
202
|
+
viewProj: Float32Array,
|
|
203
|
+
wasViewProj: Float32Array,
|
|
204
|
+
): WebGLTexture | null {
|
|
205
|
+
if (this.count === 0) return null;
|
|
206
|
+
this.ensureTarget(gl, depth, width, height);
|
|
207
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
208
|
+
gl.viewport(0, 0, width, height);
|
|
209
|
+
gl.clearColor(0, 0, 0, 0);
|
|
210
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
211
|
+
gl.enable(gl.DEPTH_TEST);
|
|
212
|
+
gl.depthMask(false);
|
|
213
|
+
gl.depthFunc(reversedDepth ? gl.GEQUAL : gl.LEQUAL);
|
|
214
|
+
/* Toward the eye, which is a larger depth when depth is reversed. */
|
|
215
|
+
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
216
|
+
gl.polygonOffset(reversedDepth ? 1 : -1, reversedDepth ? 4 : -4);
|
|
217
|
+
gl.disable(gl.BLEND);
|
|
218
|
+
gl.disable(gl.CULL_FACE);
|
|
219
|
+
for (let i = 0; i < this.count; i++) {
|
|
220
|
+
const mesh = this.meshes[i] as Mesh;
|
|
221
|
+
const kind = this.kinds[i] as Kind;
|
|
222
|
+
const eight = kind === SKINNED && mesh.isSkinnedEight;
|
|
223
|
+
const { program, uniforms } = this.programFor(gl, kind, eight);
|
|
224
|
+
gl.useProgram(program);
|
|
225
|
+
gl.uniformMatrix4fv(uniforms['uJittered'] ?? null, false, jittered);
|
|
226
|
+
gl.uniformMatrix4fv(uniforms['uViewProj'] ?? null, false, viewProj);
|
|
227
|
+
gl.uniformMatrix4fv(uniforms['uWasViewProj'] ?? null, false, wasViewProj);
|
|
228
|
+
gl.uniformMatrix4fv(uniforms['uModel'] ?? null, false, this.matrices, i * 32, 16);
|
|
229
|
+
gl.uniformMatrix4fv(uniforms['uWasModel'] ?? null, false, this.matrices, i * 32 + 16, 16);
|
|
230
|
+
if (kind === SKINNED) {
|
|
231
|
+
/* On the units a skinned draw binds its palette and morph on, which every one binds again
|
|
232
|
+
for itself: nothing the lit pass keeps across frames is moved. */
|
|
233
|
+
gl.activeTexture(gl.TEXTURE0 + SKIN_PALETTE_TEXTURE_UNIT);
|
|
234
|
+
this.paletteNow.update(gl, this.palettes[i] as Float32Array);
|
|
235
|
+
gl.activeTexture(gl.TEXTURE0 + MORPH_DELTA_TEXTURE_UNIT);
|
|
236
|
+
this.paletteWas.update(gl, this.wasPalettes[i] as Float32Array);
|
|
237
|
+
gl.uniform1i(uniforms['uPaletteNow'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
238
|
+
gl.uniform1i(uniforms['uPaletteWas'] ?? null, MORPH_DELTA_TEXTURE_UNIT);
|
|
239
|
+
}
|
|
240
|
+
mesh.drawMotion(gl, kind === DYNAMIC);
|
|
241
|
+
}
|
|
242
|
+
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
243
|
+
gl.polygonOffset(0, 0);
|
|
244
|
+
gl.depthMask(true);
|
|
245
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
246
|
+
gl.useProgram(null);
|
|
247
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
248
|
+
return this.texture;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private programFor(gl: WebGL2RenderingContext, kind: Kind, eight: boolean): Program {
|
|
252
|
+
const key = kind * 2 + (eight ? 1 : 0);
|
|
253
|
+
const held = this.programs.get(key);
|
|
254
|
+
if (held !== undefined) return held;
|
|
255
|
+
const program = compileProgram(gl, vertexSource(kind, eight), FRAGMENT, `motion.${key}`);
|
|
256
|
+
const made = { program, uniforms: uniformLocations(gl, program, `motion.${key}`) };
|
|
257
|
+
this.programs.set(key, made);
|
|
258
|
+
return made;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** An `RGBA16F` target the frame's size, its depth the frame's own attachment. */
|
|
262
|
+
private ensureTarget(
|
|
263
|
+
gl: WebGL2RenderingContext,
|
|
264
|
+
depth: WebGLTexture,
|
|
265
|
+
width: number,
|
|
266
|
+
height: number,
|
|
267
|
+
): void {
|
|
268
|
+
if (this.texture !== null && this.width === width && this.height === height) {
|
|
269
|
+
if (this.depth === depth) return;
|
|
270
|
+
} else {
|
|
271
|
+
if (this.texture !== null) gl.deleteTexture(this.texture);
|
|
272
|
+
this.texture = gl.createTexture();
|
|
273
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
274
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA16F, width, height);
|
|
275
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
276
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
277
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
278
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
279
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
280
|
+
this.width = width;
|
|
281
|
+
this.height = height;
|
|
282
|
+
}
|
|
283
|
+
this.framebuffer ??= gl.createFramebuffer();
|
|
284
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
285
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
|
|
286
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depth, 0);
|
|
287
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
288
|
+
this.depth = depth;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
dispose(gl: WebGL2RenderingContext): void {
|
|
292
|
+
for (const { program } of this.programs.values()) gl.deleteProgram(program);
|
|
293
|
+
this.programs.clear();
|
|
294
|
+
if (this.texture !== null) gl.deleteTexture(this.texture);
|
|
295
|
+
if (this.framebuffer !== null) gl.deleteFramebuffer(this.framebuffer);
|
|
296
|
+
this.texture = null;
|
|
297
|
+
this.framebuffer = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { POINT_SKIN_TEXTURE_UNIT } from '../../lightBudget.ts';
|
|
12
12
|
import type { Mesh } from '../../mesh.ts';
|
|
13
|
-
import { reachOf, type PointSkinData, type PointSkinReach } from '../../pointSkin.ts';
|
|
13
|
+
import { reachOf, widenReach, type PointSkinData, type PointSkinReach } from '../../pointSkin.ts';
|
|
14
14
|
import { compileProgram } from '../../shader.ts';
|
|
15
15
|
|
|
16
16
|
const POSE_VERT = /* glsl */ `#version 300 es
|
|
@@ -80,12 +80,14 @@ export function createPointSkinPose(gl: WebGL2RenderingContext): PointSkinPose {
|
|
|
80
80
|
|
|
81
81
|
export class GlPointSkin {
|
|
82
82
|
readonly mesh: Mesh;
|
|
83
|
-
|
|
83
|
+
/** Widened by each rewrite, never narrowed: see `widenReach`. */
|
|
84
|
+
reach: PointSkinReach;
|
|
84
85
|
private readonly count: number;
|
|
85
86
|
private readonly vao: WebGLVertexArrayObject;
|
|
87
|
+
/** Joints, weights, points and normals, in that order. */
|
|
86
88
|
private readonly buffers: WebGLBuffer[] = [];
|
|
87
|
-
private
|
|
88
|
-
private
|
|
89
|
+
private joints: WebGLTexture;
|
|
90
|
+
private width: number;
|
|
89
91
|
|
|
90
92
|
constructor(gl: WebGL2RenderingContext, mesh: Mesh, data: PointSkinData) {
|
|
91
93
|
this.mesh = mesh;
|
|
@@ -107,17 +109,37 @@ export class GlPointSkin {
|
|
|
107
109
|
gl.bindVertexArray(null);
|
|
108
110
|
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
109
111
|
|
|
110
|
-
/* One row, four texels a joint, as the skin palette is. */
|
|
111
112
|
this.width = Math.max(1, this.reach.joints * 4);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
113
|
+
this.joints = jointTexture(gl, this.width);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Rewrite vertices `first` onward, as many as `data` holds, in place: their four arrays sent at
|
|
118
|
+
* their offsets and nothing else. A command in the stream like a pose, so the pose after it reads
|
|
119
|
+
* the rewrite and one before it does not. A joint none of the skin named before grows the joint
|
|
120
|
+
* texture, which is made again for it; a skin settles at the joints its shapes name, so that is
|
|
121
|
+
* rare after its first changes.
|
|
122
|
+
*/
|
|
123
|
+
rewrite(gl: WebGL2RenderingContext, first: number, data: PointSkinData): void {
|
|
124
|
+
const sent: readonly (readonly [Float32Array, number])[] = [
|
|
125
|
+
[data.joints, 16],
|
|
126
|
+
[data.weights, 16],
|
|
127
|
+
[data.points, 48],
|
|
128
|
+
[data.normals, 12],
|
|
129
|
+
];
|
|
130
|
+
for (let at = 0; at < sent.length; at++) {
|
|
131
|
+
const [values, stride] = sent[at] as readonly [Float32Array, number];
|
|
132
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[at] as WebGLBuffer);
|
|
133
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, first * stride, values);
|
|
134
|
+
}
|
|
135
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
136
|
+
this.reach = widenReach(this.reach, data);
|
|
137
|
+
const width = Math.max(1, this.reach.joints * 4);
|
|
138
|
+
if (width > this.width) {
|
|
139
|
+
gl.deleteTexture(this.joints);
|
|
140
|
+
this.width = width;
|
|
141
|
+
this.joints = jointTexture(gl, width);
|
|
142
|
+
}
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
/** One buffer, read as each `[location, size, stride, offset]` says. */
|
|
@@ -171,3 +193,16 @@ export class GlPointSkin {
|
|
|
171
193
|
gl.deleteVertexArray(this.vao);
|
|
172
194
|
}
|
|
173
195
|
}
|
|
196
|
+
|
|
197
|
+
/** One row, four texels a joint, as the skin palette is. */
|
|
198
|
+
function jointTexture(gl: WebGL2RenderingContext, width: number): WebGLTexture {
|
|
199
|
+
const joints = gl.createTexture();
|
|
200
|
+
if (joints === null) throw new Error('createTexture failed');
|
|
201
|
+
gl.activeTexture(gl.TEXTURE0 + POINT_SKIN_TEXTURE_UNIT);
|
|
202
|
+
gl.bindTexture(gl.TEXTURE_2D, joints);
|
|
203
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, width, 1);
|
|
204
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
205
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
206
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
207
|
+
return joints;
|
|
208
|
+
}
|