@driftengine/core 4.9.0 → 4.11.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 +133 -11
- package/README.md +43 -38
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -4
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/backend/api.d.ts +10 -3
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +189 -4
- package/dist/render/backend/webgl2/renderer.js +614 -66
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +24 -4
- package/dist/render/backend/webgpu/buffers.js +116 -59
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/drawCommand.d.ts +20 -4
- package/dist/render/backend/webgpu/drawCommand.js +11 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +137 -20
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
- package/dist/render/backend/webgpu/pipelineCache.js +46 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +200 -35
- package/dist/render/backend/webgpu/renderer.js +993 -168
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +242 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +14 -0
- package/dist/render/mesh.js +44 -14
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/flat/directionalShadow.js +84 -10
- 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 +21 -0
- package/dist/render/shaders/flat/index.js +42 -7
- package/dist/render/shaders/flat/layered.d.ts +19 -0
- package/dist/render/shaders/flat/layered.js +115 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +58 -21
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +14 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +42 -20
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +59 -27
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3616 -3042
- package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceLayers.d.ts +39 -0
- package/dist/render/surfaceLayers.js +26 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +68 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +12 -4
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/backend/api.ts +13 -3
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +723 -79
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +195 -70
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/drawCommand.ts +25 -4
- package/src/render/backend/webgpu/flatPass.ts +212 -41
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +53 -7
- package/src/render/backend/webgpu/renderer.ts +1201 -202
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +364 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/glCompressed.ts +83 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +72 -14
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +63 -7
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +58 -21
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +14 -3
- package/src/render/shaders/flat/overlay.ts +42 -20
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +59 -27
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceLayers.ts +57 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +124 -27
- package/src/render/vertexPacking.ts +109 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* millisecond a frame on the main thread, and 4,500 are twenty — a garment that size wants the
|
|
12
12
|
* device. The device solver ran 4,500 particles in 0.65 ms a frame, most of it the dispatches
|
|
13
13
|
* rather than the particles (`demo/dev/skinnedCloth.html?bench=1`).
|
|
14
|
+
*
|
|
15
|
+
* **Several garments step as one with `createSkinnedClothSet`**: one pass, one submit and one
|
|
16
|
+
* garment's dispatches a step for all of them, where a garment each was a pass, a submit and its
|
|
17
|
+
* own dispatches each. They must share a step, substeps, iterations and most steps a frame.
|
|
14
18
|
*/
|
|
15
19
|
import { SkinnedCloth } from '@driftengine/physics';
|
|
16
20
|
import type { SkinnedClothSetup } from '@driftengine/physics';
|
|
@@ -18,7 +22,7 @@ import type { SkinnedClothSetup } from '@driftengine/physics';
|
|
|
18
22
|
import type { ClothParticlesHandle, RendererApi } from './backend/api.ts';
|
|
19
23
|
import { computeHostOf } from './backend/webgpu/computeHost.ts';
|
|
20
24
|
import type { ComputeHost } from './backend/webgpu/computeHost.ts';
|
|
21
|
-
import {
|
|
25
|
+
import { GpuClothSet } from './backend/webgpu/gpuClothSet.ts';
|
|
22
26
|
|
|
23
27
|
/** A garment a renderer solves, from `createSkinnedCloth`. */
|
|
24
28
|
export interface SkinnedClothSolver {
|
|
@@ -43,86 +47,182 @@ export interface SkinnedClothSolver {
|
|
|
43
47
|
dispose(): void;
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
/** Garments a renderer solves together, from `createSkinnedClothSet`, each named by its index. */
|
|
51
|
+
export interface SkinnedClothSet {
|
|
52
|
+
/** Each garment's particles, in the order its set-up was given: what `setCloth` draws it by. */
|
|
53
|
+
readonly particles: readonly ClothParticlesHandle[];
|
|
54
|
+
/** Where the set runs: on the device, or on the CPU beneath a backend without compute. */
|
|
55
|
+
readonly runsOn: 'device' | 'cpu';
|
|
56
|
+
/**
|
|
57
|
+
* Garment `g`'s rig for the next `step`: each joint's global matrix, model space, sixteen floats a
|
|
58
|
+
* joint, and the model matrix. A garment not posed in a frame steps toward its last pose.
|
|
59
|
+
*/
|
|
60
|
+
setPose(g: number, globals: Float32Array, model: Float32Array): void;
|
|
61
|
+
/** The air garment `g` drags toward from its next step: the frame's sample of the one wind. */
|
|
62
|
+
setWind(g: number, x: number, y: number, z: number): void;
|
|
63
|
+
/** Garment `g` back to its skinned pose, settled, as a teleport resets it. */
|
|
64
|
+
reset(g: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* `dt` seconds for every garment, run as whole fixed steps; each garment's particles at its
|
|
67
|
+
* `alpha` are then what the frame draws. **Once a frame, before the draws**, as a solver's `step`.
|
|
68
|
+
*/
|
|
69
|
+
step(dt: number): void;
|
|
70
|
+
dispose(): void;
|
|
71
|
+
}
|
|
72
|
+
|
|
46
73
|
/** A garment for `renderer`, from a set-up checked by name. */
|
|
47
74
|
export function createSkinnedCloth(
|
|
48
75
|
renderer: RendererApi,
|
|
49
76
|
setup: SkinnedClothSetup,
|
|
50
77
|
): SkinnedClothSolver {
|
|
51
78
|
const host = computeHostOf(renderer);
|
|
52
|
-
|
|
79
|
+
const set =
|
|
80
|
+
host === null ? new CpuClothSet(renderer, [setup]) : new DeviceClothSet(host, [setup]);
|
|
81
|
+
return new OneGarment(set);
|
|
53
82
|
}
|
|
54
83
|
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
/** A set of one, as a solver: what `createSkinnedCloth` hands back. */
|
|
85
|
+
class OneGarment implements SkinnedClothSolver {
|
|
57
86
|
readonly particles: ClothParticlesHandle;
|
|
58
|
-
|
|
87
|
+
readonly runsOn: 'device' | 'cpu';
|
|
88
|
+
|
|
89
|
+
constructor(private readonly set: DeviceClothSet | CpuClothSet) {
|
|
90
|
+
this.particles = set.particles[0] as ClothParticlesHandle;
|
|
91
|
+
this.runsOn = set.runsOn;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
step(globals: Float32Array, model: Float32Array, dt: number): void {
|
|
95
|
+
this.set.setPose(0, globals, model);
|
|
96
|
+
this.set.step(dt);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setWind(x: number, y: number, z: number): void {
|
|
100
|
+
this.set.setWind(0, x, y, z);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
reset(): void {
|
|
104
|
+
this.set.reset(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
dispose(): void {
|
|
108
|
+
this.set.dispose();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The particles where the last step left them: the parity check's door. */
|
|
112
|
+
read(): Promise<Float32Array> {
|
|
113
|
+
return this.set.read(0);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Garments for `renderer` stepped together, from set-ups checked by name: one pass, one submit and
|
|
119
|
+
* one garment's dispatches a step on the device, whatever the count. They must agree on `step`,
|
|
120
|
+
* `substeps`, `iterations` and `maxSteps`, and are refused by the first they disagree on.
|
|
121
|
+
*/
|
|
122
|
+
export function createSkinnedClothSet(
|
|
123
|
+
renderer: RendererApi,
|
|
124
|
+
setups: readonly SkinnedClothSetup[],
|
|
125
|
+
): SkinnedClothSet {
|
|
126
|
+
const host = computeHostOf(renderer);
|
|
127
|
+
return host === null ? new CpuClothSet(renderer, setups) : new DeviceClothSet(host, setups);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class DeviceClothSet implements SkinnedClothSet {
|
|
131
|
+
readonly runsOn = 'device';
|
|
132
|
+
readonly particles: readonly ClothParticlesHandle[];
|
|
133
|
+
private readonly set: GpuClothSet;
|
|
59
134
|
|
|
60
135
|
constructor(
|
|
61
136
|
private readonly host: ComputeHost,
|
|
62
|
-
|
|
137
|
+
setups: readonly SkinnedClothSetup[],
|
|
63
138
|
) {
|
|
64
|
-
this.
|
|
65
|
-
|
|
139
|
+
this.set = new GpuClothSet(host.device(), setups);
|
|
140
|
+
const particles: ClothParticlesHandle[] = [];
|
|
141
|
+
for (let g = 0; g < this.set.size; g++) particles.push(this.set.particles(g));
|
|
142
|
+
this.particles = particles;
|
|
66
143
|
}
|
|
67
144
|
|
|
68
|
-
|
|
145
|
+
setPose(g: number, globals: Float32Array, model: Float32Array): void {
|
|
69
146
|
if (this.host.lost()) return;
|
|
70
|
-
this.
|
|
147
|
+
this.set.setPose(g, globals, model);
|
|
71
148
|
}
|
|
72
149
|
|
|
73
|
-
setWind(x: number, y: number, z: number): void {
|
|
74
|
-
this.
|
|
150
|
+
setWind(g: number, x: number, y: number, z: number): void {
|
|
151
|
+
this.set.setWind(g, x, y, z);
|
|
75
152
|
}
|
|
76
153
|
|
|
77
|
-
reset(): void {
|
|
154
|
+
reset(g: number): void {
|
|
78
155
|
if (this.host.lost()) return;
|
|
79
|
-
this.
|
|
156
|
+
this.set.reset(g);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
step(dt: number): void {
|
|
160
|
+
if (this.host.lost()) return;
|
|
161
|
+
this.set.step(dt, this.host.changeFrame());
|
|
80
162
|
}
|
|
81
163
|
|
|
82
164
|
/** Its buffers at once — its work is submitted — and its textures with the frame's. */
|
|
83
165
|
dispose(): void {
|
|
84
|
-
this.
|
|
85
|
-
this.host.retire(this.
|
|
166
|
+
this.set.dispose();
|
|
167
|
+
for (let g = 0; g < this.set.size; g++) this.host.retire(this.set.particles(g).textures);
|
|
86
168
|
}
|
|
87
169
|
|
|
88
|
-
/**
|
|
89
|
-
read(): Promise<Float32Array> {
|
|
90
|
-
return this.
|
|
170
|
+
/** Garment `g`'s particles where its last step left them: the parity check's, waits on the device. */
|
|
171
|
+
read(g: number): Promise<Float32Array> {
|
|
172
|
+
return this.set.read(g);
|
|
91
173
|
}
|
|
92
174
|
}
|
|
93
175
|
|
|
94
|
-
class
|
|
176
|
+
class CpuClothSet implements SkinnedClothSet {
|
|
95
177
|
readonly runsOn = 'cpu';
|
|
96
|
-
readonly particles: ClothParticlesHandle;
|
|
97
|
-
private readonly
|
|
98
|
-
/**
|
|
99
|
-
private readonly staging: Float32Array;
|
|
178
|
+
readonly particles: readonly ClothParticlesHandle[];
|
|
179
|
+
private readonly cloths: readonly SkinnedCloth[];
|
|
180
|
+
/** Each garment's particles at `alpha`, before they go up. */
|
|
181
|
+
private readonly staging: readonly Float32Array[];
|
|
100
182
|
|
|
101
183
|
constructor(
|
|
102
184
|
private readonly renderer: RendererApi,
|
|
103
|
-
|
|
185
|
+
setups: readonly SkinnedClothSetup[],
|
|
104
186
|
) {
|
|
105
|
-
this.
|
|
106
|
-
this.particles = renderer.createClothParticles(
|
|
107
|
-
this.staging = new Float32Array(
|
|
187
|
+
this.cloths = setups.map((setup) => new SkinnedCloth(setup));
|
|
188
|
+
this.particles = this.cloths.map((cloth) => renderer.createClothParticles(cloth.count));
|
|
189
|
+
this.staging = this.cloths.map((cloth) => new Float32Array(cloth.count * 3));
|
|
108
190
|
}
|
|
109
191
|
|
|
110
|
-
|
|
111
|
-
this.
|
|
112
|
-
this.cloth.advance(dt);
|
|
113
|
-
this.cloth.interpolate(this.cloth.alpha, this.staging);
|
|
114
|
-
this.renderer.updateClothParticles(this.particles, this.staging);
|
|
192
|
+
setPose(g: number, globals: Float32Array, model: Float32Array): void {
|
|
193
|
+
this.at(g).setPose(globals, model);
|
|
115
194
|
}
|
|
116
195
|
|
|
117
|
-
setWind(x: number, y: number, z: number): void {
|
|
118
|
-
this.
|
|
196
|
+
setWind(g: number, x: number, y: number, z: number): void {
|
|
197
|
+
this.at(g).setWind(x, y, z);
|
|
119
198
|
}
|
|
120
199
|
|
|
121
|
-
reset(): void {
|
|
122
|
-
this.
|
|
200
|
+
reset(g: number): void {
|
|
201
|
+
this.at(g).reset();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
step(dt: number): void {
|
|
205
|
+
for (let g = 0; g < this.cloths.length; g++) {
|
|
206
|
+
const cloth = this.cloths[g] as SkinnedCloth;
|
|
207
|
+
const staging = this.staging[g] as Float32Array;
|
|
208
|
+
cloth.advance(dt);
|
|
209
|
+
cloth.interpolate(cloth.alpha, staging);
|
|
210
|
+
this.renderer.updateClothParticles(this.particles[g] as ClothParticlesHandle, staging);
|
|
211
|
+
}
|
|
123
212
|
}
|
|
124
213
|
|
|
125
214
|
dispose(): void {
|
|
126
|
-
this.renderer.disposeClothParticles(
|
|
215
|
+
for (const particles of this.particles) this.renderer.disposeClothParticles(particles);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Garment `g`'s particles where its last step left them, as the device set reads its own. */
|
|
219
|
+
read(g: number): Promise<Float32Array> {
|
|
220
|
+
return Promise.resolve(new Float32Array(this.at(g).positions));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private at(g: number): SkinnedCloth {
|
|
224
|
+
const cloth = this.cloths[g];
|
|
225
|
+
if (cloth === undefined) throw new Error(`skinned cloth set: no garment ${g}`);
|
|
226
|
+
return cloth;
|
|
127
227
|
}
|
|
128
228
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draws that do not change between frames, captured once from an enumeration and replayed into any
|
|
3
|
+
* colour pass: what `createStaticDraws` keeps, and the one rule both backends replay it by.
|
|
4
|
+
*
|
|
5
|
+
* **Captured, not referenced.** An enumeration is a closure over the caller's state, written to
|
|
6
|
+
* allocate nothing, so it hands a sink its scratch: the matrix it fills for every entry and the
|
|
7
|
+
* count it rewrites for the next batch. A list kept across frames copies both when it is made, so
|
|
8
|
+
* what it draws is what the enumeration said then. Meshes, batches and materials are held by
|
|
9
|
+
* reference: they are the caller's, and disposing one a list still holds is the caller's error.
|
|
10
|
+
*
|
|
11
|
+
* **Rigid meshes and instanced batches only.** A skinned mesh is posed every frame and a scatter
|
|
12
|
+
* batch bends with every frame's wind, so neither can be kept, and both are refused by name rather
|
|
13
|
+
* than captured in the pose they happened to have. A caller draws them each frame as it always has.
|
|
14
|
+
*
|
|
15
|
+
* What a backend does with the list is its own: WebGL2 replays the entries every frame through
|
|
16
|
+
* the scene-caster path, and WebGPU records them once into a render bundle per view. See
|
|
17
|
+
* `RendererApi.createStaticDraws` for what a caller is promised, which is the same on both.
|
|
18
|
+
*/
|
|
19
|
+
import type { InstancedHandle, MeshHandle } from './backend/api.ts';
|
|
20
|
+
import type { MeshInstances } from './instances.ts';
|
|
21
|
+
import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from './shadowCasters.ts';
|
|
22
|
+
|
|
23
|
+
/** A list from `createStaticDraws`. Opaque but for its size. */
|
|
24
|
+
export interface StaticDrawsHandle {
|
|
25
|
+
/** How many entries it holds: meshes and batches, a batch counting once. */
|
|
26
|
+
readonly draws: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** One captured entry. */
|
|
30
|
+
export type StaticEntry =
|
|
31
|
+
| {
|
|
32
|
+
readonly kind: 'mesh';
|
|
33
|
+
readonly mesh: MeshHandle;
|
|
34
|
+
readonly model: Float32Array;
|
|
35
|
+
readonly material: SceneCasterMaterial;
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
readonly kind: 'instanced';
|
|
39
|
+
readonly batch: InstancedHandle;
|
|
40
|
+
readonly data: MeshInstances;
|
|
41
|
+
readonly material: SceneCasterMaterial;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** What both backends hold: the entries, and whether the list has been disposed. */
|
|
45
|
+
export interface StaticDrawsList extends StaticDrawsHandle {
|
|
46
|
+
readonly entries: readonly StaticEntry[];
|
|
47
|
+
disposed: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Enumerate `casters` once into a list. Allocates: it runs when the list is made, not per frame. */
|
|
51
|
+
export function captureStaticDraws(casters: ShadowCasters): StaticDrawsList {
|
|
52
|
+
const entries: StaticEntry[] = [];
|
|
53
|
+
const sink: ShadowCasterSink = {
|
|
54
|
+
mesh: (mesh, model, material) => {
|
|
55
|
+
entries.push({
|
|
56
|
+
kind: 'mesh',
|
|
57
|
+
mesh,
|
|
58
|
+
model: new Float32Array(model as Float32Array),
|
|
59
|
+
material: material ?? null,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
instanced: (batch, data, material) => {
|
|
63
|
+
/* The batch's placements are on the device already; the count is the caller's scratch. */
|
|
64
|
+
entries.push({ kind: 'instanced', batch, data: { ...data }, material: material ?? null });
|
|
65
|
+
},
|
|
66
|
+
skinnedMesh: () => {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'createStaticDraws: a skinned mesh is posed every frame, so a list kept across frames ' +
|
|
69
|
+
'cannot hold one. Draw it each frame, with drawMesh or drawSceneCasters.',
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
scatter: () => {
|
|
73
|
+
throw new Error(
|
|
74
|
+
'createStaticDraws: a scatter batch bends with every frame’s wind, so a list kept across ' +
|
|
75
|
+
'frames cannot hold one. Draw it each frame with drawScatter.',
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
casters(sink);
|
|
80
|
+
return {
|
|
81
|
+
entries,
|
|
82
|
+
get draws() {
|
|
83
|
+
return entries.length;
|
|
84
|
+
},
|
|
85
|
+
disposed: false,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Hand every entry to `sink`, in the order it was captured. */
|
|
90
|
+
export function replayStaticDraws(list: StaticDrawsList, sink: ShadowCasterSink): void {
|
|
91
|
+
for (const entry of list.entries) {
|
|
92
|
+
if (entry.kind === 'mesh') sink.mesh(entry.mesh, entry.model, entry.material);
|
|
93
|
+
else sink.instanced?.(entry.batch, entry.data, entry.material);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material blended from layers by a mask: `SurfaceMaterial.layers`, and the two vectors
|
|
3
|
+
* `shaders/flat/layered.ts` reads it from.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The most layers a material blends: a base and one for each of the mask's four channels. */
|
|
7
|
+
export const MAX_SURFACE_LAYERS = 5;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Up to five layers, each its own colour, normal and roughness at its own repeat, blended by a mask.
|
|
11
|
+
*
|
|
12
|
+
* **Layer `i` is layer `i` of the material's arrays**: `albedo`, `normal` and `orm` each an array
|
|
13
|
+
* whose layers are the material's layers in order, read at the mesh's coordinates times
|
|
14
|
+
* `repeats[i]`, so rock can repeat forty times across a cliff while the moss on it repeats three.
|
|
15
|
+
* **The mask** is read at the mesh's own coordinates and lays each layer over those before it: red
|
|
16
|
+
* lays layer 1 over the base, green layer 2 over that, blue layer 3, alpha layer 4. Its layer 0 is
|
|
17
|
+
* read, from where a shading model's map goes, which is why a layered material carries no
|
|
18
|
+
* `modelMap`. `emissiveLayer` gives the material's emissive map to one layer, glowing where that
|
|
19
|
+
* layer shows and nowhere it is covered; absent, the map glows as on any material.
|
|
20
|
+
*
|
|
21
|
+
* **What it gives up**: every layer's maps are read wherever any shows, three reads a layer, so five
|
|
22
|
+
* layers are fifteen reads where one material is three; layers share an array's size and format; a
|
|
23
|
+
* cutout reads the base layer's alpha; and the mask is sampled linear, so it is uploaded as data
|
|
24
|
+
* (`colorSpace: 'linear'`).
|
|
25
|
+
*/
|
|
26
|
+
export interface SurfaceLayers<Texture> {
|
|
27
|
+
/** The weights, red to alpha laying layers 1 to 4, read at the mesh's coordinates. */
|
|
28
|
+
readonly mask: Texture;
|
|
29
|
+
/** Each layer's repeats across the mesh's coordinates, base first: one to five of them. */
|
|
30
|
+
readonly repeats: readonly number[];
|
|
31
|
+
/** The layer the emissive map glows on, 0 to 4. Absent: everywhere, as on any material. */
|
|
32
|
+
readonly emissiveLayer?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Floats `packSurfaceLayers` writes: two vectors. */
|
|
36
|
+
export const LAYER_FLOATS = 8;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `layers` into `out`: the five repeats (1 for a layer not given), then how many layers there are
|
|
40
|
+
* and which one glows (−1 for none); zeros, no layers, where there are none. A repeat that is not a
|
|
41
|
+
* positive number is 1, so a layer is never laid at a scale that collapses it to a point.
|
|
42
|
+
*/
|
|
43
|
+
export function packSurfaceLayers<Texture>(
|
|
44
|
+
layers: SurfaceLayers<Texture> | null | undefined,
|
|
45
|
+
out: Float32Array,
|
|
46
|
+
): void {
|
|
47
|
+
out.fill(0);
|
|
48
|
+
if (layers === null || layers === undefined || layers.repeats.length === 0) return;
|
|
49
|
+
const count = Math.min(layers.repeats.length, MAX_SURFACE_LAYERS);
|
|
50
|
+
for (let i = 0; i < MAX_SURFACE_LAYERS; i++) {
|
|
51
|
+
const repeat = i < count ? (layers.repeats[i] as number) : 1;
|
|
52
|
+
out[i] = Number.isFinite(repeat) && repeat > 0 ? repeat : 1;
|
|
53
|
+
}
|
|
54
|
+
out[5] = count;
|
|
55
|
+
const glow = layers.emissiveLayer;
|
|
56
|
+
out[6] = glow !== undefined && Number.isInteger(glow) && glow >= 0 && glow < count ? glow : -1;
|
|
57
|
+
}
|
|
@@ -29,12 +29,24 @@ export interface OverlayRegion {
|
|
|
29
29
|
* raised to a contrast, times a noise laid in screen space, a slow pulse and a mask, in a colour.
|
|
30
30
|
* Added to what the surface emits, and not gated on the night as emission is: it is a glow the
|
|
31
31
|
* surface wears, not light it gives off.
|
|
32
|
+
*
|
|
33
|
+
* **Or, with `mode: 'blend'`, a colour the surface is pulled toward**, lit like the rest of it: the
|
|
34
|
+
* base colour mixed toward the rim's by the edge times `alpha`, before any light reads it, so the
|
|
35
|
+
* rim is bright where light falls and dark where none does. The colour is `colour` times
|
|
36
|
+
* `intensity`, each channel held at 1 as a base colour is, so a colour of 8 red is a red of 1 and
|
|
37
|
+
* not a glow; `alpha` is the weight, which may pass 1 to saturate the edge sooner.
|
|
32
38
|
*/
|
|
33
39
|
export interface SurfaceRim {
|
|
34
40
|
readonly colour: Vec3;
|
|
35
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* How bright, 0 for none, multiplied with `alpha`. Where the rim blends it scales `colour`
|
|
43
|
+
* instead, so 0 there is a rim of black, and `alpha` alone says how much: 0 for none.
|
|
44
|
+
*/
|
|
36
45
|
readonly intensity: number;
|
|
46
|
+
/** The rim's weight, 1 unless given. */
|
|
37
47
|
readonly alpha?: number;
|
|
48
|
+
/** `'add'`, the default, lays the rim on as glow; `'blend'` pulls the base colour toward it, lit. */
|
|
49
|
+
readonly mode?: 'add' | 'blend';
|
|
38
50
|
/** The edge's exponent: larger keeps the rim nearer the silhouette. 1.5 unless given. */
|
|
39
51
|
readonly falloff?: number;
|
|
40
52
|
/**
|
|
@@ -122,10 +134,11 @@ function region(out: Float32Array, at: number, r: OverlayRegion | undefined | nu
|
|
|
122
134
|
* no overlay. A region with a zero scale is an image not given, and its term reads as none — a
|
|
123
135
|
* noise of 1, a mask of 0, no wrinkle — and every region is one without `maps`. Fifteen vectors:
|
|
124
136
|
*
|
|
125
|
-
* - 0: rim colour, rim strength (intensity times alpha; 0 is no rim)
|
|
137
|
+
* - 0: rim colour, rim strength (intensity times alpha; 0 is no rim). A blending rim: the colour
|
|
138
|
+
* times intensity, each channel held at 1, and the alpha alone
|
|
126
139
|
* - 1: falloff, upward lean, contrast, mask weight
|
|
127
140
|
* - 2: the rim's noise region; 3: its scroll along x and y, its tiling, the pulse's rate
|
|
128
|
-
* - 4: the pulse's low and high (1 and 1 where none)
|
|
141
|
+
* - 4: the pulse's low and high (1 and 1 where none), and the mode: 0 adds, 1 blends
|
|
129
142
|
* - 5: the rim's mask region
|
|
130
143
|
* - 6: the dissolve's noise region (a zero scale is no dissolve); 7: its tiling, threshold, edge
|
|
131
144
|
* - 8: the edge's light and its strength; 9: the laid-over colour and its amount
|
|
@@ -138,10 +151,14 @@ export function packSurfaceOverlay(overlay: SurfaceOverlay | null, out: Float32A
|
|
|
138
151
|
const images = overlay.maps !== undefined;
|
|
139
152
|
const rim = overlay.rim;
|
|
140
153
|
if (rim !== undefined) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
154
|
+
const blend = rim.mode === 'blend';
|
|
155
|
+
const scale = blend ? finite(rim.intensity, 0) : 1;
|
|
156
|
+
for (let c = 0; c < 3; c++) {
|
|
157
|
+
const channel = finite(rim.colour[c], 0) * scale;
|
|
158
|
+
out[c] = blend ? Math.min(Math.max(channel, 0), 1) : channel;
|
|
159
|
+
}
|
|
160
|
+
out[3] = Math.max(0, (blend ? 1 : finite(rim.intensity, 0)) * finite(rim.alpha, 1));
|
|
161
|
+
out[18] = blend ? 1 : 0;
|
|
145
162
|
out[4] = Math.max(0, finite(rim.falloff, 1.5));
|
|
146
163
|
out[5] = Math.max(0, finite(rim.upward, 0.4));
|
|
147
164
|
out[6] = Math.max(0, finite(rim.contrast, 1));
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's maps placed by where a point is in the world: `SurfaceMaterial.projection`, and the
|
|
3
|
+
* one vector `shaders/flat/worldUv.ts` reads it from.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Texture coordinates from the world rather than from the mesh.
|
|
8
|
+
*
|
|
9
|
+
* **`'planar'`** lays the maps across the two horizontal axes, `x` and `z`: ground whose mesh
|
|
10
|
+
* carries flat coordinates, or none worth reading, shows its texture at `scale` repeats a metre.
|
|
11
|
+
* **`'triplanar'`** reads each map on the three planes across `x`, `y` and `z` and blends them by
|
|
12
|
+
* how squarely the surface faces each, the normal's components raised to `sharpness`, so walls and
|
|
13
|
+
* rock wear a texture without stretching. See `worldUv.ts` for the normal map's blend.
|
|
14
|
+
*
|
|
15
|
+
* **What it gives up**: the world's coordinates do not move with a mesh, so a moving mesh slides
|
|
16
|
+
* under its texture; triplanar reads every map three times; and the material's `uScale`, `vScale`
|
|
17
|
+
* and offsets, which are the mesh's coordinates', do not apply.
|
|
18
|
+
*/
|
|
19
|
+
export interface SurfaceProjection {
|
|
20
|
+
readonly kind: 'planar' | 'triplanar';
|
|
21
|
+
/** Repeats a metre: 2 lays a texture every half metre. */
|
|
22
|
+
readonly scale: number;
|
|
23
|
+
/** How sharply triplanar's planes meet: the exponent on the normal. 4 unless given. */
|
|
24
|
+
readonly sharpness?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Floats `packSurfaceProjection` writes: one vector. */
|
|
28
|
+
export const PROJECTION_FLOATS = 4;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* `projection` into `out`: its kind (0 none, 1 planar, 2 triplanar), its repeats a metre and its
|
|
32
|
+
* sharpness; zeros for none. A scale that is not finite and above zero projects nothing, so a
|
|
33
|
+
* material is never drawn at a scale that collapses its texture to a point.
|
|
34
|
+
*/
|
|
35
|
+
export function packSurfaceProjection(
|
|
36
|
+
projection: SurfaceProjection | null | undefined,
|
|
37
|
+
out: Float32Array,
|
|
38
|
+
): void {
|
|
39
|
+
out.fill(0);
|
|
40
|
+
if (projection === null || projection === undefined) return;
|
|
41
|
+
const scale = projection.scale;
|
|
42
|
+
if (!Number.isFinite(scale) || scale <= 0) return;
|
|
43
|
+
out[0] = projection.kind === 'triplanar' ? 2 : 1;
|
|
44
|
+
out[1] = scale;
|
|
45
|
+
const sharpness = projection.sharpness ?? 4;
|
|
46
|
+
out[2] = Number.isFinite(sharpness) ? Math.min(Math.max(sharpness, 1), 64) : 4;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Whether a material projects its maps at all. */
|
|
50
|
+
export function projects(projection: SurfaceProjection | null | undefined): boolean {
|
|
51
|
+
return (
|
|
52
|
+
projection !== null &&
|
|
53
|
+
projection !== undefined &&
|
|
54
|
+
Number.isFinite(projection.scale) &&
|
|
55
|
+
projection.scale > 0
|
|
56
|
+
);
|
|
57
|
+
}
|