@driftengine/core 4.13.1 → 4.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +42 -0
- package/README.md +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/render/backend/api.d.ts +63 -8
- package/dist/render/backend/webgl2/pointSkinFeedback.d.ts +23 -0
- package/dist/render/backend/webgl2/pointSkinFeedback.js +159 -0
- package/dist/render/backend/webgl2/renderer.d.ts +33 -0
- package/dist/render/backend/webgl2/renderer.js +88 -4
- package/dist/render/backend/webgpu/buffers.d.ts +10 -2
- package/dist/render/backend/webgpu/buffers.js +66 -16
- package/dist/render/backend/webgpu/clothTextures.d.ts +6 -3
- package/dist/render/backend/webgpu/clothTextures.js +14 -11
- package/dist/render/backend/webgpu/drawCommand.js +3 -3
- package/dist/render/backend/webgpu/gpuClothSet.js +1 -0
- package/dist/render/backend/webgpu/groupShelf.d.ts +31 -0
- package/dist/render/backend/webgpu/groupShelf.js +54 -0
- package/dist/render/backend/webgpu/pointSkinPass.d.ts +27 -0
- package/dist/render/backend/webgpu/pointSkinPass.js +104 -0
- package/dist/render/backend/webgpu/renderer.d.ts +40 -7
- package/dist/render/backend/webgpu/renderer.js +151 -48
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +7 -2
- package/dist/render/backend/webgpu/shaders/pointSkin.wgsl.d.ts +13 -0
- package/dist/render/backend/webgpu/shaders/pointSkin.wgsl.js +57 -0
- package/dist/render/backend/webgpu/vertexFormats.js +1 -1
- package/dist/render/lightBudget.d.ts +6 -0
- package/dist/render/lightBudget.js +6 -0
- package/dist/render/mesh.d.ts +19 -5
- package/dist/render/mesh.js +39 -8
- package/dist/render/pointSkin.d.ts +55 -0
- package/dist/render/pointSkin.js +121 -0
- package/dist/render/rendererHarness.js +6 -0
- package/dist/render/shader.d.ts +3 -1
- package/dist/render/shader.js +6 -1
- package/dist/render/shaders/flat/detail.d.ts +21 -0
- package/dist/render/shaders/flat/detail.js +74 -0
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +1 -0
- package/dist/render/shaders/flat/index.d.ts +5 -0
- package/dist/render/shaders/flat/index.js +3 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +2 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +144 -16
- package/dist/render/shaders/generated/flat.wgsl.js +183 -55
- package/dist/render/surfaceDetail.d.ts +79 -0
- package/dist/render/surfaceDetail.js +86 -0
- package/dist/render/surfaceTexture.d.ts +9 -0
- package/dist/render/vertexPacking.d.ts +16 -4
- package/dist/render/vertexPacking.js +10 -3
- package/package.json +6 -6
- package/src/index.ts +4 -0
- package/src/render/backend/api.ts +70 -7
- package/src/render/backend/webgl2/pointSkinFeedback.ts +173 -0
- package/src/render/backend/webgl2/renderer.ts +121 -4
- package/src/render/backend/webgpu/buffers.ts +90 -16
- package/src/render/backend/webgpu/clothTextures.ts +14 -11
- package/src/render/backend/webgpu/drawCommand.ts +3 -3
- package/src/render/backend/webgpu/gpuClothSet.ts +1 -0
- package/src/render/backend/webgpu/groupShelf.ts +74 -0
- package/src/render/backend/webgpu/pointSkinPass.ts +115 -0
- package/src/render/backend/webgpu/renderer.ts +192 -83
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +7 -2
- package/src/render/backend/webgpu/shaders/pointSkin.wgsl.ts +58 -0
- package/src/render/backend/webgpu/vertexFormats.ts +1 -1
- package/src/render/lightBudget.ts +7 -0
- package/src/render/mesh.ts +49 -7
- package/src/render/pointSkin.ts +178 -0
- package/src/render/rendererHarness.ts +6 -0
- package/src/render/shader.ts +5 -0
- package/src/render/shaders/flat/detail.ts +74 -0
- package/src/render/shaders/flat/glassTint.ts +3 -1
- package/src/render/shaders/flat/index.ts +8 -0
- package/src/render/shaders/flat/main.ts +2 -0
- package/src/render/shaders/generated/flat.wgsl.ts +183 -55
- package/src/render/surfaceDetail.ts +136 -0
- package/src/render/surfaceTexture.ts +9 -0
- package/src/render/vertexPacking.ts +25 -5
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's tiled detail: small normal maps, and occlusion, repeated many times over the base
|
|
3
|
+
* maps, a region map choosing which one each texel wears. `SurfaceMaterial.detail`, packed into
|
|
4
|
+
* `uDetail` for `shaders/flat/detail.ts`.
|
|
5
|
+
*
|
|
6
|
+
* **What it is for.** A surface's finest grain — leather, a weave, brushed metal, a face's pores —
|
|
7
|
+
* is a map of a few hundred texels repeated tens of times across it. Baked into the base maps it is
|
|
8
|
+
* lost: a 256-texel detail tiled forty times over a 2048-texel map would want some ten thousand
|
|
9
|
+
* texels a side. So the base maps carry the shape and a detail map the grain, each garment region
|
|
10
|
+
* with its own grain at its own repeat, which is how such surfaces are authored.
|
|
11
|
+
*
|
|
12
|
+
* **Where the maps are.** The lit stage has no texture binding to spare on either backend, so the
|
|
13
|
+
* detail maps are layers of the array read where a shading model's map is (`uModelMap`), and the
|
|
14
|
+
* region map is a layer of the material's own ORM array, one index a texel in its red. What that
|
|
15
|
+
* gives up, said where each is refused: a material whose shading model has a map must put its
|
|
16
|
+
* detail layers in that same array; a layered material whose mask is a map cannot have detail; and
|
|
17
|
+
* the region map shares the ORM array's size and format, which must keep an index exactly, so an
|
|
18
|
+
* uncompressed one.
|
|
19
|
+
*
|
|
20
|
+
* **Sixteen regions, two vectors each**, behind one vector saying how many and where the region
|
|
21
|
+
* map is: thirty-three in all, which is why WebGL2 compiles them in only when a material first asks
|
|
22
|
+
* and refuses them on a part with no room for them.
|
|
23
|
+
*/
|
|
24
|
+
/** Regions a material may name. */
|
|
25
|
+
export declare const MAX_DETAIL_REGIONS = 16;
|
|
26
|
+
/** Floats `uDetail` holds: a header vector, then two vectors a region. */
|
|
27
|
+
export declare const DETAIL_FLOATS: number;
|
|
28
|
+
/** One region's detail. */
|
|
29
|
+
export interface SurfaceDetailRegion {
|
|
30
|
+
/** The layer of the detail array holding this region's tangent-space normal map. */
|
|
31
|
+
readonly normalLayer: number;
|
|
32
|
+
/** The layer holding its occlusion, in red; absent, the region adds none. */
|
|
33
|
+
readonly occlusionLayer?: number;
|
|
34
|
+
/** Repeats across the base coordinates, the same both ways or across and down apart. */
|
|
35
|
+
readonly repeat: number | readonly [number, number];
|
|
36
|
+
/** Added to the repeated coordinates, so neighbouring regions need not line up. Default none. */
|
|
37
|
+
readonly offset?: readonly [number, number];
|
|
38
|
+
/** How far the detail turns the normal: 0 lies flat, 1 as drawn, more steeper. Default 1. */
|
|
39
|
+
readonly normalStrength?: number;
|
|
40
|
+
/** How much of its occlusion is laid on, from 0 to 1. Default 1. */
|
|
41
|
+
readonly occlusionStrength?: number;
|
|
42
|
+
}
|
|
43
|
+
/** A material's tiled detail. */
|
|
44
|
+
export interface SurfaceDetail<Texture> {
|
|
45
|
+
/**
|
|
46
|
+
* The array whose layers the regions name, read where a shading model's map is. A material with
|
|
47
|
+
* a `modelMap` puts its detail layers in that array and names it here too.
|
|
48
|
+
*/
|
|
49
|
+
readonly maps: Texture;
|
|
50
|
+
/**
|
|
51
|
+
* The layer of the material's ORM array holding the region map: in its red, each texel's region
|
|
52
|
+
* as an index from 0, stored as `index / 255`, read exactly at the base coordinates. Absent, every
|
|
53
|
+
* texel is region 0, which is a surface with one detail all over: a face's pores.
|
|
54
|
+
*/
|
|
55
|
+
readonly regionLayer?: number;
|
|
56
|
+
/** Up to sixteen, by index. A texel naming a region past the last wears the last. */
|
|
57
|
+
readonly regions: readonly SurfaceDetailRegion[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Pack a material's detail into `out`, `DETAIL_FLOATS` long; zeros for none, which the shader reads
|
|
61
|
+
* as no detail. `hasOrm` says whether the material carries the ORM array a region map is a layer
|
|
62
|
+
* of; without one every texel is region 0. Allocates nothing.
|
|
63
|
+
*/
|
|
64
|
+
export declare function packSurfaceDetail(detail: SurfaceDetail<unknown> | null, hasOrm: boolean, out: Float32Array): void;
|
|
65
|
+
/** Why a material's detail is drawn without, or null where nothing stands in its way. */
|
|
66
|
+
export type DetailRefusal = 'slot' | 'projection';
|
|
67
|
+
/**
|
|
68
|
+
* Whether a material's detail can be drawn, given what else wants the slot its maps are read
|
|
69
|
+
* through: a shading model's own map, or a layered material's mask where it is a map. Either must
|
|
70
|
+
* be the detail's array itself. And a material placed by the world has no base coordinates for a
|
|
71
|
+
* region map to be read at, so its detail is refused too.
|
|
72
|
+
*/
|
|
73
|
+
export declare function detailRefusal(detail: SurfaceDetail<unknown>, slotTakenBy: unknown, projected: boolean): DetailRefusal | null;
|
|
74
|
+
/** A material's detail, or null where it names none or no region. */
|
|
75
|
+
export declare function detailOf<Texture>(material: {
|
|
76
|
+
readonly detail?: SurfaceDetail<Texture> | null;
|
|
77
|
+
} | null): SurfaceDetail<Texture> | null;
|
|
78
|
+
/** The sentence each backend says once when it draws a material without its detail. */
|
|
79
|
+
export declare function detailRefusalWarning(refusal: DetailRefusal): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's tiled detail: small normal maps, and occlusion, repeated many times over the base
|
|
3
|
+
* maps, a region map choosing which one each texel wears. `SurfaceMaterial.detail`, packed into
|
|
4
|
+
* `uDetail` for `shaders/flat/detail.ts`.
|
|
5
|
+
*
|
|
6
|
+
* **What it is for.** A surface's finest grain — leather, a weave, brushed metal, a face's pores —
|
|
7
|
+
* is a map of a few hundred texels repeated tens of times across it. Baked into the base maps it is
|
|
8
|
+
* lost: a 256-texel detail tiled forty times over a 2048-texel map would want some ten thousand
|
|
9
|
+
* texels a side. So the base maps carry the shape and a detail map the grain, each garment region
|
|
10
|
+
* with its own grain at its own repeat, which is how such surfaces are authored.
|
|
11
|
+
*
|
|
12
|
+
* **Where the maps are.** The lit stage has no texture binding to spare on either backend, so the
|
|
13
|
+
* detail maps are layers of the array read where a shading model's map is (`uModelMap`), and the
|
|
14
|
+
* region map is a layer of the material's own ORM array, one index a texel in its red. What that
|
|
15
|
+
* gives up, said where each is refused: a material whose shading model has a map must put its
|
|
16
|
+
* detail layers in that same array; a layered material whose mask is a map cannot have detail; and
|
|
17
|
+
* the region map shares the ORM array's size and format, which must keep an index exactly, so an
|
|
18
|
+
* uncompressed one.
|
|
19
|
+
*
|
|
20
|
+
* **Sixteen regions, two vectors each**, behind one vector saying how many and where the region
|
|
21
|
+
* map is: thirty-three in all, which is why WebGL2 compiles them in only when a material first asks
|
|
22
|
+
* and refuses them on a part with no room for them.
|
|
23
|
+
*/
|
|
24
|
+
/** Regions a material may name. */
|
|
25
|
+
export const MAX_DETAIL_REGIONS = 16;
|
|
26
|
+
/** Floats `uDetail` holds: a header vector, then two vectors a region. */
|
|
27
|
+
export const DETAIL_FLOATS = 4 + MAX_DETAIL_REGIONS * 8;
|
|
28
|
+
/**
|
|
29
|
+
* Pack a material's detail into `out`, `DETAIL_FLOATS` long; zeros for none, which the shader reads
|
|
30
|
+
* as no detail. `hasOrm` says whether the material carries the ORM array a region map is a layer
|
|
31
|
+
* of; without one every texel is region 0. Allocates nothing.
|
|
32
|
+
*/
|
|
33
|
+
export function packSurfaceDetail(detail, hasOrm, out) {
|
|
34
|
+
out.fill(0);
|
|
35
|
+
if (detail === null)
|
|
36
|
+
return;
|
|
37
|
+
const count = Math.min(MAX_DETAIL_REGIONS, detail.regions.length);
|
|
38
|
+
out[0] = count;
|
|
39
|
+
const regionLayer = detail.regionLayer;
|
|
40
|
+
out[1] = hasOrm && regionLayer !== undefined ? Math.max(0, Math.floor(regionLayer)) : -1;
|
|
41
|
+
for (let i = 0; i < count; i++) {
|
|
42
|
+
const region = detail.regions[i];
|
|
43
|
+
const at = 4 + i * 8;
|
|
44
|
+
const repeat = region.repeat;
|
|
45
|
+
const across = typeof repeat === 'number' ? repeat : repeat[0];
|
|
46
|
+
const down = typeof repeat === 'number' ? repeat : repeat[1];
|
|
47
|
+
out[at] = Math.max(0, Math.floor(region.normalLayer));
|
|
48
|
+
out[at + 1] =
|
|
49
|
+
region.occlusionLayer === undefined ? -1 : Math.max(0, Math.floor(region.occlusionLayer));
|
|
50
|
+
out[at + 2] = Math.max(0, region.normalStrength ?? 1);
|
|
51
|
+
out[at + 3] = Math.min(1, Math.max(0, region.occlusionStrength ?? 1));
|
|
52
|
+
/* A repeat at or below zero has no grain to show, so it is one rather than a mirror or nothing. */
|
|
53
|
+
out[at + 4] = across > 0 ? across : 1;
|
|
54
|
+
out[at + 5] = down > 0 ? down : 1;
|
|
55
|
+
out[at + 6] = region.offset?.[0] ?? 0;
|
|
56
|
+
out[at + 7] = region.offset?.[1] ?? 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether a material's detail can be drawn, given what else wants the slot its maps are read
|
|
61
|
+
* through: a shading model's own map, or a layered material's mask where it is a map. Either must
|
|
62
|
+
* be the detail's array itself. And a material placed by the world has no base coordinates for a
|
|
63
|
+
* region map to be read at, so its detail is refused too.
|
|
64
|
+
*/
|
|
65
|
+
export function detailRefusal(detail, slotTakenBy, projected) {
|
|
66
|
+
if (projected)
|
|
67
|
+
return 'projection';
|
|
68
|
+
if (slotTakenBy !== null && slotTakenBy !== undefined && slotTakenBy !== detail.maps) {
|
|
69
|
+
return 'slot';
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/** A material's detail, or null where it names none or no region. */
|
|
74
|
+
export function detailOf(material) {
|
|
75
|
+
const detail = material?.detail ?? null;
|
|
76
|
+
return detail === null || detail.regions.length === 0 ? null : detail;
|
|
77
|
+
}
|
|
78
|
+
/** The sentence each backend says once when it draws a material without its detail. */
|
|
79
|
+
export function detailRefusalWarning(refusal) {
|
|
80
|
+
return refusal === 'slot'
|
|
81
|
+
? "Renderer: a material's detail is drawn without: its maps are read where a shading model's " +
|
|
82
|
+
"map goes, and this material binds another array there, its `modelMap` or its layers' " +
|
|
83
|
+
'mask. Put the detail layers in the `modelMap` array and name that array as `detail.maps`.'
|
|
84
|
+
: "Renderer: a material's detail is drawn without: it is placed by the world (`projection`), " +
|
|
85
|
+
'so there are no base coordinates for its regions and repeats to be read at.';
|
|
86
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
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 { SurfaceDetail } from './surfaceDetail.ts';
|
|
11
12
|
import type { SurfaceLayers } from './surfaceLayers.ts';
|
|
12
13
|
import type { SurfaceProjection } from './surfaceProjection.ts';
|
|
13
14
|
import type { SurfaceModel } from './surfaceModel.ts';
|
|
@@ -235,6 +236,14 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
|
|
|
235
236
|
* vertices. See `surfaceLayers.ts` for the rest and what it gives up.
|
|
236
237
|
*/
|
|
237
238
|
layers?: SurfaceLayers<Texture> | null;
|
|
239
|
+
/**
|
|
240
|
+
* Fine grain tiled over the base maps: up to sixteen regions, each a detail normal map, and an
|
|
241
|
+
* occlusion, at its own repeat and strength, a region map in the ORM array choosing which one each
|
|
242
|
+
* texel wears. The detail maps are layers of an array read where `modelMap` goes, so a material
|
|
243
|
+
* with a shading model's map puts them in that array. **A lit switch**, compiled in the first time
|
|
244
|
+
* a material asks. See `surfaceDetail.ts` for the rest and what it gives up.
|
|
245
|
+
*/
|
|
246
|
+
detail?: SurfaceDetail<Texture> | null;
|
|
238
247
|
}
|
|
239
248
|
export interface SurfaceTextureOptions {
|
|
240
249
|
/**
|
|
@@ -22,15 +22,27 @@ import type { MeshData } from '@driftengine/drft';
|
|
|
22
22
|
* on the CPU. What would make this wrong is a value outside its range, which the range check sends
|
|
23
23
|
* to floats rather than clamping, or a shader reading an attribute as something other than the
|
|
24
24
|
* value it carries.
|
|
25
|
+
*
|
|
26
|
+
* **A mesh whose colours are rewritten keeps them as floats in a buffer of their own** (`stream`),
|
|
27
|
+
* whatever they are: one value every vertex shares has no buffer to rewrite, sixteen bits would
|
|
28
|
+
* have to be packed on the CPU every rewrite, and colours interleaved with the rest of a vertex
|
|
29
|
+
* could only be rewritten by keeping the rows, which a mesh lets go once it is uploaded. It costs
|
|
30
|
+
* twelve bytes a vertex where the colours might have cost none, which is why a mesh asks for it.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Floats; sixteen-bit fixed point, signed or not; one value for every vertex; or floats in a
|
|
34
|
+
* buffer of their own, for an attribute a caller rewrites.
|
|
25
35
|
*/
|
|
26
|
-
|
|
27
|
-
export type AttributePacking = 'float' | 'snorm16' | 'unorm16' | 'constant';
|
|
36
|
+
export type AttributePacking = 'float' | 'snorm16' | 'unorm16' | 'constant' | 'stream';
|
|
28
37
|
/** The attributes this decides for. Every other one travels as floats. */
|
|
29
38
|
export declare const PACKED_ATTRIBUTES: readonly ['normals', 'tangents', 'colors', 'emissive', 'weights', 'weights2'];
|
|
30
39
|
export type PackedAttribute = (typeof PACKED_ATTRIBUTES)[number];
|
|
31
40
|
export type VertexPacking = Readonly<Record<PackedAttribute, AttributePacking>>;
|
|
32
|
-
/**
|
|
33
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The choice for this mesh. `dynamic` is a mesh whose positions and normals are rewritten, and
|
|
43
|
+
* `dynamicColors` one whose colours are.
|
|
44
|
+
*/
|
|
45
|
+
export declare function vertexPackingOf(data: MeshData, dynamic: boolean, dynamicColors?: boolean): VertexPacking;
|
|
34
46
|
/**
|
|
35
47
|
* What 1 is in each fixed point, the values WebGPU's `snorm16` and `unorm16` and WebGL2's
|
|
36
48
|
* normalised `SHORT` and `UNSIGNED_SHORT` divide by. A value is stored rounded to nearest, so 1, 0
|
|
@@ -7,12 +7,19 @@ export const PACKED_ATTRIBUTES = [
|
|
|
7
7
|
'weights',
|
|
8
8
|
'weights2',
|
|
9
9
|
];
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The choice for this mesh. `dynamic` is a mesh whose positions and normals are rewritten, and
|
|
12
|
+
* `dynamicColors` one whose colours are.
|
|
13
|
+
*/
|
|
14
|
+
export function vertexPackingOf(data, dynamic, dynamicColors = false) {
|
|
12
15
|
return {
|
|
13
16
|
normals: dynamic ? 'float' : fixedOrFloat(data.normals, -1),
|
|
14
17
|
tangents: fixedOrFloat(data.tangents, -1),
|
|
15
|
-
colors:
|
|
18
|
+
colors: dynamicColors
|
|
19
|
+
? 'stream'
|
|
20
|
+
: shared(data.colors, 3)
|
|
21
|
+
? 'constant'
|
|
22
|
+
: fixedOrFloat(data.colors, 0),
|
|
16
23
|
emissive: shared(data.emissive, 1) ? 'constant' : 'float',
|
|
17
24
|
weights: fixedOrFloat(data.weights, 0),
|
|
18
25
|
weights2: fixedOrFloat(data.weights2, 0),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftengine/core",
|
|
3
|
-
"version": "4.13.
|
|
3
|
+
"version": "4.13.3",
|
|
4
4
|
"description": "The runtime of DriftEngine, a WebGPU game engine with a WebGL2 fallback: renderer, loop, input, cameras, scene graph and physics",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@driftengine/drft": "4.13.
|
|
33
|
-
"@driftengine/physics": "4.13.
|
|
32
|
+
"@driftengine/drft": "4.13.3",
|
|
33
|
+
"@driftengine/physics": "4.13.3",
|
|
34
34
|
"gl-matrix": "^3.4.3"
|
|
35
35
|
},
|
|
36
36
|
"author": "Drift Technologies",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"prepack": "node ../../scripts/build.mjs @driftengine/core"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@driftengine/nav": "4.13.
|
|
71
|
-
"@driftengine/network": "4.13.
|
|
72
|
-
"@driftengine/texture": "4.13.
|
|
70
|
+
"@driftengine/nav": "4.13.3",
|
|
71
|
+
"@driftengine/network": "4.13.3",
|
|
72
|
+
"@driftengine/texture": "4.13.3"
|
|
73
73
|
}
|
|
74
74
|
}
|
package/src/index.ts
CHANGED
|
@@ -483,6 +483,7 @@ export type {
|
|
|
483
483
|
BoltHandle,
|
|
484
484
|
CausticsHandle,
|
|
485
485
|
ClothBindingHandle,
|
|
486
|
+
PointSkinHandle,
|
|
486
487
|
ClothParticlesHandle,
|
|
487
488
|
FlockHandle,
|
|
488
489
|
IncrementalMeshHandle,
|
|
@@ -503,6 +504,7 @@ export type {
|
|
|
503
504
|
WindStreakHandle,
|
|
504
505
|
} from './render/backend/api.ts';
|
|
505
506
|
export type { ClothBindingData } from './render/clothBindingData.ts';
|
|
507
|
+
export type { PointSkinData } from './render/pointSkin.ts';
|
|
506
508
|
/*
|
|
507
509
|
* A material's shading model, for what the standard one is not: brushed metal, hair, skin, an eye.
|
|
508
510
|
* A frozen descriptor made by a factory and set as `SurfaceMaterial.model`; each is a pipeline of
|
|
@@ -675,6 +677,8 @@ export { SurfaceTexture } from './render/surfaceTexture.ts';
|
|
|
675
677
|
export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
|
|
676
678
|
export type { SurfaceProjection } from './render/surfaceProjection.ts';
|
|
677
679
|
export type { SurfaceLayers } from './render/surfaceLayers.ts';
|
|
680
|
+
export type { SurfaceDetail, SurfaceDetailRegion } from './render/surfaceDetail.ts';
|
|
681
|
+
export { MAX_DETAIL_REGIONS } from './render/surfaceDetail.ts';
|
|
678
682
|
export type { SurfaceLayerLook } from './render/surfaceLayerLooks.ts';
|
|
679
683
|
/*
|
|
680
684
|
* Compressed textures — BC, ETC2 and EAC, ASTC: the source `createSurfaceTexture` takes as blocks,
|
|
@@ -12,6 +12,7 @@ import type { CompressedTextureSource, SurfaceSource } from '../compressedSource
|
|
|
12
12
|
import type { LightmapPage } from '../lightmap.ts';
|
|
13
13
|
import type { DeviceParticles } from '../particlePool.ts';
|
|
14
14
|
import type { ClothBindingData } from '../clothBindingData.ts';
|
|
15
|
+
import type { PointSkinData } from '../pointSkin.ts';
|
|
15
16
|
import type { TextStyle } from '../textLayout.ts';
|
|
16
17
|
import type { SdfFont } from '../sdfFont.ts';
|
|
17
18
|
import type { SdfTextStyle } from '../sdfTextLayout.ts';
|
|
@@ -64,6 +65,15 @@ export interface ClothBindingHandle {
|
|
|
64
65
|
/* Deliberately empty, for the weak-type reason `MeshHandle` explains. */
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* A dynamic mesh's point skin on the device, from `createPointSkin`: its vertices as weighted points
|
|
70
|
+
* in its joints' spaces. Opaque likewise.
|
|
71
|
+
*/
|
|
72
|
+
/* eslint-disable-next-line @typescript-eslint/no-empty-object-type -- opaque by design */
|
|
73
|
+
export interface PointSkinHandle {
|
|
74
|
+
/* Deliberately empty, for the weak-type reason `MeshHandle` explains. */
|
|
75
|
+
}
|
|
76
|
+
|
|
67
77
|
/** One character's cloth particles on the device, from `createClothParticles`. Opaque likewise. */
|
|
68
78
|
/* eslint-disable-next-line @typescript-eslint/no-empty-object-type -- opaque by design */
|
|
69
79
|
export interface ClothParticlesHandle {
|
|
@@ -249,11 +259,20 @@ export interface MeshOptions {
|
|
|
249
259
|
/**
|
|
250
260
|
* Whether this mesh's positions will be rewritten with `updateMesh`.
|
|
251
261
|
*
|
|
252
|
-
* Costs a `DYNAMIC_DRAW` hint on WebGL2 and
|
|
253
|
-
*
|
|
254
|
-
*
|
|
262
|
+
* Costs a `DYNAMIC_DRAW` hint on WebGL2, and on WebGPU two buffers of its own for the positions
|
|
263
|
+
* and the normals beside the rest of the vertex, bound with it on every draw, and floats for the
|
|
264
|
+
* normals where they might have been sixteen bits. Paid only where a consumer asks.
|
|
255
265
|
*/
|
|
256
266
|
readonly dynamic?: boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Whether this mesh's colours will be rewritten with `updateMeshColors`.
|
|
269
|
+
*
|
|
270
|
+
* Costs the colours as floats in a buffer of their own, twelve bytes a vertex where they might
|
|
271
|
+
* have been sixteen bits or, for one colour every vertex shares, nothing at all. Independent of
|
|
272
|
+
* `dynamic`: a surface shaded again as a camera moves rewrites its colours every frame and its
|
|
273
|
+
* positions never, and should pay for neither the positions' buffers nor their motion.
|
|
274
|
+
*/
|
|
275
|
+
readonly dynamicColors?: boolean;
|
|
257
276
|
}
|
|
258
277
|
|
|
259
278
|
export type RendererApi = Omit<
|
|
@@ -262,6 +281,7 @@ export type RendererApi = Omit<
|
|
|
262
281
|
| 'createMesh'
|
|
263
282
|
| 'createMeshIncremental'
|
|
264
283
|
| 'updateMesh'
|
|
284
|
+
| 'updateMeshColors'
|
|
265
285
|
| 'drawMesh'
|
|
266
286
|
| 'disposeMesh'
|
|
267
287
|
| 'drawTranslucentMesh'
|
|
@@ -330,6 +350,9 @@ export type RendererApi = Omit<
|
|
|
330
350
|
| 'setCloth'
|
|
331
351
|
| 'disposeClothBinding'
|
|
332
352
|
| 'disposeClothParticles'
|
|
353
|
+
| 'createPointSkin'
|
|
354
|
+
| 'posePointSkin'
|
|
355
|
+
| 'disposePointSkin'
|
|
333
356
|
> & {
|
|
334
357
|
/**
|
|
335
358
|
* A mesh's cloth binding: which simulation triangle each of its vertices follows, where on it and
|
|
@@ -354,6 +377,29 @@ export type RendererApi = Omit<
|
|
|
354
377
|
setCloth(binding: ClothBindingHandle | null, particles?: ClothParticlesHandle | null): void;
|
|
355
378
|
disposeClothBinding(binding: ClothBindingHandle): void;
|
|
356
379
|
disposeClothParticles(particles: ClothParticlesHandle): void;
|
|
380
|
+
/**
|
|
381
|
+
* A dynamic mesh posed on the device from weighted points in its joints' own spaces
|
|
382
|
+
* (`PointSkinData`): each vertex the weighted sum of up to four points, each moved by its joint,
|
|
383
|
+
* and a normal turned by its first joint. Posed into the mesh's own positions and normals, so
|
|
384
|
+
* every pass draws it as any dynamic mesh, motion vectors included. See `pointSkin.ts`.
|
|
385
|
+
*
|
|
386
|
+
* **For a rig whose vertices are stored that way**, which ordinary skinning cannot pose: it moves
|
|
387
|
+
* one bind-pose position by a blend of joints. Such a rig was posed on the main thread and
|
|
388
|
+
* uploaded every frame, reported at 38% of the main thread's drawing time.
|
|
389
|
+
*
|
|
390
|
+
* The mesh must be `{ dynamic: true }` and have the skin's vertex count; any other is refused in
|
|
391
|
+
* words, as is a joint past 511.
|
|
392
|
+
*/
|
|
393
|
+
createPointSkin(mesh: MeshHandle, data: PointSkinData): PointSkinHandle;
|
|
394
|
+
/**
|
|
395
|
+
* Pose a skin: `matrices` holds each joint's transform into the mesh's model space, sixteen floats
|
|
396
|
+
* a joint, column-major, at least as many joints as the skin names. **Once a frame, before the
|
|
397
|
+
* mesh's draws**: a second pose in a frame reaches every draw of it on WebGPU, as a second
|
|
398
|
+
* `updateMesh` does. The mesh's bounds become a box around every joint's reach, so it is culled
|
|
399
|
+
* as it moves without the posed vertices ever coming back to the CPU.
|
|
400
|
+
*/
|
|
401
|
+
posePointSkin(skin: PointSkinHandle, matrices: Float32Array): void;
|
|
402
|
+
disposePointSkin(skin: PointSkinHandle): void;
|
|
357
403
|
/**
|
|
358
404
|
* What the following draws lay over their surfaces beyond their materials — a rim of light, a
|
|
359
405
|
* dissolve with a glowing edge, wrinkle normals by region — or null for none, which every pass
|
|
@@ -928,10 +974,12 @@ export type RendererApi = Omit<
|
|
|
928
974
|
* quads with a matrix each, which caps the resolution at the number of draw calls a consumer
|
|
929
975
|
* can afford. A consumer found it by trying to hang a banner.
|
|
930
976
|
*
|
|
931
|
-
* **Only positions and normals.**
|
|
932
|
-
*
|
|
933
|
-
*
|
|
934
|
-
*
|
|
977
|
+
* **Only positions and normals.** Emissive and the rest describe what a surface *is* and do not
|
|
978
|
+
* change because it bent; rewriting them every frame would be uploading unchanged bytes. Colour
|
|
979
|
+
* is the exception a consumer found, a surface shaded again every frame without moving, and it
|
|
980
|
+
* has `updateMeshColors` of its own rather than a place here, so a rewrite of either sends only
|
|
981
|
+
* what changed. The vertex *count* is fixed: the index buffer, the pipeline and every other
|
|
982
|
+
* attribute are sized against it, and an update of a different size is refused naming both.
|
|
935
983
|
*
|
|
936
984
|
* The mesh must have been created with `{ dynamic: true }`. That is not ceremony — it decides
|
|
937
985
|
* the buffer hint on one backend and what has to be kept on the other, and a mesh that never
|
|
@@ -939,6 +987,21 @@ export type RendererApi = Omit<
|
|
|
939
987
|
*/
|
|
940
988
|
updateMesh(mesh: MeshHandle, positions: Float32Array, normals?: Float32Array): void;
|
|
941
989
|
|
|
990
|
+
/**
|
|
991
|
+
* Rewrite a mesh's colours, three floats a vertex, linear as `MeshData.colors` is.
|
|
992
|
+
*
|
|
993
|
+
* **For a surface whose colour changes while it stays where it is**: ground shaded with its
|
|
994
|
+
* distance from a moving camera, a heat map, a tile lit by a game's own rules. Without it such a
|
|
995
|
+
* surface is made again as a new mesh every frame, which is a buffer allocated and let go each
|
|
996
|
+
* frame on both backends and every pipeline it draws with looked up again.
|
|
997
|
+
*
|
|
998
|
+
* The mesh must have been created with `{ dynamicColors: true }`, which gives the colours a buffer
|
|
999
|
+
* of their own; any other is refused in words. Nothing else moves: not the positions, not the
|
|
1000
|
+
* bounds, and no motion vector, since a colour does not move. A different vertex count is refused
|
|
1001
|
+
* naming both.
|
|
1002
|
+
*/
|
|
1003
|
+
updateMeshColors(mesh: MeshHandle, colors: Float32Array): void;
|
|
1004
|
+
|
|
942
1005
|
/** Draw a handle this renderer made. Passing one from the other backend is a caller error. */
|
|
943
1006
|
drawMesh(
|
|
944
1007
|
mesh: MeshHandle,
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A point skin on WebGL2: posed by transform feedback, a vertex stage run once a vertex with the
|
|
3
|
+
* rasteriser off, its two outputs captured into the dynamic mesh's own position and normal buffers.
|
|
4
|
+
* What a point skin is, and why it poses into a mesh, is `pointSkin.ts`; the same arithmetic in the
|
|
5
|
+
* same order is `backend/webgpu/shaders/pointSkin.wgsl.ts`.
|
|
6
|
+
*
|
|
7
|
+
* **Every binding it makes it undoes**, because it runs between a frame's draws: the program, the
|
|
8
|
+
* vertex array, the joint texture on its own unit, the feedback buffers and the discard. A buffer
|
|
9
|
+
* left bound for feedback while a draw reads it as vertices is an error WebGL2 raises at the draw.
|
|
10
|
+
*/
|
|
11
|
+
import { POINT_SKIN_TEXTURE_UNIT } from '../../lightBudget.ts';
|
|
12
|
+
import type { Mesh } from '../../mesh.ts';
|
|
13
|
+
import { reachOf, type PointSkinData, type PointSkinReach } from '../../pointSkin.ts';
|
|
14
|
+
import { compileProgram } from '../../shader.ts';
|
|
15
|
+
|
|
16
|
+
const POSE_VERT = /* glsl */ `#version 300 es
|
|
17
|
+
precision highp float;
|
|
18
|
+
precision highp int;
|
|
19
|
+
layout(location = 0) in vec4 aNamed;
|
|
20
|
+
layout(location = 1) in vec4 aWeights;
|
|
21
|
+
layout(location = 2) in vec3 aPoint0;
|
|
22
|
+
layout(location = 3) in vec3 aPoint1;
|
|
23
|
+
layout(location = 4) in vec3 aPoint2;
|
|
24
|
+
layout(location = 5) in vec3 aPoint3;
|
|
25
|
+
layout(location = 6) in vec3 aNormal;
|
|
26
|
+
uniform highp sampler2D uJoints;
|
|
27
|
+
out vec3 vPosition;
|
|
28
|
+
out vec3 vNormal;
|
|
29
|
+
|
|
30
|
+
mat4 jointAt(float joint) {
|
|
31
|
+
int at = int(joint) * 4;
|
|
32
|
+
return mat4(
|
|
33
|
+
texelFetch(uJoints, ivec2(at, 0), 0),
|
|
34
|
+
texelFetch(uJoints, ivec2(at + 1, 0), 0),
|
|
35
|
+
texelFetch(uJoints, ivec2(at + 2, 0), 0),
|
|
36
|
+
texelFetch(uJoints, ivec2(at + 3, 0), 0)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
void main() {
|
|
41
|
+
vec3 p = vec3(0.0);
|
|
42
|
+
if (aWeights.x != 0.0) p += aWeights.x * (jointAt(aNamed.x) * vec4(aPoint0, 1.0)).xyz;
|
|
43
|
+
if (aWeights.y != 0.0) p += aWeights.y * (jointAt(aNamed.y) * vec4(aPoint1, 1.0)).xyz;
|
|
44
|
+
if (aWeights.z != 0.0) p += aWeights.z * (jointAt(aNamed.z) * vec4(aPoint2, 1.0)).xyz;
|
|
45
|
+
if (aWeights.w != 0.0) p += aWeights.w * (jointAt(aNamed.w) * vec4(aPoint3, 1.0)).xyz;
|
|
46
|
+
vec3 raw = mat3(jointAt(aNamed.x)) * aNormal;
|
|
47
|
+
float l = length(raw);
|
|
48
|
+
vNormal = l > 0.0 ? raw / l : raw;
|
|
49
|
+
vPosition = p;
|
|
50
|
+
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
const POSE_FRAG = /* glsl */ `#version 300 es
|
|
55
|
+
precision mediump float;
|
|
56
|
+
out vec4 colour;
|
|
57
|
+
void main() {
|
|
58
|
+
colour = vec4(0.0);
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
/** The program and feedback object every skin on a context poses with, made at the first skin. */
|
|
63
|
+
export interface PointSkinPose {
|
|
64
|
+
readonly program: WebGLProgram;
|
|
65
|
+
readonly feedback: WebGLTransformFeedback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createPointSkinPose(gl: WebGL2RenderingContext): PointSkinPose {
|
|
69
|
+
const program = compileProgram(gl, POSE_VERT, POSE_FRAG, 'pointSkin.pose', [
|
|
70
|
+
'vPosition',
|
|
71
|
+
'vNormal',
|
|
72
|
+
]);
|
|
73
|
+
gl.useProgram(program);
|
|
74
|
+
gl.uniform1i(gl.getUniformLocation(program, 'uJoints'), POINT_SKIN_TEXTURE_UNIT);
|
|
75
|
+
gl.useProgram(null);
|
|
76
|
+
const feedback = gl.createTransformFeedback();
|
|
77
|
+
if (feedback === null) throw new Error('createTransformFeedback failed');
|
|
78
|
+
return { program, feedback };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class GlPointSkin {
|
|
82
|
+
readonly mesh: Mesh;
|
|
83
|
+
readonly reach: PointSkinReach;
|
|
84
|
+
private readonly count: number;
|
|
85
|
+
private readonly vao: WebGLVertexArrayObject;
|
|
86
|
+
private readonly buffers: WebGLBuffer[] = [];
|
|
87
|
+
private readonly joints: WebGLTexture;
|
|
88
|
+
private readonly width: number;
|
|
89
|
+
|
|
90
|
+
constructor(gl: WebGL2RenderingContext, mesh: Mesh, data: PointSkinData) {
|
|
91
|
+
this.mesh = mesh;
|
|
92
|
+
this.reach = reachOf(data);
|
|
93
|
+
this.count = data.weights.length / 4;
|
|
94
|
+
const vao = gl.createVertexArray();
|
|
95
|
+
if (vao === null) throw new Error('createVertexArray failed');
|
|
96
|
+
this.vao = vao;
|
|
97
|
+
gl.bindVertexArray(vao);
|
|
98
|
+
this.attach(gl, data.joints, [[0, 4, 16, 0]]);
|
|
99
|
+
this.attach(gl, data.weights, [[1, 4, 16, 0]]);
|
|
100
|
+
this.attach(gl, data.points, [
|
|
101
|
+
[2, 3, 48, 0],
|
|
102
|
+
[3, 3, 48, 12],
|
|
103
|
+
[4, 3, 48, 24],
|
|
104
|
+
[5, 3, 48, 36],
|
|
105
|
+
]);
|
|
106
|
+
this.attach(gl, data.normals, [[6, 3, 12, 0]]);
|
|
107
|
+
gl.bindVertexArray(null);
|
|
108
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
109
|
+
|
|
110
|
+
/* One row, four texels a joint, as the skin palette is. */
|
|
111
|
+
this.width = Math.max(1, this.reach.joints * 4);
|
|
112
|
+
const joints = gl.createTexture();
|
|
113
|
+
if (joints === null) throw new Error('createTexture failed');
|
|
114
|
+
this.joints = joints;
|
|
115
|
+
gl.activeTexture(gl.TEXTURE0 + POINT_SKIN_TEXTURE_UNIT);
|
|
116
|
+
gl.bindTexture(gl.TEXTURE_2D, joints);
|
|
117
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, this.width, 1);
|
|
118
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
119
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
120
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** One buffer, read as each `[location, size, stride, offset]` says. */
|
|
124
|
+
private attach(
|
|
125
|
+
gl: WebGL2RenderingContext,
|
|
126
|
+
values: Float32Array,
|
|
127
|
+
reads: readonly (readonly [number, number, number, number])[],
|
|
128
|
+
): void {
|
|
129
|
+
const buffer = gl.createBuffer();
|
|
130
|
+
if (buffer === null) throw new Error('createBuffer failed');
|
|
131
|
+
this.buffers.push(buffer);
|
|
132
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
133
|
+
gl.bufferData(gl.ARRAY_BUFFER, values, gl.STATIC_DRAW);
|
|
134
|
+
for (const [location, size, stride, offset] of reads) {
|
|
135
|
+
gl.enableVertexAttribArray(location);
|
|
136
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, stride, offset);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Pose every vertex into the mesh's buffers now, and undo every binding it made. */
|
|
141
|
+
pose(gl: WebGL2RenderingContext, pose: PointSkinPose, matrices: Float32Array): void {
|
|
142
|
+
const streams = this.mesh.streams;
|
|
143
|
+
if (streams === null) return;
|
|
144
|
+
gl.useProgram(pose.program);
|
|
145
|
+
gl.bindVertexArray(this.vao);
|
|
146
|
+
gl.activeTexture(gl.TEXTURE0 + POINT_SKIN_TEXTURE_UNIT);
|
|
147
|
+
gl.bindTexture(gl.TEXTURE_2D, this.joints);
|
|
148
|
+
if (this.reach.joints > 0) {
|
|
149
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, 1, gl.RGBA, gl.FLOAT, matrices, 0);
|
|
150
|
+
}
|
|
151
|
+
gl.enable(gl.RASTERIZER_DISCARD);
|
|
152
|
+
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, pose.feedback);
|
|
153
|
+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, streams.positions);
|
|
154
|
+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, streams.normals);
|
|
155
|
+
gl.beginTransformFeedback(gl.POINTS);
|
|
156
|
+
gl.drawArrays(gl.POINTS, 0, this.count);
|
|
157
|
+
gl.endTransformFeedback();
|
|
158
|
+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
|
|
159
|
+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, null);
|
|
160
|
+
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
|
|
161
|
+
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, null);
|
|
162
|
+
gl.disable(gl.RASTERIZER_DISCARD);
|
|
163
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
164
|
+
gl.bindVertexArray(null);
|
|
165
|
+
gl.useProgram(null);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
dispose(gl: WebGL2RenderingContext): void {
|
|
169
|
+
for (const buffer of this.buffers) gl.deleteBuffer(buffer);
|
|
170
|
+
gl.deleteTexture(this.joints);
|
|
171
|
+
gl.deleteVertexArray(this.vao);
|
|
172
|
+
}
|
|
173
|
+
}
|