reze-engine 0.42.3 → 0.50.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/README.md +40 -410
- package/dist/camera.d.ts +3 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +33 -8
- package/dist/engine.d.ts +924 -29
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4043 -278
- package/dist/graph/registry.d.ts +2 -2
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +1 -1
- package/dist/graph/slots.d.ts +0 -1
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +37 -9
- package/dist/hdr.d.ts +18 -0
- package/dist/hdr.d.ts.map +1 -0
- package/dist/hdr.js +162 -0
- package/dist/ibl.d.ts +19 -0
- package/dist/ibl.d.ts.map +1 -0
- package/dist/ibl.js +113 -0
- package/dist/ik-solver.d.ts +2 -1
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/math.d.ts +20 -1
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +23 -16
- package/dist/midi-loader.d.ts +10 -0
- package/dist/midi-loader.d.ts.map +1 -0
- package/dist/midi-loader.js +247 -0
- package/dist/model.d.ts +2 -13
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +29 -0
- package/dist/param-track.d.ts +48 -0
- package/dist/param-track.d.ts.map +1 -0
- package/dist/param-track.js +80 -0
- package/dist/physics/types.d.ts.map +1 -1
- package/dist/physics/types.js +3 -0
- package/dist/reflection.d.ts +27 -0
- package/dist/reflection.d.ts.map +1 -0
- package/dist/reflection.js +93 -0
- package/dist/shaders/anchor-table.d.ts +56 -0
- package/dist/shaders/anchor-table.d.ts.map +1 -0
- package/dist/shaders/anchor-table.js +128 -0
- package/dist/shaders/audio-api.d.ts +3 -0
- package/dist/shaders/audio-api.d.ts.map +1 -0
- package/dist/shaders/audio-api.js +81 -0
- package/dist/shaders/cast-api.d.ts +2 -0
- package/dist/shaders/cast-api.d.ts.map +1 -0
- package/dist/shaders/cast-api.js +121 -0
- package/dist/shaders/cast-layout.d.ts +21 -0
- package/dist/shaders/cast-layout.d.ts.map +1 -0
- package/dist/shaders/cast-layout.js +20 -0
- package/dist/shaders/lights.d.ts +79 -0
- package/dist/shaders/lights.d.ts.map +1 -0
- package/dist/shaders/lights.js +269 -0
- package/dist/shaders/lyrics-api.d.ts +39 -0
- package/dist/shaders/lyrics-api.d.ts.map +1 -0
- package/dist/shaders/lyrics-api.js +187 -0
- package/dist/shaders/materials/common.d.ts +2 -4
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +87 -35
- package/dist/shaders/midi-api.d.ts +10 -0
- package/dist/shaders/midi-api.d.ts.map +1 -0
- package/dist/shaders/midi-api.js +114 -0
- package/dist/shaders/passes/composite.d.ts +31 -15
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +225 -135
- package/dist/shaders/passes/cull.d.ts +2 -0
- package/dist/shaders/passes/cull.d.ts.map +1 -0
- package/dist/shaders/passes/cull.js +138 -0
- package/dist/shaders/passes/field-blit.d.ts +26 -0
- package/dist/shaders/passes/field-blit.d.ts.map +1 -0
- package/dist/shaders/passes/field-blit.js +65 -0
- package/dist/shaders/passes/grid.d.ts +31 -0
- package/dist/shaders/passes/grid.d.ts.map +1 -0
- package/dist/shaders/passes/grid.js +169 -0
- package/dist/shaders/passes/ground.d.ts +13 -1
- package/dist/shaders/passes/ground.d.ts.map +1 -1
- package/dist/shaders/passes/ground.js +170 -25
- package/dist/shaders/passes/hosted-api.d.ts +57 -0
- package/dist/shaders/passes/hosted-api.d.ts.map +1 -0
- package/dist/shaders/passes/hosted-api.js +166 -0
- package/dist/shaders/passes/id-debug.d.ts +28 -0
- package/dist/shaders/passes/id-debug.d.ts.map +1 -0
- package/dist/shaders/passes/id-debug.js +74 -0
- package/dist/shaders/passes/particles.d.ts +66 -0
- package/dist/shaders/passes/particles.d.ts.map +1 -0
- package/dist/shaders/passes/particles.js +279 -0
- package/dist/shaders/passes/scene-contract.d.ts +128 -0
- package/dist/shaders/passes/scene-contract.d.ts.map +1 -0
- package/dist/shaders/passes/scene-contract.js +207 -0
- package/dist/shaders/passes/sim.d.ts +34 -0
- package/dist/shaders/passes/sim.d.ts.map +1 -0
- package/dist/shaders/passes/sim.js +169 -0
- package/dist/shaders/passes/trails.d.ts +59 -0
- package/dist/shaders/passes/trails.d.ts.map +1 -0
- package/dist/shaders/passes/trails.js +340 -0
- package/dist/shaders/score-api.d.ts +10 -0
- package/dist/shaders/score-api.d.ts.map +1 -0
- package/dist/shaders/score-api.js +114 -0
- package/dist/shadow-cascades.d.ts +45 -0
- package/dist/shadow-cascades.d.ts.map +1 -0
- package/dist/shadow-cascades.js +70 -0
- package/dist/vmd-loader.d.ts +3 -2
- package/dist/vmd-loader.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/camera.ts +31 -8
- package/src/engine.ts +4535 -296
- package/src/graph/registry.ts +2 -2
- package/src/graph/slots.ts +37 -9
- package/src/hdr.ts +156 -0
- package/src/ibl.ts +115 -0
- package/src/ik-solver.ts +1 -1
- package/src/index.ts +12 -0
- package/src/math.ts +23 -17
- package/src/midi-loader.ts +246 -0
- package/src/model.ts +31 -3
- package/src/param-track.ts +83 -0
- package/src/physics/types.ts +4 -1
- package/src/reflection.ts +94 -0
- package/src/shaders/anchor-table.ts +147 -0
- package/src/shaders/audio-api.ts +82 -0
- package/src/shaders/cast-api.ts +123 -0
- package/src/shaders/cast-layout.ts +20 -0
- package/src/shaders/lights.ts +280 -0
- package/src/shaders/lyrics-api.ts +202 -0
- package/src/shaders/materials/common.ts +89 -35
- package/src/shaders/midi-api.ts +116 -0
- package/src/shaders/passes/composite.ts +244 -136
- package/src/shaders/passes/cull.ts +139 -0
- package/src/shaders/passes/grid.ts +178 -0
- package/src/shaders/passes/ground.ts +172 -25
- package/src/shaders/passes/hosted-api.ts +171 -0
- package/src/shaders/passes/id-debug.ts +75 -0
- package/src/shaders/passes/particles.ts +340 -0
- package/src/shaders/passes/scene-contract.ts +266 -0
- package/src/shaders/passes/trails.ts +390 -0
- package/src/shadow-cascades.ts +97 -0
- package/src/vmd-loader.ts +2 -2
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scene pass's attachment contract, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Everything drawn INSIDE the scene pass — models, ground, outline hulls,
|
|
5
|
+
* particles, ribbons, and the transparent depth prepass — shares one set of
|
|
6
|
+
* attachments, and therefore one set of formats, blends and write masks. That
|
|
7
|
+
* agreement used to be restated at every pipeline that joins the pass and in
|
|
8
|
+
* every shader that writes to it, which is why adding an attachment was
|
|
9
|
+
* dangerous rather than tedious: a class left behind does not fail loudly, it
|
|
10
|
+
* fails as a validation error naming a pipeline that was never edited.
|
|
11
|
+
*
|
|
12
|
+
* So the contract is DATA here, and the pipelines ask for it by render class.
|
|
13
|
+
* Adding the id attachment (MRT) becomes one edit in this file plus a per-class
|
|
14
|
+
* decision about whether that class writes it — which is the shape of the
|
|
15
|
+
* question, and now the shape of the code.
|
|
16
|
+
*
|
|
17
|
+
* WHAT IS NOT HERE. Two passes look like they belong and do not:
|
|
18
|
+
* - the FIELD pass has its own pair of rgba16float targets, its own blend and
|
|
19
|
+
* no MSAA. It is a different pass with a different contract.
|
|
20
|
+
* - the gizmo and selection-edge pipelines draw to the SWAPCHAIN in their own
|
|
21
|
+
* unmultisampled pass, so the presentation format is the whole of their
|
|
22
|
+
* contract. The plan listed gizmo as a scene render class; the code says
|
|
23
|
+
* otherwise, and the code is right.
|
|
24
|
+
*/
|
|
25
|
+
/** The scene pass's attachments, as the engine has them at init. Passed in
|
|
26
|
+
* rather than imported: hdr is chosen per device (rg11b10ufloat where it is
|
|
27
|
+
* available and blendable, rgba16float otherwise). */
|
|
28
|
+
export type SceneFormats = {
|
|
29
|
+
/** The HDR colour attachment, @location(0). */
|
|
30
|
+
hdr: GPUTextureFormat;
|
|
31
|
+
/** The aux attachment, @location(1) — (bloom mask, coverage). */
|
|
32
|
+
aux: GPUTextureFormat;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The id attachment's format: (material index, object index), one u16 each.
|
|
36
|
+
*
|
|
37
|
+
* Two 16-bit channels rather than one 32-bit: 32-bit formats are not
|
|
38
|
+
* multisamplable, and this attachment is multisampled with the rest of the
|
|
39
|
+
* pass. Uint targets take no blend at all per spec, which is exactly right —
|
|
40
|
+
* an averaged id is not an id.
|
|
41
|
+
*/
|
|
42
|
+
export declare const SCENE_ID_FORMAT: GPUTextureFormat;
|
|
43
|
+
/** Called by the engine at init, after probing the device. */
|
|
44
|
+
export declare function setMrtIds(on: boolean): void;
|
|
45
|
+
export declare function mrtIdsEnabled(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* What is being drawn, which is the only thing that varies.
|
|
48
|
+
*
|
|
49
|
+
* The classes differ ONLY in blend and write mask; formats are the pass's, not
|
|
50
|
+
* the draw's. A class here is a thing with a reason, not a pipeline name — two
|
|
51
|
+
* pipelines that blend the same way share one.
|
|
52
|
+
*/
|
|
53
|
+
type SceneRenderClass =
|
|
54
|
+
/** Models, opaque and transparent. Straight alpha over. */
|
|
55
|
+
"material"
|
|
56
|
+
/** The shadow-catcher floor. Blends PREMULTIPLIED, not like a material: its
|
|
57
|
+
* coverage is a lit surface plus a colourless shadow layer, so it weights
|
|
58
|
+
* its own colour before the blend sees it. */
|
|
59
|
+
| "ground"
|
|
60
|
+
/** Backface-expanded hulls. Also a material blend — it is geometry. */
|
|
61
|
+
| "outline"
|
|
62
|
+
/** Particles and ribbons in their default, non-additive mode. */
|
|
63
|
+
| "particle"
|
|
64
|
+
/** Particles declaring `// @blend additive` — LIGHT rather than matter, so
|
|
65
|
+
* colour sums and alpha is left alone: a glow must not claim coverage it
|
|
66
|
+
* never occluded. The aux target sums with it, which is what lets an
|
|
67
|
+
* additive effect reach the bloom gate at all. */
|
|
68
|
+
| "particle-additive"
|
|
69
|
+
/** Ribbons. Additive colour like the above, but premultiplied by the
|
|
70
|
+
* fragment's own alpha on the way in (src-alpha, one) rather than added
|
|
71
|
+
* whole, and an ordinary alpha-over aux so a ribbon's mask does not
|
|
72
|
+
* saturate along every overlap. */
|
|
73
|
+
| "trail"
|
|
74
|
+
/** The transparent depth prepass: it exists to write DEPTH after the fabric's
|
|
75
|
+
* colour blended, so an outline drawn later is occluded behind it. It must
|
|
76
|
+
* therefore write no colour at all — the targets exist only to make the
|
|
77
|
+
* pipeline compatible with the pass it joins. */
|
|
78
|
+
| "depth-prepass";
|
|
79
|
+
/**
|
|
80
|
+
* The colour targets a scene-pass pipeline of this class declares, in
|
|
81
|
+
* attachment order.
|
|
82
|
+
*
|
|
83
|
+
* Fresh objects every call, deliberately: a caller that mutated a shared
|
|
84
|
+
* descriptor would change every pipeline built after it, and the ones built
|
|
85
|
+
* before would keep the old value — a difference that only shows up as one
|
|
86
|
+
* pipeline blending unlike its neighbours.
|
|
87
|
+
*/
|
|
88
|
+
export declare function sceneTargets(cls: SceneRenderClass, formats: SceneFormats): GPUColorTargetState[];
|
|
89
|
+
/**
|
|
90
|
+
* The pass's colour attachments, in order — for the things that describe the
|
|
91
|
+
* pass itself rather than a draw within it.
|
|
92
|
+
*
|
|
93
|
+
* A render bundle declares the formats it will be replayed into and is rejected
|
|
94
|
+
* against a pass that does not match, so the bundle encoder has to move in
|
|
95
|
+
* lockstep with the targets above. It restated the list independently until
|
|
96
|
+
* this existed, which made it the one consumer an MRT change would have missed
|
|
97
|
+
* — a bundle is recorded once and replayed, so the failure would have arrived
|
|
98
|
+
* at replay, naming the bundle rather than the attachment that changed.
|
|
99
|
+
*/
|
|
100
|
+
export declare function sceneColorFormats(formats: SceneFormats): GPUTextureFormat[];
|
|
101
|
+
/**
|
|
102
|
+
* The fragment-output struct a scene-pass shader returns.
|
|
103
|
+
*
|
|
104
|
+
* Emitted rather than written out per file so that the attachment list has one
|
|
105
|
+
* author. The struct and the targets above have to agree on count and order,
|
|
106
|
+
* and they now disagree in one file rather than in five.
|
|
107
|
+
*
|
|
108
|
+
* Only the shaders that will GAIN an output take this today — the materials
|
|
109
|
+
* (hand-written and graph-generated alike, through COMMON_FS_OUT_WGSL) and the
|
|
110
|
+
* ground. Outline, particles and ribbons keep their own declarations on
|
|
111
|
+
* purpose: they never write the id, so they would take an `id: false` argument
|
|
112
|
+
* forever, and their structs carry comments about their own blend that belong
|
|
113
|
+
* where they are.
|
|
114
|
+
*/
|
|
115
|
+
export declare function sceneFsOutWgsl(opts?: {
|
|
116
|
+
name?: string;
|
|
117
|
+
aux?: string;
|
|
118
|
+
}): string;
|
|
119
|
+
/**
|
|
120
|
+
* The line a fragment shader assigns its id with, or nothing when ids are off.
|
|
121
|
+
*
|
|
122
|
+
* Emitted rather than written into each shader for the same reason as the
|
|
123
|
+
* struct: with ids off there must be no assignment either, and a shader cannot
|
|
124
|
+
* ask the device what it supports.
|
|
125
|
+
*/
|
|
126
|
+
export declare function sceneIdWriteWgsl(out: string, material: string, object: string): string;
|
|
127
|
+
export {};
|
|
128
|
+
//# sourceMappingURL=scene-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-contract.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/scene-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;uDAEuD;AACvD,MAAM,MAAM,YAAY,GAAG;IACzB,+CAA+C;IAC/C,GAAG,EAAE,gBAAgB,CAAA;IACrB,iEAAiE;IACjE,GAAG,EAAE,gBAAgB,CAAA;CACtB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,gBAA6B,CAAA;AAkB3D,8DAA8D;AAC9D,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAE3C;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;;;GAMG;AACH,KAAK,gBAAgB;AACnB,2DAA2D;AACzD,UAAU;AACZ;;+CAE+C;GAC7C,QAAQ;AACV,uEAAuE;GACrE,SAAS;AACX,iEAAiE;GAC/D,UAAU;AACZ;;;mDAGmD;GACjD,mBAAmB;AACrB;;;oCAGoC;GAClC,OAAO;AACT;;;kDAGkD;GAChD,eAAe,CAAA;AAwEnB;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,GAAG,mBAAmB,EAAE,CAqBhG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAE3E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAmB7E;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtF"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scene pass's attachment contract, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Everything drawn INSIDE the scene pass — models, ground, outline hulls,
|
|
5
|
+
* particles, ribbons, and the transparent depth prepass — shares one set of
|
|
6
|
+
* attachments, and therefore one set of formats, blends and write masks. That
|
|
7
|
+
* agreement used to be restated at every pipeline that joins the pass and in
|
|
8
|
+
* every shader that writes to it, which is why adding an attachment was
|
|
9
|
+
* dangerous rather than tedious: a class left behind does not fail loudly, it
|
|
10
|
+
* fails as a validation error naming a pipeline that was never edited.
|
|
11
|
+
*
|
|
12
|
+
* So the contract is DATA here, and the pipelines ask for it by render class.
|
|
13
|
+
* Adding the id attachment (MRT) becomes one edit in this file plus a per-class
|
|
14
|
+
* decision about whether that class writes it — which is the shape of the
|
|
15
|
+
* question, and now the shape of the code.
|
|
16
|
+
*
|
|
17
|
+
* WHAT IS NOT HERE. Two passes look like they belong and do not:
|
|
18
|
+
* - the FIELD pass has its own pair of rgba16float targets, its own blend and
|
|
19
|
+
* no MSAA. It is a different pass with a different contract.
|
|
20
|
+
* - the gizmo and selection-edge pipelines draw to the SWAPCHAIN in their own
|
|
21
|
+
* unmultisampled pass, so the presentation format is the whole of their
|
|
22
|
+
* contract. The plan listed gizmo as a scene render class; the code says
|
|
23
|
+
* otherwise, and the code is right.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* The id attachment's format: (material index, object index), one u16 each.
|
|
27
|
+
*
|
|
28
|
+
* Two 16-bit channels rather than one 32-bit: 32-bit formats are not
|
|
29
|
+
* multisamplable, and this attachment is multisampled with the rest of the
|
|
30
|
+
* pass. Uint targets take no blend at all per spec, which is exactly right —
|
|
31
|
+
* an averaged id is not an id.
|
|
32
|
+
*/
|
|
33
|
+
export const SCENE_ID_FORMAT = "rg16uint";
|
|
34
|
+
/**
|
|
35
|
+
* Whether the scene pass carries the id attachment.
|
|
36
|
+
*
|
|
37
|
+
* Runtime rather than a compile-time constant, and mutable, because it is not
|
|
38
|
+
* only a decision — it is a CAPABILITY. Multisampled rg16uint has to be probed
|
|
39
|
+
* on the device (see the engine's init), and a device that cannot do it must
|
|
40
|
+
* leave this off. That forces the shaders to be assembled after the probe,
|
|
41
|
+
* which is why the two shader modules that gain an output stopped being
|
|
42
|
+
* module-level constants: a string baked at import cannot know what the device
|
|
43
|
+
* said.
|
|
44
|
+
*
|
|
45
|
+
* Set ONCE at init, before any pipeline or shader module is built. Nothing
|
|
46
|
+
* reads it per frame.
|
|
47
|
+
*/
|
|
48
|
+
let mrtIds = false;
|
|
49
|
+
/** Called by the engine at init, after probing the device. */
|
|
50
|
+
export function setMrtIds(on) {
|
|
51
|
+
mrtIds = on;
|
|
52
|
+
}
|
|
53
|
+
export function mrtIdsEnabled() {
|
|
54
|
+
return mrtIds;
|
|
55
|
+
}
|
|
56
|
+
const ALPHA_OVER = {
|
|
57
|
+
color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" },
|
|
58
|
+
alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
|
|
59
|
+
};
|
|
60
|
+
/** Colour sums; alpha is not touched (src zero, dst one). */
|
|
61
|
+
const ADD_KEEP_ALPHA = {
|
|
62
|
+
color: { srcFactor: "one", dstFactor: "one", operation: "add" },
|
|
63
|
+
alpha: { srcFactor: "zero", dstFactor: "one", operation: "add" },
|
|
64
|
+
};
|
|
65
|
+
/** Both channels sum. rg8unorm clamps at 1, which is the saturation alpha-over
|
|
66
|
+
* would have reached anyway. */
|
|
67
|
+
const ADD_BOTH = {
|
|
68
|
+
color: { srcFactor: "one", dstFactor: "one", operation: "add" },
|
|
69
|
+
alpha: { srcFactor: "one", dstFactor: "one", operation: "add" },
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* OVER for something that arrives ALREADY premultiplied: take the source whole
|
|
73
|
+
* and let it displace the destination by its own coverage.
|
|
74
|
+
*
|
|
75
|
+
* The ground needs this and nothing else does. Every other class writes a
|
|
76
|
+
* straight colour and an alpha, and the src-alpha factor premultiplies it once
|
|
77
|
+
* on the way in. The ground cannot: its coverage is the SUM of a lit surface
|
|
78
|
+
* and a colourless shadow-catcher layer, so it has to weight its own colour by
|
|
79
|
+
* the surface's share before it gets here. Handed to the src-alpha blend, that
|
|
80
|
+
* weighting happened a second time.
|
|
81
|
+
*/
|
|
82
|
+
const PREMULTIPLIED_OVER = {
|
|
83
|
+
color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
|
|
84
|
+
alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
|
|
85
|
+
};
|
|
86
|
+
/** Additive, premultiplied by the fragment's alpha as it writes. */
|
|
87
|
+
const ADD_PREMULTIPLIED = {
|
|
88
|
+
color: { srcFactor: "src-alpha", dstFactor: "one", operation: "add" },
|
|
89
|
+
alpha: { srcFactor: "zero", dstFactor: "one", operation: "add" },
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Which classes actually WRITE an id, and so gain a fragment output for it.
|
|
93
|
+
*
|
|
94
|
+
* Everything else keeps its shader exactly as it is and takes the id target at
|
|
95
|
+
* writeMask 0 — legal, specified, and free. The alternative (leaving the target
|
|
96
|
+
* off those pipelines) is not available: every pipeline in a pass must agree
|
|
97
|
+
* with the pass's attachments.
|
|
98
|
+
*
|
|
99
|
+
* The ground is in because a mark placed by id needs the floor to have one.
|
|
100
|
+
* Transparent fabric writes ids too — dissolving a dress needs the dress's own
|
|
101
|
+
* pixels, and last write wins there, which is a documented choice rather than a
|
|
102
|
+
* consequence. Outline hulls, particles and ribbons are OUT: they are not
|
|
103
|
+
* things you would ever address by id, and a hull would overwrite the id of the
|
|
104
|
+
* body it traces.
|
|
105
|
+
*/
|
|
106
|
+
const WRITES_ID = new Set(["material", "ground"]);
|
|
107
|
+
/** The blends each class writes its two attachments with. */
|
|
108
|
+
const BLENDS = {
|
|
109
|
+
material: [ALPHA_OVER, ALPHA_OVER],
|
|
110
|
+
// PREMULTIPLIED colour, alone among the classes — see the blend's own note.
|
|
111
|
+
// The aux is ordinary alpha-over: the ground writes its mask unweighted, like
|
|
112
|
+
// everything else, and coverage is what the blend applies.
|
|
113
|
+
ground: [PREMULTIPLIED_OVER, ALPHA_OVER],
|
|
114
|
+
outline: [ALPHA_OVER, ALPHA_OVER],
|
|
115
|
+
particle: [ALPHA_OVER, ALPHA_OVER],
|
|
116
|
+
"particle-additive": [ADD_KEEP_ALPHA, ADD_BOTH],
|
|
117
|
+
trail: [ADD_PREMULTIPLIED, ALPHA_OVER],
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The colour targets a scene-pass pipeline of this class declares, in
|
|
121
|
+
* attachment order.
|
|
122
|
+
*
|
|
123
|
+
* Fresh objects every call, deliberately: a caller that mutated a shared
|
|
124
|
+
* descriptor would change every pipeline built after it, and the ones built
|
|
125
|
+
* before would keep the old value — a difference that only shows up as one
|
|
126
|
+
* pipeline blending unlike its neighbours.
|
|
127
|
+
*/
|
|
128
|
+
export function sceneTargets(cls, formats) {
|
|
129
|
+
const targets = cls === "depth-prepass"
|
|
130
|
+
? // Format only, and writeMask 0. Note the asymmetry this leans on, which
|
|
131
|
+
// is the same one the id target leans on below: a target the shader has
|
|
132
|
+
// no output for is legal at writeMask 0 (gpuweb#1918), while an output
|
|
133
|
+
// with no target is NOT governed (gpuweb#5341). This is the specified
|
|
134
|
+
// direction, and it is the only one this file ever uses.
|
|
135
|
+
[
|
|
136
|
+
{ format: formats.hdr, writeMask: 0 },
|
|
137
|
+
{ format: formats.aux, writeMask: 0 },
|
|
138
|
+
]
|
|
139
|
+
: (() => {
|
|
140
|
+
const [color, aux] = BLENDS[cls];
|
|
141
|
+
return [
|
|
142
|
+
{ format: formats.hdr, blend: { color: { ...color.color }, alpha: { ...color.alpha } } },
|
|
143
|
+
{ format: formats.aux, blend: { color: { ...aux.color }, alpha: { ...aux.alpha } } },
|
|
144
|
+
];
|
|
145
|
+
})();
|
|
146
|
+
if (mrtIds)
|
|
147
|
+
targets.push({ format: SCENE_ID_FORMAT, writeMask: WRITES_ID.has(cls) ? 0xf : 0 });
|
|
148
|
+
return targets;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The pass's colour attachments, in order — for the things that describe the
|
|
152
|
+
* pass itself rather than a draw within it.
|
|
153
|
+
*
|
|
154
|
+
* A render bundle declares the formats it will be replayed into and is rejected
|
|
155
|
+
* against a pass that does not match, so the bundle encoder has to move in
|
|
156
|
+
* lockstep with the targets above. It restated the list independently until
|
|
157
|
+
* this existed, which made it the one consumer an MRT change would have missed
|
|
158
|
+
* — a bundle is recorded once and replayed, so the failure would have arrived
|
|
159
|
+
* at replay, naming the bundle rather than the attachment that changed.
|
|
160
|
+
*/
|
|
161
|
+
export function sceneColorFormats(formats) {
|
|
162
|
+
return mrtIds ? [formats.hdr, formats.aux, SCENE_ID_FORMAT] : [formats.hdr, formats.aux];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The fragment-output struct a scene-pass shader returns.
|
|
166
|
+
*
|
|
167
|
+
* Emitted rather than written out per file so that the attachment list has one
|
|
168
|
+
* author. The struct and the targets above have to agree on count and order,
|
|
169
|
+
* and they now disagree in one file rather than in five.
|
|
170
|
+
*
|
|
171
|
+
* Only the shaders that will GAIN an output take this today — the materials
|
|
172
|
+
* (hand-written and graph-generated alike, through COMMON_FS_OUT_WGSL) and the
|
|
173
|
+
* ground. Outline, particles and ribbons keep their own declarations on
|
|
174
|
+
* purpose: they never write the id, so they would take an `id: false` argument
|
|
175
|
+
* forever, and their structs carry comments about their own blend that belong
|
|
176
|
+
* where they are.
|
|
177
|
+
*/
|
|
178
|
+
export function sceneFsOutWgsl(opts) {
|
|
179
|
+
const name = opts?.name ?? "FSOut";
|
|
180
|
+
const aux = opts?.aux ?? "mask";
|
|
181
|
+
// No @interpolate here, deliberately. The plan called for
|
|
182
|
+
// `@location(2) @interpolate(flat)`, and that attribute is only legal on a
|
|
183
|
+
// vertex OUTPUT or a fragment INPUT — a fragment output is neither, so it
|
|
184
|
+
// would not compile. It is also unnecessary: the id is read from the per-draw
|
|
185
|
+
// uniform, not carried across the triangle as a varying, so there is no
|
|
186
|
+
// interpolation to suppress. (A varying carrying it WOULD need flat, since an
|
|
187
|
+
// integer varying must be.)
|
|
188
|
+
//
|
|
189
|
+
// vec2u for rg16uint: the output type must be compatible with the format, and
|
|
190
|
+
// uint targets take no blend, which is what makes last-write-wins the rule.
|
|
191
|
+
const id = mrtIds ? ` @location(2) id: vec2u,\n` : "";
|
|
192
|
+
return `struct ${name} {
|
|
193
|
+
@location(0) color: vec4f,
|
|
194
|
+
@location(1) ${aux}: vec4f,
|
|
195
|
+
${id}};
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* The line a fragment shader assigns its id with, or nothing when ids are off.
|
|
200
|
+
*
|
|
201
|
+
* Emitted rather than written into each shader for the same reason as the
|
|
202
|
+
* struct: with ids off there must be no assignment either, and a shader cannot
|
|
203
|
+
* ask the device what it supports.
|
|
204
|
+
*/
|
|
205
|
+
export function sceneIdWriteWgsl(out, material, object) {
|
|
206
|
+
return mrtIds ? ` ${out}.id = vec2u(${material}, ${object});\n` : "";
|
|
207
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type CastLayout } from "./particles";
|
|
2
|
+
export declare const SIM_MAX = 1024;
|
|
3
|
+
/** rgba16float, ping-ponged. Two of these is the whole memory cost. */
|
|
4
|
+
export declare const SIM_FORMAT: GPUTextureFormat;
|
|
5
|
+
/** `// @sim 256` — the grid's resolution, in texels per side. */
|
|
6
|
+
export declare function parseSimSize(wgsl: string, max: number): number;
|
|
7
|
+
/** Whether this effect drives a grid at all. */
|
|
8
|
+
export declare function simEntryPoint(wgsl: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* `rzSim(uv)`, for a shader that READS the grid rather than steps it.
|
|
11
|
+
*
|
|
12
|
+
* Always compiled in, even for an effect with no grid — it then samples a 1×1
|
|
13
|
+
* of zeroes. An accessor that exists only sometimes is one an author has to
|
|
14
|
+
* guard, and a missing function is a compile error rather than a blank result.
|
|
15
|
+
*/
|
|
16
|
+
export declare function simReadApi(group: number, tex: number, samp: number, size: number): string;
|
|
17
|
+
/** `rzTime`/`rzDt`, for a module that has no clock of its own — the field pass.
|
|
18
|
+
* Same reason as above: a spliced simStep refers to them. */
|
|
19
|
+
export declare function simClockApi(timeExpr: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* The step shader: one invocation per texel, once per frame.
|
|
22
|
+
*
|
|
23
|
+
* The previous grid is bound as a SAMPLED texture and the next as a storage
|
|
24
|
+
* texture, which is what makes the two different resources and the whole thing
|
|
25
|
+
* legal — a shader cannot read and write one texture coherently, and the
|
|
26
|
+
* ping-pong is not an optimisation but the only correct way to do this.
|
|
27
|
+
*
|
|
28
|
+
* Sampled rather than only fetched, because advection needs to read BETWEEN
|
|
29
|
+
* texels: a semi-Lagrangian step asks "what was at the place this parcel came
|
|
30
|
+
* from", and that place is almost never a texel centre. Point-sampling it is
|
|
31
|
+
* what turns smoke into a staircase.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildSimShader(wgsl: string, size: number, _cast: CastLayout): string;
|
|
34
|
+
//# sourceMappingURL=sim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sim.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/sim.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAgC7C,eAAO,MAAM,OAAO,OAAO,CAAA;AAC3B,uEAAuE;AACvE,eAAO,MAAM,UAAU,EAAE,gBAAgC,CAAA;AAEzD,iEAAiE;AACjE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED,gDAAgD;AAChD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA6BzF;AAED;8DAC8D;AAC9D,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKpD;AAWD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAwDpF"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { audioApi } from "../audio-api";
|
|
2
|
+
import { scoreApi } from "../score-api";
|
|
3
|
+
import { EFFECT_SCENE_API } from "./composite";
|
|
4
|
+
// A persistent grid an effect can step and read: the one thing an effect could
|
|
5
|
+
// not have before, which is MEMORY.
|
|
6
|
+
//
|
|
7
|
+
// Every other mount is a pure function of position and time. A field shader
|
|
8
|
+
// recomputes its noise from scratch every frame; a trail reads history the
|
|
9
|
+
// engine happens to keep. Neither can build on what it produced last frame, and
|
|
10
|
+
// a whole class of effect is nothing but that:
|
|
11
|
+
//
|
|
12
|
+
// Fog that a dancer walks through does not "look displaced near her" — it IS
|
|
13
|
+
// displaced, and stays displaced, and creeps back over seconds. Water does not
|
|
14
|
+
// look rippled where a foot landed; a ripple leaves and propagates and
|
|
15
|
+
// reflects off the far edge long after the foot is gone. Both are the same
|
|
16
|
+
// shape of thing — a grid, stepped by its own previous value.
|
|
17
|
+
//
|
|
18
|
+
// It is also the only way to get the LOOK of a fluid, not merely its motion.
|
|
19
|
+
// Advection compounds: each frame's field is the last one pushed sideways, and a
|
|
20
|
+
// few hundred frames of that stretches and folds a smooth field into filaments
|
|
21
|
+
// and vortex sheets finer than the grid storing them. No noise function
|
|
22
|
+
// produces that, at any octave count, because the structure is not a function of
|
|
23
|
+
// position at all — it is a function of history.
|
|
24
|
+
//
|
|
25
|
+
// The engine owns the ping-pong, the dispatch and the sampling. The author owns
|
|
26
|
+
// the KERNEL, which is the part that differs: advection for smoke, the wave
|
|
27
|
+
// equation for water, a decaying stamp for footprints. That split is the same
|
|
28
|
+
// one the particle mounts make, and for the same reason — the loop is identical
|
|
29
|
+
// in every such effect and the physics never is.
|
|
30
|
+
// A megatexel of rgba16float is 8MB, and two of those is the whole cost. What
|
|
31
|
+
// actually bounds this is the STEP: one invocation per texel per frame, each
|
|
32
|
+
// taking a dozen samples, so doubling the side quadruples the work.
|
|
33
|
+
export const SIM_MAX = 1024;
|
|
34
|
+
/** rgba16float, ping-ponged. Two of these is the whole memory cost. */
|
|
35
|
+
export const SIM_FORMAT = "rgba16float";
|
|
36
|
+
/** `// @sim 256` — the grid's resolution, in texels per side. */
|
|
37
|
+
export function parseSimSize(wgsl, max) {
|
|
38
|
+
const m = /^\s*\/\/\s*@sim\s+(\d+)\s*$/m.exec(wgsl);
|
|
39
|
+
if (!m)
|
|
40
|
+
return 0;
|
|
41
|
+
// Clamped rather than rejected, as the particle count is: an author asking for
|
|
42
|
+
// 4096 gets the most the engine will give and a scene that still runs.
|
|
43
|
+
return Math.max(8, Math.min(max, parseInt(m[1], 10)));
|
|
44
|
+
}
|
|
45
|
+
/** Whether this effect drives a grid at all. */
|
|
46
|
+
export function simEntryPoint(wgsl) {
|
|
47
|
+
return /\bfn\s+simStep\s*\(/.test(wgsl);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `rzSim(uv)`, for a shader that READS the grid rather than steps it.
|
|
51
|
+
*
|
|
52
|
+
* Always compiled in, even for an effect with no grid — it then samples a 1×1
|
|
53
|
+
* of zeroes. An accessor that exists only sometimes is one an author has to
|
|
54
|
+
* guard, and a missing function is a compile error rather than a blank result.
|
|
55
|
+
*/
|
|
56
|
+
export function simReadApi(group, tex, samp, size) {
|
|
57
|
+
return /* wgsl */ `
|
|
58
|
+
const RZ_SIM_SIZE: f32 = ${size > 0 ? size : 1}.0;
|
|
59
|
+
@group(${group}) @binding(${tex}) var _rzSimTex: texture_2d<f32>;
|
|
60
|
+
@group(${group}) @binding(${samp}) var _rzSimSamp: sampler;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The grid, bilinearly sampled. uv is 0..1 across it, and what that MEANS in
|
|
64
|
+
* the world is the effect's own business — the engine deliberately does not
|
|
65
|
+
* impose a mapping, because a fog laid over a stage and a ripple field around a
|
|
66
|
+
* character want different ones and both are two lines of arithmetic.
|
|
67
|
+
*/
|
|
68
|
+
fn rzSim(uv: vec2f) -> vec4f {
|
|
69
|
+
return textureSampleLevel(_rzSimTex, _rzSimSamp, clamp(uv, vec2f(0.0), vec2f(1.0)), 0.0);
|
|
70
|
+
}
|
|
71
|
+
fn rzSimSize() -> f32 { return RZ_SIM_SIZE; }
|
|
72
|
+
fn rzSimTexel() -> f32 { return 1.0 / RZ_SIM_SIZE; }
|
|
73
|
+
|
|
74
|
+
// ── The step-only half, defined here too and never called here ──
|
|
75
|
+
//
|
|
76
|
+
// One effect file is spliced into EVERY module it has a mount in, so a file
|
|
77
|
+
// with a grid compiles its simStep inside the field and particle shaders as
|
|
78
|
+
// well — where it is dead code that nothing calls, but still code that has to
|
|
79
|
+
// resolve. Leaving these out is a compile error on a function the author was
|
|
80
|
+
// right to write. rzSimPrev reads the current grid rather than a previous one,
|
|
81
|
+
// which is the honest answer outside the step: there is no previous frame here.
|
|
82
|
+
fn rzSimPrev(uv: vec2f) -> vec4f { return rzSim(uv); }
|
|
83
|
+
fn rzSimFrame() -> i32 { return 1; }
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
/** `rzTime`/`rzDt`, for a module that has no clock of its own — the field pass.
|
|
87
|
+
* Same reason as above: a spliced simStep refers to them. */
|
|
88
|
+
export function simClockApi(timeExpr) {
|
|
89
|
+
return /* wgsl */ `
|
|
90
|
+
fn rzTime() -> f32 { return ${timeExpr}; }
|
|
91
|
+
fn rzDt() -> f32 { return 0.0; }
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
const SIM_UNIFORMS = /* wgsl */ `
|
|
95
|
+
struct SimU {
|
|
96
|
+
time: f32,
|
|
97
|
+
dt: f32,
|
|
98
|
+
size: f32,
|
|
99
|
+
frame: f32,
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
/**
|
|
103
|
+
* The step shader: one invocation per texel, once per frame.
|
|
104
|
+
*
|
|
105
|
+
* The previous grid is bound as a SAMPLED texture and the next as a storage
|
|
106
|
+
* texture, which is what makes the two different resources and the whole thing
|
|
107
|
+
* legal — a shader cannot read and write one texture coherently, and the
|
|
108
|
+
* ping-pong is not an optimisation but the only correct way to do this.
|
|
109
|
+
*
|
|
110
|
+
* Sampled rather than only fetched, because advection needs to read BETWEEN
|
|
111
|
+
* texels: a semi-Lagrangian step asks "what was at the place this parcel came
|
|
112
|
+
* from", and that place is almost never a texel centre. Point-sampling it is
|
|
113
|
+
* what turns smoke into a staircase.
|
|
114
|
+
*/
|
|
115
|
+
export function buildSimShader(wgsl, size, _cast) {
|
|
116
|
+
return (SIM_UNIFORMS +
|
|
117
|
+
/* wgsl */ `
|
|
118
|
+
@group(0) @binding(0) var<uniform> su: SimU;
|
|
119
|
+
@group(0) @binding(1) var _rzSimPrevTex: texture_2d<f32>;
|
|
120
|
+
@group(0) @binding(2) var _rzSimSamp: sampler;
|
|
121
|
+
@group(0) @binding(3) var _rzSimOut: texture_storage_2d<${SIM_FORMAT}, write>;
|
|
122
|
+
@group(0) @binding(4) var<storage, read> _rzCast: array<vec4f>;
|
|
123
|
+
// The same view uniform the composite reads, so the scene API below is the real
|
|
124
|
+
// thing here rather than a stub — a kernel can ask where a bone is, and a
|
|
125
|
+
// foreground spliced into this module resolves its camera calls.
|
|
126
|
+
@group(0) @binding(6) var<uniform> viewU: array<vec4<f32>, 15>;
|
|
127
|
+
|
|
128
|
+
fn rzTime() -> f32 { return su.time; }
|
|
129
|
+
fn rzDt() -> f32 { return su.dt; }
|
|
130
|
+
/** Texels per side. */
|
|
131
|
+
fn rzSimSize() -> f32 { return su.size; }
|
|
132
|
+
/** One texel, in uv — the step a neighbour lookup takes. */
|
|
133
|
+
fn rzSimTexel() -> f32 { return 1.0 / su.size; }
|
|
134
|
+
/**
|
|
135
|
+
* Steps since this effect was installed. Zero on the very first one, which is
|
|
136
|
+
* the only chance to seed a grid: everything after it builds on what is there.
|
|
137
|
+
*/
|
|
138
|
+
fn rzSimFrame() -> i32 { return i32(su.frame); }
|
|
139
|
+
|
|
140
|
+
/** The grid as it was LAST frame, bilinear. The only input a kernel really has. */
|
|
141
|
+
fn rzSimPrev(uv: vec2f) -> vec4f {
|
|
142
|
+
return textureSampleLevel(_rzSimPrevTex, _rzSimSamp, clamp(uv, vec2f(0.0), vec2f(1.0)), 0.0);
|
|
143
|
+
}
|
|
144
|
+
` +
|
|
145
|
+
EFFECT_SCENE_API +
|
|
146
|
+
audioApi(0, 5) +
|
|
147
|
+
scoreApi(0, 7) +
|
|
148
|
+
// rzSim itself, so a kernel may read the grid it is writing — through the
|
|
149
|
+
// PREVIOUS frame's texture, which is the only version of it that exists
|
|
150
|
+
// while the current one is still being written.
|
|
151
|
+
/* wgsl */ `
|
|
152
|
+
fn rzSim(uv: vec2f) -> vec4f { return rzSimPrev(uv); }
|
|
153
|
+
` +
|
|
154
|
+
"\n// ── user effect (setEffect) ──\n" +
|
|
155
|
+
wgsl +
|
|
156
|
+
/* wgsl */ `
|
|
157
|
+
|
|
158
|
+
@compute @workgroup_size(8, 8)
|
|
159
|
+
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
160
|
+
let n = u32(${size});
|
|
161
|
+
if (gid.x >= n || gid.y >= n) { return; }
|
|
162
|
+
let xy = vec2i(i32(gid.x), i32(gid.y));
|
|
163
|
+
// The texel CENTRE, so a kernel that reads its own uv back through rzSimPrev
|
|
164
|
+
// lands on itself rather than a quarter-texel off.
|
|
165
|
+
let uv = (vec2f(f32(gid.x), f32(gid.y)) + vec2f(0.5)) / su.size;
|
|
166
|
+
textureStore(_rzSimOut, xy, simStep(uv, textureLoad(_rzSimPrevTex, xy, 0), su.dt));
|
|
167
|
+
}
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
type TrailSource = {
|
|
2
|
+
/** The author's WGSL verbatim. */
|
|
3
|
+
wgsl: string;
|
|
4
|
+
/** How many ribbons to draw — one per trailed anchor. */
|
|
5
|
+
slots: number;
|
|
6
|
+
/** For each ribbon, the LOCAL anchor slot it belongs to. Identity when every
|
|
7
|
+
* anchor is trailed; otherwise it skips the untrailed ones. */
|
|
8
|
+
ribbonSlots: number[];
|
|
9
|
+
/** Additive, like most glowing ribbons, or straight alpha. */
|
|
10
|
+
blend: "alpha" | "additive";
|
|
11
|
+
bloom: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Sub-segments drawn between each pair of recorded samples.
|
|
15
|
+
*
|
|
16
|
+
* The path is sampled at a fixed rate on the scene clock, so a fast hand leaves
|
|
17
|
+
* its samples far apart and a strip drawn straight between them is visibly
|
|
18
|
+
* faceted. Four sub-segments on a Catmull-Rom curve through the neighbours costs
|
|
19
|
+
* four times the vertices — which is nothing, they are vertices — and removes
|
|
20
|
+
* both the faceting and most of the jitter, since a spline tangent varies
|
|
21
|
+
* smoothly where a per-segment direction snaps about whenever the hand slows.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TRAIL_SUBDIVISIONS = 6;
|
|
24
|
+
/** Does the source define the trail contract? Both are required together. */
|
|
25
|
+
export declare function trailEntryPoints(wgsl: string): {
|
|
26
|
+
width: boolean;
|
|
27
|
+
shade: boolean;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* One quad per segment, laid out flat across every anchor and character.
|
|
31
|
+
*
|
|
32
|
+
* The instance index encodes all three — segment, subject, slot — so a scene
|
|
33
|
+
* with three dancers and eight declared bones is still ONE draw call and needs
|
|
34
|
+
* nothing computed on the CPU per frame. Instances past the end of a real trail
|
|
35
|
+
* collapse to a degenerate quad, which costs a vertex shader invocation and no
|
|
36
|
+
* fragments; the alternative is a compacted instance list, which costs a
|
|
37
|
+
* readback every frame to save exactly that.
|
|
38
|
+
*
|
|
39
|
+
* The ribbon faces the camera per SEGMENT rather than as a whole: the side
|
|
40
|
+
* vector is the segment direction crossed with the direction to the eye, so a
|
|
41
|
+
* ribbon that loops back on itself stays visible along its entire length instead
|
|
42
|
+
* of vanishing edge-on where it turns.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildTrailShader(src: TrailSource, cast: {
|
|
45
|
+
subjects: number;
|
|
46
|
+
samples: number;
|
|
47
|
+
base: number;
|
|
48
|
+
trailBase: number;
|
|
49
|
+
slots: number;
|
|
50
|
+
alias: number[];
|
|
51
|
+
/** Depth convention of the scene buffer this layer tests against. The
|
|
52
|
+
* occlusion compare below is MANUAL (the layer has no depth attachment),
|
|
53
|
+
* so it does not flip with the pipelines' depthCompare — it has to be
|
|
54
|
+
* emitted the right way round at build time. On a reversed-Z device,
|
|
55
|
+
* larger z is CLOSER; the unflipped test drew ribbons only when occluded. */
|
|
56
|
+
reversedZ: boolean;
|
|
57
|
+
}): string;
|
|
58
|
+
export {};
|
|
59
|
+
//# sourceMappingURL=trails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trails.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/trails.ts"],"names":[],"mappings":"AAwBA,KAAK,WAAW,GAAG;IACjB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb;oEACgE;IAChE,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAKjF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;kFAI8E;IAC9E,SAAS,EAAE,OAAO,CAAA;CACnB,GACA,MAAM,CA6SR"}
|