@driftengine/core 4.10.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 +71 -0
- package/README.md +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/render/backend/webgl2/renderer.d.ts +99 -2
- package/dist/render/backend/webgl2/renderer.js +361 -42
- package/dist/render/backend/webgpu/buffers.d.ts +5 -0
- package/dist/render/backend/webgpu/buffers.js +98 -48
- 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/drawCommand.d.ts +2 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
- package/dist/render/backend/webgpu/flatPass.js +2 -1
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
- package/dist/render/backend/webgpu/pipelineCache.js +39 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +48 -0
- package/dist/render/backend/webgpu/renderer.js +207 -27
- 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/staticBundles.js +2 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -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/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/mesh.d.ts +9 -0
- package/dist/render/mesh.js +38 -14
- 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/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- 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 +15 -0
- package/dist/render/shaders/flat/index.js +15 -2
- 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 +43 -16
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- 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 +41 -19
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
- package/dist/render/shaders/generated/flat.wgsl.js +791 -391
- 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/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- 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 +30 -0
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +4 -2
- package/src/render/backend/webgl2/renderer.ts +408 -55
- package/src/render/backend/webgpu/buffers.ts +153 -54
- 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/drawCommand.ts +3 -0
- package/src/render/backend/webgpu/flatPass.ts +26 -28
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +46 -7
- package/src/render/backend/webgpu/renderer.ts +274 -48
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/staticBundles.ts +2 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/mesh.ts +66 -14
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/shader.ts +59 -0
- 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 +30 -2
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +43 -16
- package/src/render/shaders/flat/mapReads.ts +38 -0
- 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 +41 -19
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/flat.wgsl.ts +791 -391
- 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/tangentFrame.ts +16 -3
- package/src/render/skinnedClothRun.ts +138 -38
- 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 +30 -0
- 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
|
@@ -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
|
+
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* array samples exactly as a 2D texture does. A mesh names its layer per vertex (`MeshData.layers`),
|
|
9
9
|
* so a merged block wearing forty facades is one draw.
|
|
10
10
|
*/
|
|
11
|
+
import type { SurfaceLayers } from './surfaceLayers.ts';
|
|
12
|
+
import type { SurfaceProjection } from './surfaceProjection.ts';
|
|
11
13
|
import type { SurfaceModel } from './surfaceModel.ts';
|
|
12
14
|
import { layerSize, refuseArrayUpdate, sourceSize } from './textureSource.ts';
|
|
13
15
|
import {
|
|
@@ -213,6 +215,34 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
|
|
|
213
215
|
* and an 8-bit frame clips the brighter peak without `hdrScene`.
|
|
214
216
|
*/
|
|
215
217
|
physicalSpecular?: boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Texture coordinates from where a point is in the world rather than from the mesh: `'planar'`
|
|
220
|
+
* across the horizontal axes for ground, `'triplanar'` on three planes for walls and rock, at so
|
|
221
|
+
* many repeats a metre. Absent, the mesh's, as every material's were. **A lit switch**, compiled in
|
|
222
|
+
* the first time a material asks, so a scene that never asks pays nothing. See
|
|
223
|
+
* `surfaceProjection.ts` for what it gives up.
|
|
224
|
+
*/
|
|
225
|
+
projection?: SurfaceProjection | null;
|
|
226
|
+
/**
|
|
227
|
+
* How much light from behind a thin surface lets through, 0 to 1: a banner lit from behind, a
|
|
228
|
+
* leaf against the sun, a lampshade. The light falling on the far side — the sun, lamps, area
|
|
229
|
+
* lights, DriftLight — reaches the eye through the surface, coloured by its own colour; a metal
|
|
230
|
+
* lets none through. 0, the default, is every material as it was. Two-sided or not: a one-sided
|
|
231
|
+
* surface seen from its front shows the light behind it as well.
|
|
232
|
+
*
|
|
233
|
+
* **What it gives up**: a thin surface, not a volume, so a thick one lets through as much as a
|
|
234
|
+
* sheet; the light behind is not blurred by the surface; and none of it on glass, which lets
|
|
235
|
+
* light through by its own rule (`TranslucentMeshOptions.glass`).
|
|
236
|
+
*/
|
|
237
|
+
diffuseTransmission?: number;
|
|
238
|
+
/**
|
|
239
|
+
* Up to five layers blended by a mask, each at its own repeat: `albedo`, `normal` and `orm` arrays
|
|
240
|
+
* whose layers are the material's, and a mask laying each over the ones before it. Absent, the
|
|
241
|
+
* material is one layer, as every material was. **A lit switch**, compiled in the first time a
|
|
242
|
+
* material asks. The mask is read where `modelMap` goes, so a material carries one or the other.
|
|
243
|
+
* See `surfaceLayers.ts` for what it gives up.
|
|
244
|
+
*/
|
|
245
|
+
layers?: SurfaceLayers<Texture> | null;
|
|
216
246
|
}
|
|
217
247
|
|
|
218
248
|
export interface SurfaceTextureOptions {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { MeshData } from '@driftengine/drft';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How each of a mesh's vertex attributes travels to the device, decided once from its data and
|
|
5
|
+
* bound by each backend: as floats, as sixteen-bit fixed point, or as one value every vertex
|
|
6
|
+
* shares.
|
|
7
|
+
*
|
|
8
|
+
* **Only what reads back as it went, or near enough that no picture can tell.** One value every
|
|
9
|
+
* vertex shares is exact, and is a constant rather than a stream: a colour or an emissive naming
|
|
10
|
+
* no difference between vertices costs nothing a vertex, where both were always stored per vertex.
|
|
11
|
+
* A unit direction — a normal, a tangent and its handedness — in sixteen-bit signed fixed point is
|
|
12
|
+
* within 1/32767 a component, a few thousandths of a degree: measured against floats, 12 pixels of
|
|
13
|
+
* 921,600 moved in one published scene, by 2 of 255 on average, and none in the other eight.
|
|
14
|
+
* Colours and skin weights inside [0, 1] in sixteen-bit unsigned fixed point are within 1/65535. A
|
|
15
|
+
* textured vertex with a tangent frame, white and not glowing, goes from 64 bytes to 36 on WebGPU.
|
|
16
|
+
*
|
|
17
|
+
* **What it gives up.** Eight-bit normals would save four bytes more and are not taken: a normal in
|
|
18
|
+
* steps of 1/127 can band a highlight. **Texture coordinates stay floats**, in any range: a
|
|
19
|
+
* coordinate on a tile's far edge rounds past it in sixteen-bit fixed point, by 1/131,070, and a
|
|
20
|
+
* pixel there samples the neighbouring tile of an atlas — measured as 29 pixels moved by 62 of 255
|
|
21
|
+
* in the voxel scene, every one at a face's edge — and a half float, which steps by a 2048th near
|
|
22
|
+
* 1.0, is worse. A dynamic mesh's normals stay floats, because each rewrite would have to be packed
|
|
23
|
+
* on the CPU. What would make this wrong is a value outside its range, which the range check sends
|
|
24
|
+
* to floats rather than clamping, or a shader reading an attribute as something other than the
|
|
25
|
+
* value it carries.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Floats; sixteen-bit fixed point, signed or not; or one value for every vertex. */
|
|
29
|
+
export type AttributePacking = 'float' | 'snorm16' | 'unorm16' | 'constant';
|
|
30
|
+
|
|
31
|
+
/** The attributes this decides for. Every other one travels as floats. */
|
|
32
|
+
export const PACKED_ATTRIBUTES = [
|
|
33
|
+
'normals',
|
|
34
|
+
'tangents',
|
|
35
|
+
'colors',
|
|
36
|
+
'emissive',
|
|
37
|
+
'weights',
|
|
38
|
+
'weights2',
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
export type PackedAttribute = (typeof PACKED_ATTRIBUTES)[number];
|
|
42
|
+
|
|
43
|
+
export type VertexPacking = Readonly<Record<PackedAttribute, AttributePacking>>;
|
|
44
|
+
|
|
45
|
+
/** The choice for this mesh. `dynamic` is a mesh whose positions and normals are rewritten. */
|
|
46
|
+
export function vertexPackingOf(data: MeshData, dynamic: boolean): VertexPacking {
|
|
47
|
+
return {
|
|
48
|
+
normals: dynamic ? 'float' : fixedOrFloat(data.normals, -1),
|
|
49
|
+
tangents: fixedOrFloat(data.tangents, -1),
|
|
50
|
+
colors: shared(data.colors, 3) ? 'constant' : fixedOrFloat(data.colors, 0),
|
|
51
|
+
emissive: shared(data.emissive, 1) ? 'constant' : 'float',
|
|
52
|
+
weights: fixedOrFloat(data.weights, 0),
|
|
53
|
+
weights2: fixedOrFloat(data.weights2, 0),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* What 1 is in each fixed point, the values WebGPU's `snorm16` and `unorm16` and WebGL2's
|
|
59
|
+
* normalised `SHORT` and `UNSIGNED_SHORT` divide by. A value is stored rounded to nearest, so 1, 0
|
|
60
|
+
* and -1 come back exactly.
|
|
61
|
+
*/
|
|
62
|
+
export const SNORM16_ONE = 32767;
|
|
63
|
+
export const UNORM16_ONE = 65535;
|
|
64
|
+
|
|
65
|
+
/** One value in the fixed point whose 1 is `one`, rounded to nearest: the only place it is. */
|
|
66
|
+
export function toFixed16(value: number, one: number): number {
|
|
67
|
+
return Math.round(value * one);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** An attribute's values in its fixed point, tightly, for a backend reading separate arrays. */
|
|
71
|
+
export function fixedArray(
|
|
72
|
+
values: Float32Array,
|
|
73
|
+
packing: 'snorm16' | 'unorm16',
|
|
74
|
+
): Int16Array | Uint16Array {
|
|
75
|
+
const out =
|
|
76
|
+
packing === 'snorm16' ? new Int16Array(values.length) : new Uint16Array(values.length);
|
|
77
|
+
const one = packing === 'snorm16' ? SNORM16_ONE : UNORM16_ONE;
|
|
78
|
+
for (let at = 0; at < values.length; at++) out[at] = toFixed16(values[at] as number, one);
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** How `name` travels under this packing: floats for an attribute it does not decide for. */
|
|
83
|
+
export function packingFor(packing: VertexPacking, name: string): AttributePacking {
|
|
84
|
+
return (packing as Readonly<Record<string, AttributePacking>>)[name] ?? 'float';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The value every vertex shares, for an attribute packed as `'constant'`. */
|
|
88
|
+
export function sharedValue(values: Float32Array, width: number): number[] {
|
|
89
|
+
return Array.from(values.subarray(0, width));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Fixed point where every value lies in [low, 1]; floats otherwise, and for a NaN. */
|
|
93
|
+
function fixedOrFloat(values: Float32Array | undefined, low: -1 | 0): AttributePacking {
|
|
94
|
+
if (values === undefined) return 'float';
|
|
95
|
+
for (let at = 0; at < values.length; at++) {
|
|
96
|
+
const value = values[at] as number;
|
|
97
|
+
if (!(value >= low && value <= 1)) return 'float';
|
|
98
|
+
}
|
|
99
|
+
return low < 0 ? 'snorm16' : 'unorm16';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Whether every vertex holds the first vertex's value. Not for a mesh of none. */
|
|
103
|
+
function shared(values: Float32Array, width: number): boolean {
|
|
104
|
+
if (values.length < width) return false;
|
|
105
|
+
for (let at = width; at < values.length; at++) {
|
|
106
|
+
if (values[at] !== values[at % width]) return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { SkinnedClothSetup } from '@driftengine/physics';
|
|
2
|
-
import { GpuClothParticles } from './clothTextures.ts';
|
|
3
|
-
/** One garment's solver on the device, and the particle textures it publishes into. */
|
|
4
|
-
export declare class GpuSkinnedCloth {
|
|
5
|
-
private readonly device;
|
|
6
|
-
/** The particles a bound mesh draws by: see `RendererApi.setCloth`. */
|
|
7
|
-
readonly particles: GpuClothParticles;
|
|
8
|
-
readonly count: number;
|
|
9
|
-
private readonly control;
|
|
10
|
-
private readonly packed;
|
|
11
|
-
private readonly kernels;
|
|
12
|
-
private readonly buffers;
|
|
13
|
-
private readonly state;
|
|
14
|
-
private readonly frameData;
|
|
15
|
-
private readonly stepSlots;
|
|
16
|
-
private readonly group;
|
|
17
|
-
private readonly outputs;
|
|
18
|
-
private readonly frameStaging;
|
|
19
|
-
private readonly stepStaging;
|
|
20
|
-
private readonly stepFloats;
|
|
21
|
-
private readonly stepWords;
|
|
22
|
-
/** Each batch's slot, by kind and by whether it opens a substep's iterations. */
|
|
23
|
-
private readonly batchSlot;
|
|
24
|
-
/** The air the steps drag toward: the set-up's until `setWind`, written into every step. */
|
|
25
|
-
private readonly wind;
|
|
26
|
-
private readonly groups;
|
|
27
|
-
private readonly substeps;
|
|
28
|
-
private readonly iterations;
|
|
29
|
-
private encoder;
|
|
30
|
-
private pass;
|
|
31
|
-
private slot;
|
|
32
|
-
/** Where the pose a step starts from and the one it moves toward sit in `pose`, in floats. */
|
|
33
|
-
private fromAt;
|
|
34
|
-
private toAt;
|
|
35
|
-
constructor(device: GPUDevice, setup: SkinnedClothSetup);
|
|
36
|
-
/**
|
|
37
|
-
* One frame: the rig's pose, `dt` of the caller's time in whole fixed steps, and the particles at
|
|
38
|
-
* the frame's `alpha` published for the draws that follow, as the particles of `frame`.
|
|
39
|
-
*/
|
|
40
|
-
advance(globals: Float32Array, model: Float32Array, dt: number, frame: number): void;
|
|
41
|
-
/** The frame's sample of the scene's wind, for the steps to come. See `SkinnedCloth.setWind`. */
|
|
42
|
-
setWind(x: number, y: number, z: number): void;
|
|
43
|
-
/** Back to the skinned pose, settled; the next `advance` publishes it. */
|
|
44
|
-
reset(): void;
|
|
45
|
-
/**
|
|
46
|
-
* The particles where the last step left them, read back: for the parity check, which compares
|
|
47
|
-
* them with the CPU solver's. Not for a frame — it waits on the device.
|
|
48
|
-
*/
|
|
49
|
-
read(): Promise<Float32Array>;
|
|
50
|
-
/** Its buffers; the particle textures are the renderer's to retire with the frame. */
|
|
51
|
-
dispose(): void;
|
|
52
|
-
private targetsChanged;
|
|
53
|
-
private rest;
|
|
54
|
-
private step;
|
|
55
|
-
/** Every batch of one kind, in colour order, each a dispatch over its range. */
|
|
56
|
-
private batches;
|
|
57
|
-
private dispatch;
|
|
58
|
-
private passFor;
|
|
59
|
-
/** Send what has been recorded: every slot it reads was written ahead of it on the queue. */
|
|
60
|
-
private flush;
|
|
61
|
-
/**
|
|
62
|
-
* A step's slot: its fraction, blend and alpha, the poses it reads, and — where `targets` is
|
|
63
|
-
* given — the share of the character's motion the cloth is carried by, as `carryParticles` takes
|
|
64
|
-
* it. Returns the slot's byte offset.
|
|
65
|
-
*/
|
|
66
|
-
private takeSlot;
|
|
67
|
-
/** `carryParticles`' rotation, origin and move, into the step's slot; carry 0 where none. */
|
|
68
|
-
private writeCarry;
|
|
69
|
-
/** The pose's skin matrices — or the model, for a cloth with no rig — and its colliders. */
|
|
70
|
-
private writeFrameData;
|
|
71
|
-
private constants;
|
|
72
|
-
/** Each batch's range, twice — opening a substep's iterations and not — at a slot each. */
|
|
73
|
-
private writeBatches;
|
|
74
|
-
}
|