reze-engine 0.43.0 → 0.50.1
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 +53 -429
- package/dist/animation.d.ts +26 -0
- package/dist/animation.d.ts.map +1 -1
- package/dist/animation.js +42 -0
- 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 +841 -53
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +3498 -451
- 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 +19 -14
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +31 -2
- 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/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 +28 -22
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +165 -138
- 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 +10 -2
- package/dist/shaders/passes/particles.d.ts.map +1 -1
- package/dist/shaders/passes/particles.js +37 -109
- 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 +14 -2
- package/dist/shaders/passes/trails.d.ts.map +1 -1
- package/dist/shaders/passes/trails.js +55 -90
- 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/animation.ts +41 -0
- package/src/camera.ts +31 -8
- package/src/engine.ts +4015 -556
- 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 +34 -4
- 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/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 +182 -139
- 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 +54 -112
- package/src/shaders/passes/scene-contract.ts +266 -0
- package/src/shaders/passes/trails.ts +77 -93
- package/src/shadow-cascades.ts +97 -0
- package/src/vmd-loader.ts +2 -2
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// The half of the effect API whose VALUES are module-local.
|
|
2
|
+
//
|
|
3
|
+
// An effect file is spliced WHOLE into every module it has a mount in. A file
|
|
4
|
+
// with a trail and a lightEmit compiles its trail code inside the light module,
|
|
5
|
+
// where it never runs but still has to resolve — so `rzTime()` has to exist
|
|
6
|
+
// there even though a compute pass that writes lamp positions has no obvious
|
|
7
|
+
// clock, and `rzFalloff()` has to exist there even though nothing calls it.
|
|
8
|
+
//
|
|
9
|
+
// EFFECT_SCENE_API (composite.ts) is the other half: names that mean the same
|
|
10
|
+
// thing everywhere, written once. These are the ones that cannot be, because
|
|
11
|
+
// each module keeps its clock and its viewport in a different uniform. So they
|
|
12
|
+
// are written once HERE and the expression is the parameter — which is the
|
|
13
|
+
// same trick the grid pass already used for one of them, generalised to all.
|
|
14
|
+
//
|
|
15
|
+
// The rule this file exists to make keepable: a hosting module includes every
|
|
16
|
+
// block below. Not the ones its own mount happens to need — every one, because
|
|
17
|
+
// what lands in it is the author's whole file.
|
|
18
|
+
//
|
|
19
|
+
// EXCEPT that EFFECT_SCENE_API already carries EFFECT_MATH_API and the Particle
|
|
20
|
+
// struct, so the three modules built on it take those two by inheritance and
|
|
21
|
+
// must not add them again. tests/hosted-api.test.mjs fails on a name declared
|
|
22
|
+
// twice for exactly this reason — it is the mistake sharing blocks invites.
|
|
23
|
+
/**
|
|
24
|
+
* The particle record, laid out by hand — declared in EVERY hosting module.
|
|
25
|
+
*
|
|
26
|
+
* Not only in the particle ones: `fn particleStep(p: Particle)` is part of the
|
|
27
|
+
* author's file, so the trail, field and lightEmit modules compile that
|
|
28
|
+
* signature as dead code and a missing struct is a compile error on a function
|
|
29
|
+
* the author was right to write. The trail module used to carry a hand-copied
|
|
30
|
+
* duplicate for exactly this reason.
|
|
31
|
+
*
|
|
32
|
+
* `age` and `life` sit in the padding that vec3f alignment would waste anyway
|
|
33
|
+
* (a vec3f occupies 12 bytes but aligns the next field to 16), so the struct is
|
|
34
|
+
* 48 bytes rather than the 64 a naive ordering costs. At 4096 particles that is
|
|
35
|
+
* 192KB instead of 256KB, and it is read every frame by both stages.
|
|
36
|
+
*
|
|
37
|
+
* `life <= 0` means "not alive" and is what the pool checks to recycle a slot,
|
|
38
|
+
* so a freshly zeroed buffer is entirely dead and every particle is born on the
|
|
39
|
+
* first step rather than needing a separate seeding pass.
|
|
40
|
+
*
|
|
41
|
+
* RzLight is the same kind of thing and lives in lights.ts, because it is that
|
|
42
|
+
* module's own type; every hosting module splices it for this same reason.
|
|
43
|
+
*/
|
|
44
|
+
export const PARTICLE_STRUCT_WGSL = /* wgsl */ `
|
|
45
|
+
struct Particle {
|
|
46
|
+
pos: vec3f,
|
|
47
|
+
age: f32,
|
|
48
|
+
vel: vec3f,
|
|
49
|
+
life: f32,
|
|
50
|
+
size: f32,
|
|
51
|
+
rot: f32,
|
|
52
|
+
seed: f32,
|
|
53
|
+
// Aspect along the direction of travel. 1 or less is a square billboard; a
|
|
54
|
+
// raindrop is 10 or 20. Zero-initialised, so an effect that never sets it gets
|
|
55
|
+
// the square it expects.
|
|
56
|
+
stretch: f32,
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
/**
|
|
60
|
+
* `rzTime`/`rzDt`, from whatever the module calls its clock.
|
|
61
|
+
*
|
|
62
|
+
* A module with no clock of its own passes a literal. Zero dt is honest there:
|
|
63
|
+
* nothing spliced into it is being stepped, so a kernel that integrates would
|
|
64
|
+
* integrate by nothing rather than by a plausible-looking wrong number.
|
|
65
|
+
*/
|
|
66
|
+
export function clockApi(timeExpr, dtExpr) {
|
|
67
|
+
return /* wgsl */ `
|
|
68
|
+
fn rzTime() -> f32 { return ${timeExpr}; }
|
|
69
|
+
fn rzDt() -> f32 { return ${dtExpr}; }
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
/** `rzViewportHeight` — the render target's height in pixels, which is what a
|
|
73
|
+
* point size in world units has to be divided by to become a pixel radius. */
|
|
74
|
+
export function viewportApi(heightExpr) {
|
|
75
|
+
return /* wgsl */ `
|
|
76
|
+
fn rzViewportHeight() -> f32 { return ${heightExpr}; }
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The one cast number that is PER EFFECT rather than per engine.
|
|
81
|
+
*
|
|
82
|
+
* Author-visible, and published effects loop over it, so its meaning is pinned:
|
|
83
|
+
* how many of THIS effect's anchors asked for a trail. Deliberately not the
|
|
84
|
+
* anchor address space — that is RZ_MAX_ANCHORS in CAST_API, and the two being
|
|
85
|
+
* one number was the old trail bug. Everything else about the cast's shape is a
|
|
86
|
+
* constant and lives there.
|
|
87
|
+
*/
|
|
88
|
+
export function trailSlotsApi(trailCount) {
|
|
89
|
+
return /* wgsl */ `
|
|
90
|
+
const RZ_TRAIL_SLOTS: i32 = ${trailCount};
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Pure math — no bindings, no uniforms, identical in every module.
|
|
95
|
+
*
|
|
96
|
+
* These were duplicated in the particle and trail modules, byte for byte in the
|
|
97
|
+
* case of rzFalloff and to a `+ vec3f(0.0)` in the case of rzValueNoise. Two
|
|
98
|
+
* copies of a noise function is one bad merge away from an effect that looks
|
|
99
|
+
* different depending on which mount drew it, which is not a bug anyone would
|
|
100
|
+
* think to look for.
|
|
101
|
+
*
|
|
102
|
+
* EVERY hosting module includes this and none defines any of these itself, so
|
|
103
|
+
* "which module am I in" can never change what a helper returns.
|
|
104
|
+
*/
|
|
105
|
+
export const EFFECT_MATH_API = /* wgsl */ `
|
|
106
|
+
fn rzHash11(x: f32) -> f32 {
|
|
107
|
+
var p = fract(x * 0.1031);
|
|
108
|
+
p = p * (p + 33.33);
|
|
109
|
+
return fract(p * (p + p));
|
|
110
|
+
}
|
|
111
|
+
fn rzHash21(p: vec2f) -> f32 {
|
|
112
|
+
var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);
|
|
113
|
+
p3 = p3 + dot(p3, p3.yzx + 33.33);
|
|
114
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
115
|
+
}
|
|
116
|
+
/** Three independent randoms from one seed — the usual need when spawning. */
|
|
117
|
+
fn rzHash13(x: f32) -> vec3f {
|
|
118
|
+
return vec3f(rzHash11(x), rzHash11(x + 17.13), rzHash11(x + 41.71));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Compact-support falloff: 1 at the centre, exactly 0 at r, smooth between.
|
|
122
|
+
*
|
|
123
|
+
* It reaches exactly zero rather than merely getting small, because a glow that
|
|
124
|
+
* never quite ends has to be culled somewhere, and culling it wherever it looks
|
|
125
|
+
* close enough is what put a visible hard edge on the first halo effect.
|
|
126
|
+
*/
|
|
127
|
+
fn rzFalloff(d: f32, r: f32) -> f32 {
|
|
128
|
+
let x = clamp(d / max(r, 1e-6), 0.0, 1.0);
|
|
129
|
+
let f = 1.0 - x;
|
|
130
|
+
return f * f * f;
|
|
131
|
+
}
|
|
132
|
+
fn rzHash31(p: vec3f) -> f32 {
|
|
133
|
+
var p3 = fract(p * 0.1031);
|
|
134
|
+
p3 = p3 + dot(p3, p3.zyx + 31.32);
|
|
135
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
136
|
+
}
|
|
137
|
+
fn rzValueNoise(p: vec3f) -> f32 {
|
|
138
|
+
let i = floor(p);
|
|
139
|
+
let f = fract(p);
|
|
140
|
+
let u = f * f * (3.0 - 2.0 * f);
|
|
141
|
+
let n000 = rzHash31(i);
|
|
142
|
+
let n100 = rzHash31(i + vec3f(1.0, 0.0, 0.0));
|
|
143
|
+
let n010 = rzHash31(i + vec3f(0.0, 1.0, 0.0));
|
|
144
|
+
let n110 = rzHash31(i + vec3f(1.0, 1.0, 0.0));
|
|
145
|
+
let n001 = rzHash31(i + vec3f(0.0, 0.0, 1.0));
|
|
146
|
+
let n101 = rzHash31(i + vec3f(1.0, 0.0, 1.0));
|
|
147
|
+
let n011 = rzHash31(i + vec3f(0.0, 1.0, 1.0));
|
|
148
|
+
let n111 = rzHash31(i + vec3f(1.0, 1.0, 1.0));
|
|
149
|
+
let x00 = mix(n000, n100, u.x);
|
|
150
|
+
let x10 = mix(n010, n110, u.x);
|
|
151
|
+
let x01 = mix(n001, n101, u.x);
|
|
152
|
+
let x11 = mix(n011, n111, u.x);
|
|
153
|
+
return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);
|
|
154
|
+
}
|
|
155
|
+
/** Divergence-free flow — the field a wisp of smoke follows without a solver. */
|
|
156
|
+
fn rzCurlNoise(p: vec3f) -> vec3f {
|
|
157
|
+
let e = 0.1;
|
|
158
|
+
let dx = vec3f(e, 0.0, 0.0);
|
|
159
|
+
let dy = vec3f(0.0, e, 0.0);
|
|
160
|
+
let dz = vec3f(0.0, 0.0, e);
|
|
161
|
+
let x0 = rzValueNoise(p - dx); let x1 = rzValueNoise(p + dx);
|
|
162
|
+
let y0 = rzValueNoise(p - dy); let y1 = rzValueNoise(p + dy);
|
|
163
|
+
let z0 = rzValueNoise(p - dz); let z1 = rzValueNoise(p + dz);
|
|
164
|
+
return normalize(vec3f((y1 - y0) - (z1 - z0), (z1 - z0) - (x1 - x0), (x1 - x0) - (y1 - y0)) + vec3f(1e-6));
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The id attachment, drawn so a person can look at it.
|
|
3
|
+
*
|
|
4
|
+
* The id buffer is correct or incorrect in ways nothing else in the frame can
|
|
5
|
+
* show: with no consumer, a perfect id buffer and a completely wrong one
|
|
6
|
+
* produce exactly the same picture. This pass exists so that "it works" is
|
|
7
|
+
* something seen rather than inferred.
|
|
8
|
+
*
|
|
9
|
+
* WHAT CORRECT LOOKS LIKE, and what each failure looks like instead:
|
|
10
|
+
*
|
|
11
|
+
* - Every material is ONE FLAT COLOUR, with hard edges. Ids are names, not
|
|
12
|
+
* quantities: a gradient anywhere, or fringing along an edge, means
|
|
13
|
+
* something is interpolating or resolving them — the two failures the
|
|
14
|
+
* no-resolve/no-blend rules exist to prevent.
|
|
15
|
+
* - Parts differ from each other. A whole model in one colour means the
|
|
16
|
+
* material index never made it into the uniform.
|
|
17
|
+
* - The FLOOR is a fixed light grey, the reserved id, and never shares a
|
|
18
|
+
* colour with a body part.
|
|
19
|
+
* - Where nothing was drawn is BLACK — id 0, the reserved nothing, which is
|
|
20
|
+
* also what the attachment clears to. Black *over geometry* means that draw
|
|
21
|
+
* wrote no id; colour where the sky should be means the clear is wrong.
|
|
22
|
+
*
|
|
23
|
+
* Colours come from a hash of the pair, so neighbouring materials land on
|
|
24
|
+
* unrelated hues rather than adjacent shades of one — the point is telling them
|
|
25
|
+
* apart, not ordering them.
|
|
26
|
+
*/
|
|
27
|
+
export declare const ID_DEBUG_SHADER_WGSL = "\n// Multisampled and NEVER resolved, so it is read the way it is written: one\n// sample, by texel. Sample 0 is what every consumer of this attachment reads.\n@group(0) @binding(0) var idTex: texture_multisampled_2d<u32>;\n\n@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n let x = f32((vi & 1u) << 2u) - 1.0;\n let y = f32((vi & 2u) << 1u) - 1.0;\n return vec4f(x, y, 0.0, 1.0);\n}\n\n/** Integer hash \u2192 0..1. Distinct inputs land on unrelated outputs, which is the\n * whole requirement: adjacent ids must not read as adjacent colours. */\nfn hashId(x: u32) -> f32 {\n var h = x * 747796405u + 2891336453u;\n h = ((h >> ((h >> 28u) + 4u)) ^ h) * 277803737u;\n return f32((h >> 22u) ^ h) / 4294967295.0;\n}\n\n@fragment fn fs(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {\n let ids = textureLoad(idTex, vec2<i32>(fragCoord.xy), 0);\n let materialId = ids.x;\n let objectId = ids.y;\n\n // Nothing drawn here. Black, and it must cover exactly the empty space.\n if (materialId == 0u && objectId == 0u) {\n return vec4f(0.0, 0.0, 0.0, 1.0);\n }\n // The floor, at the top of the range. Flat light grey so it is unmistakable\n // and cannot be confused with a hashed colour.\n if (materialId == 65535u) {\n return vec4f(0.72, 0.72, 0.75, 1.0);\n }\n // Hue from the material, brightness from the object, so two models wearing\n // the same material index still read apart.\n let h = hashId(materialId * 1973u + 9277u);\n let shade = 0.55 + 0.45 * hashId(objectId * 6151u + 1u);\n // Cheap hue ramp: three offset cosines. Saturated on purpose \u2014 this is a\n // diagnostic, not a look.\n let rgb = vec3f(\n 0.5 + 0.5 * cos(6.28318 * (h + 0.00)),\n 0.5 + 0.5 * cos(6.28318 * (h + 0.33)),\n 0.5 + 0.5 * cos(6.28318 * (h + 0.67)),\n );\n return vec4f(rgb * shade, 1.0);\n}\n";
|
|
28
|
+
//# sourceMappingURL=id-debug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-debug.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/id-debug.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,oBAAoB,20DA8ChC,CAAA"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { GROUND_MATERIAL_ID } from "./ground";
|
|
2
|
+
/**
|
|
3
|
+
* The id attachment, drawn so a person can look at it.
|
|
4
|
+
*
|
|
5
|
+
* The id buffer is correct or incorrect in ways nothing else in the frame can
|
|
6
|
+
* show: with no consumer, a perfect id buffer and a completely wrong one
|
|
7
|
+
* produce exactly the same picture. This pass exists so that "it works" is
|
|
8
|
+
* something seen rather than inferred.
|
|
9
|
+
*
|
|
10
|
+
* WHAT CORRECT LOOKS LIKE, and what each failure looks like instead:
|
|
11
|
+
*
|
|
12
|
+
* - Every material is ONE FLAT COLOUR, with hard edges. Ids are names, not
|
|
13
|
+
* quantities: a gradient anywhere, or fringing along an edge, means
|
|
14
|
+
* something is interpolating or resolving them — the two failures the
|
|
15
|
+
* no-resolve/no-blend rules exist to prevent.
|
|
16
|
+
* - Parts differ from each other. A whole model in one colour means the
|
|
17
|
+
* material index never made it into the uniform.
|
|
18
|
+
* - The FLOOR is a fixed light grey, the reserved id, and never shares a
|
|
19
|
+
* colour with a body part.
|
|
20
|
+
* - Where nothing was drawn is BLACK — id 0, the reserved nothing, which is
|
|
21
|
+
* also what the attachment clears to. Black *over geometry* means that draw
|
|
22
|
+
* wrote no id; colour where the sky should be means the clear is wrong.
|
|
23
|
+
*
|
|
24
|
+
* Colours come from a hash of the pair, so neighbouring materials land on
|
|
25
|
+
* unrelated hues rather than adjacent shades of one — the point is telling them
|
|
26
|
+
* apart, not ordering them.
|
|
27
|
+
*/
|
|
28
|
+
export const ID_DEBUG_SHADER_WGSL = /* wgsl */ `
|
|
29
|
+
// Multisampled and NEVER resolved, so it is read the way it is written: one
|
|
30
|
+
// sample, by texel. Sample 0 is what every consumer of this attachment reads.
|
|
31
|
+
@group(0) @binding(0) var idTex: texture_multisampled_2d<u32>;
|
|
32
|
+
|
|
33
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
34
|
+
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
35
|
+
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
36
|
+
return vec4f(x, y, 0.0, 1.0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Integer hash → 0..1. Distinct inputs land on unrelated outputs, which is the
|
|
40
|
+
* whole requirement: adjacent ids must not read as adjacent colours. */
|
|
41
|
+
fn hashId(x: u32) -> f32 {
|
|
42
|
+
var h = x * 747796405u + 2891336453u;
|
|
43
|
+
h = ((h >> ((h >> 28u) + 4u)) ^ h) * 277803737u;
|
|
44
|
+
return f32((h >> 22u) ^ h) / 4294967295.0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@fragment fn fs(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {
|
|
48
|
+
let ids = textureLoad(idTex, vec2<i32>(fragCoord.xy), 0);
|
|
49
|
+
let materialId = ids.x;
|
|
50
|
+
let objectId = ids.y;
|
|
51
|
+
|
|
52
|
+
// Nothing drawn here. Black, and it must cover exactly the empty space.
|
|
53
|
+
if (materialId == 0u && objectId == 0u) {
|
|
54
|
+
return vec4f(0.0, 0.0, 0.0, 1.0);
|
|
55
|
+
}
|
|
56
|
+
// The floor, at the top of the range. Flat light grey so it is unmistakable
|
|
57
|
+
// and cannot be confused with a hashed colour.
|
|
58
|
+
if (materialId == ${GROUND_MATERIAL_ID}u) {
|
|
59
|
+
return vec4f(0.72, 0.72, 0.75, 1.0);
|
|
60
|
+
}
|
|
61
|
+
// Hue from the material, brightness from the object, so two models wearing
|
|
62
|
+
// the same material index still read apart.
|
|
63
|
+
let h = hashId(materialId * 1973u + 9277u);
|
|
64
|
+
let shade = 0.55 + 0.45 * hashId(objectId * 6151u + 1u);
|
|
65
|
+
// Cheap hue ramp: three offset cosines. Saturated on purpose — this is a
|
|
66
|
+
// diagnostic, not a look.
|
|
67
|
+
let rgb = vec3f(
|
|
68
|
+
0.5 + 0.5 * cos(6.28318 * (h + 0.00)),
|
|
69
|
+
0.5 + 0.5 * cos(6.28318 * (h + 0.33)),
|
|
70
|
+
0.5 + 0.5 * cos(6.28318 * (h + 0.67)),
|
|
71
|
+
);
|
|
72
|
+
return vec4f(rgb * shade, 1.0);
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
@@ -4,11 +4,18 @@ export type CastLayout = {
|
|
|
4
4
|
samples: number;
|
|
5
5
|
base: number;
|
|
6
6
|
trailBase: number;
|
|
7
|
+
/** The scene's anchor CAP — the address space, not how many asked for trails. */
|
|
7
8
|
slots: number;
|
|
9
|
+
/** How many of this effect's anchors asked for a trail. AUTHOR-VISIBLE as
|
|
10
|
+
* RZ_TRAIL_SLOTS: published effects loop over it, so its meaning is pinned
|
|
11
|
+
* even though the address space it used to share is now a separate number. */
|
|
12
|
+
trailCount: number;
|
|
13
|
+
/** This effect's local slot → scene slot. Identity while one effect exists. */
|
|
14
|
+
alias: number[];
|
|
8
15
|
};
|
|
9
16
|
/** How the author's quads combine with the scene. */
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
type ParticleBlend = "alpha" | "additive";
|
|
18
|
+
type ParticleSource = {
|
|
12
19
|
/** The author's WGSL verbatim. */
|
|
13
20
|
wgsl: string;
|
|
14
21
|
/** Simultaneous particles. Fixed at install; the pool recycles rather than grows. */
|
|
@@ -55,4 +62,5 @@ export declare function buildParticleComputeShader(src: ParticleSource, cast: Ca
|
|
|
55
62
|
* readback every frame to save vertices the rasteriser was going to reject anyway.
|
|
56
63
|
*/
|
|
57
64
|
export declare function buildParticleRenderShader(src: ParticleSource, cast: CastLayout): string;
|
|
65
|
+
export {};
|
|
58
66
|
//# sourceMappingURL=particles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"particles.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/particles.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"particles.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/particles.ts"],"names":[],"mappings":"AAwBA,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb;;mFAE+E;IAC/E,UAAU,EAAE,MAAM,CAAA;IAClB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAiCD,qDAAqD;AACrD,KAAK,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAEzC,KAAK,cAAc,GAAG;IACpB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,aAAa,CAAA;IACpB,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,oEAAoE;AACpE,eAAO,MAAM,eAAe,KAAK,CAAA;AAkDjC,oDAAoD;AACpD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOpE;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAE9D;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAMlG;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA0CxF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA6GvF"}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { RZ_LIGHT_STRUCT_WGSL } from "../lights";
|
|
1
2
|
import { audioApi } from "../audio-api";
|
|
3
|
+
import { lyricsApi } from "../lyrics-api";
|
|
4
|
+
import { anchorAliasWgsl } from "../anchor-table";
|
|
5
|
+
import { midiApi } from "../midi-api";
|
|
6
|
+
import { CAST_API } from "../cast-api";
|
|
7
|
+
import { clockApi, EFFECT_MATH_API, PARTICLE_STRUCT_WGSL, trailSlotsApi, viewportApi } from "./hosted-api";
|
|
2
8
|
/**
|
|
3
9
|
* The trail accessors, in the PARTICLE module.
|
|
4
10
|
*
|
|
@@ -7,11 +13,15 @@ import { audioApi } from "../audio-api";
|
|
|
7
13
|
* particleInit reading the same recorded history the trail draws from. One
|
|
8
14
|
* effect file, two mounts, one buffer.
|
|
9
15
|
*/
|
|
16
|
+
/** Shared with the grid pass, which reads the same buffer for the same reason:
|
|
17
|
+
* a kernel that displaces fog has to know where the dancer's feet are. */
|
|
10
18
|
function castApi(cast) {
|
|
11
|
-
return
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
return (
|
|
20
|
+
// rzSubjectCount FIRST, because CAST_API is written against it and this
|
|
21
|
+
// module has no view uniform to read the engine's count out of. Scanning
|
|
22
|
+
// for a subject whose bounding sphere has a radius is the same answer by a
|
|
23
|
+
// different route — the seam is named at the top of cast-api.ts.
|
|
24
|
+
`
|
|
15
25
|
fn rzSubjectCount() -> i32 {
|
|
16
26
|
var n = 0;
|
|
17
27
|
for (var i = 0; i < RZ_SUBJECTS; i++) {
|
|
@@ -19,47 +29,13 @@ fn rzSubjectCount() -> i32 {
|
|
|
19
29
|
}
|
|
20
30
|
return n;
|
|
21
31
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/** Sample i of a path: xyz where it was, w how many seconds ago. i = 0 is now. */
|
|
27
|
-
fn rzTrail(subject: i32, slot: i32, i: i32) -> vec4f {
|
|
28
|
-
let n = rzTrailCount(subject, slot);
|
|
29
|
-
if (i < 0 || i >= n) { return vec4f(0.0); }
|
|
30
|
-
return _rzCast[${cast.trailBase} + (slot * RZ_SUBJECTS + subject) * RZ_SAMPLES + i];
|
|
31
|
-
}
|
|
32
|
-
`;
|
|
32
|
+
` +
|
|
33
|
+
CAST_API +
|
|
34
|
+
trailSlotsApi(cast.trailCount) +
|
|
35
|
+
anchorAliasWgsl(cast.alias));
|
|
33
36
|
}
|
|
34
37
|
/** Bytes per particle. Explicitly padded — see the struct below. */
|
|
35
38
|
export const PARTICLE_STRIDE = 48;
|
|
36
|
-
/**
|
|
37
|
-
* The particle record, laid out by hand.
|
|
38
|
-
*
|
|
39
|
-
* `age` and `life` sit in the padding that vec3f alignment would waste anyway
|
|
40
|
-
* (a vec3f occupies 12 bytes but aligns the next field to 16), so the struct is
|
|
41
|
-
* 48 bytes rather than the 64 a naive ordering costs. At 4096 particles that is
|
|
42
|
-
* 192KB instead of 256KB, and it is read every frame by both stages.
|
|
43
|
-
*
|
|
44
|
-
* `life <= 0` means "not alive" and is what the pool checks to recycle a slot,
|
|
45
|
-
* so a freshly zeroed buffer is entirely dead and every particle is born on the
|
|
46
|
-
* first step rather than needing a separate seeding pass.
|
|
47
|
-
*/
|
|
48
|
-
const PARTICLE_STRUCT = /* wgsl */ `
|
|
49
|
-
struct Particle {
|
|
50
|
-
pos: vec3f,
|
|
51
|
-
age: f32,
|
|
52
|
-
vel: vec3f,
|
|
53
|
-
life: f32,
|
|
54
|
-
size: f32,
|
|
55
|
-
rot: f32,
|
|
56
|
-
seed: f32,
|
|
57
|
-
// Aspect along the direction of travel. 1 or less is a square billboard; a
|
|
58
|
-
// raindrop is 10 or 20. Zero-initialised, so an effect that never sets it gets
|
|
59
|
-
// the square it expects.
|
|
60
|
-
stretch: f32,
|
|
61
|
-
}
|
|
62
|
-
`;
|
|
63
39
|
const CAMERA_STRUCT = /* wgsl */ `
|
|
64
40
|
struct CameraU {
|
|
65
41
|
view: mat4x4f,
|
|
@@ -77,72 +53,21 @@ struct ParticleU {
|
|
|
77
53
|
}
|
|
78
54
|
`;
|
|
79
55
|
/**
|
|
80
|
-
*
|
|
56
|
+
* This module's half of the prelude: the camera, from its own uniform.
|
|
81
57
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* exactly zero at r), because an exponential glow that never quite reaches zero
|
|
86
|
-
* has to be culled somewhere, and culling it wherever it "looks close enough"
|
|
87
|
-
* is what put a visible hard edge on the first halo effect.
|
|
58
|
+
* The rest — hashes, noise, falloff, clock, viewport — is shared with every
|
|
59
|
+
* other module that hosts an effect's source, because an author's helper must
|
|
60
|
+
* mean the same thing in whichever one it lands in.
|
|
88
61
|
*/
|
|
89
|
-
const PRELUDE =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
99
|
-
}
|
|
100
|
-
fn rzHash31(p: vec3f) -> f32 {
|
|
101
|
-
var p3 = fract(p * 0.1031);
|
|
102
|
-
p3 = p3 + dot(p3, p3.zyx + 31.32);
|
|
103
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
104
|
-
}
|
|
105
|
-
/** Three independent randoms from one seed — the usual need when spawning. */
|
|
106
|
-
fn rzHash13(x: f32) -> vec3f {
|
|
107
|
-
return vec3f(rzHash11(x), rzHash11(x + 17.13), rzHash11(x + 41.71));
|
|
108
|
-
}
|
|
109
|
-
fn rzValueNoise(p: vec3f) -> f32 {
|
|
110
|
-
let i = floor(p);
|
|
111
|
-
let f = fract(p);
|
|
112
|
-
let u = f * f * (3.0 - 2.0 * f);
|
|
113
|
-
let n000 = rzHash31(i + vec3f(0.0, 0.0, 0.0));
|
|
114
|
-
let n100 = rzHash31(i + vec3f(1.0, 0.0, 0.0));
|
|
115
|
-
let n010 = rzHash31(i + vec3f(0.0, 1.0, 0.0));
|
|
116
|
-
let n110 = rzHash31(i + vec3f(1.0, 1.0, 0.0));
|
|
117
|
-
let n001 = rzHash31(i + vec3f(0.0, 0.0, 1.0));
|
|
118
|
-
let n101 = rzHash31(i + vec3f(1.0, 0.0, 1.0));
|
|
119
|
-
let n011 = rzHash31(i + vec3f(0.0, 1.0, 1.0));
|
|
120
|
-
let n111 = rzHash31(i + vec3f(1.0, 1.0, 1.0));
|
|
121
|
-
let x00 = mix(n000, n100, u.x);
|
|
122
|
-
let x10 = mix(n010, n110, u.x);
|
|
123
|
-
let x01 = mix(n001, n101, u.x);
|
|
124
|
-
let x11 = mix(n011, n111, u.x);
|
|
125
|
-
return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);
|
|
126
|
-
}
|
|
127
|
-
/** Divergence-free flow — the standard drifting-air force. Snow and mist want this. */
|
|
128
|
-
fn rzCurlNoise(p: vec3f) -> vec3f {
|
|
129
|
-
let e = 0.1;
|
|
130
|
-
let dx = vec3f(e, 0.0, 0.0);
|
|
131
|
-
let dy = vec3f(0.0, e, 0.0);
|
|
132
|
-
let dz = vec3f(0.0, 0.0, e);
|
|
133
|
-
let x0 = rzValueNoise(p - dx); let x1 = rzValueNoise(p + dx);
|
|
134
|
-
let y0 = rzValueNoise(p - dy); let y1 = rzValueNoise(p + dy);
|
|
135
|
-
let z0 = rzValueNoise(p - dz); let z1 = rzValueNoise(p + dz);
|
|
136
|
-
return normalize(vec3f((y1 - y0) - (z1 - z0), (z1 - z0) - (x1 - x0), (x1 - x0) - (y1 - y0)) + vec3f(1e-6));
|
|
137
|
-
}
|
|
138
|
-
/** Compact-support falloff: 1 at the centre, exactly 0 at r, smooth between. */
|
|
139
|
-
fn rzFalloff(d: f32, r: f32) -> f32 {
|
|
140
|
-
let x = clamp(d / max(r, 1e-6), 0.0, 1.0);
|
|
141
|
-
let f = 1.0 - x;
|
|
142
|
-
return f * f * f;
|
|
143
|
-
}
|
|
144
|
-
fn rzTime() -> f32 { return pu.time; }
|
|
145
|
-
fn rzViewportHeight() -> f32 { return cam.targetHeight; }
|
|
62
|
+
const PRELUDE =
|
|
63
|
+
// The hashes, the noise and rzFalloff, identical in every module that hosts an
|
|
64
|
+
// effect's source; then the clock and the viewport, which cannot be, because
|
|
65
|
+
// this module keeps both in its own uniforms.
|
|
66
|
+
EFFECT_MATH_API +
|
|
67
|
+
clockApi("pu.time", "pu.dt") +
|
|
68
|
+
viewportApi("cam.targetHeight") +
|
|
69
|
+
/* wgsl */ `
|
|
70
|
+
${RZ_LIGHT_STRUCT_WGSL}
|
|
146
71
|
fn rzCameraPos() -> vec3f { return cam.camPos; }
|
|
147
72
|
fn rzCameraRight() -> vec3f { return vec3f(cam.view[0][0], cam.view[1][0], cam.view[2][0]); }
|
|
148
73
|
fn rzCameraUp() -> vec3f { return vec3f(cam.view[0][1], cam.view[1][1], cam.view[2][1]); }
|
|
@@ -153,7 +78,6 @@ fn rzProject(p: vec3f) -> vec3f {
|
|
|
153
78
|
let w = max(clip.w, 1e-4);
|
|
154
79
|
return vec3f(clip.xy / w * 0.5 + 0.5, clip.w);
|
|
155
80
|
}
|
|
156
|
-
fn rzDt() -> f32 { return pu.dt; }
|
|
157
81
|
fn rzCamPos() -> vec3f { return cam.camPos; }
|
|
158
82
|
`;
|
|
159
83
|
/** `// @particles 4096` — how many live at once. */
|
|
@@ -192,7 +116,7 @@ export function particleEntryPoints(wgsl) {
|
|
|
192
116
|
* snow is exactly right, and for a burst is what the age offset is for.
|
|
193
117
|
*/
|
|
194
118
|
export function buildParticleComputeShader(src, cast) {
|
|
195
|
-
return (
|
|
119
|
+
return (PARTICLE_STRUCT_WGSL +
|
|
196
120
|
CAMERA_STRUCT +
|
|
197
121
|
PARTICLE_UNIFORMS +
|
|
198
122
|
`
|
|
@@ -203,6 +127,8 @@ export function buildParticleComputeShader(src, cast) {
|
|
|
203
127
|
` +
|
|
204
128
|
castApi(cast) +
|
|
205
129
|
audioApi(0, 4) +
|
|
130
|
+
midiApi(0, 5) +
|
|
131
|
+
lyricsApi(0, 6) +
|
|
206
132
|
PRELUDE +
|
|
207
133
|
"\n// ── user effect ──\n" +
|
|
208
134
|
src.wgsl +
|
|
@@ -246,7 +172,7 @@ fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
|
246
172
|
export function buildParticleRenderShader(src, cast) {
|
|
247
173
|
return (`override BLOOM: bool = ${src.bloom ? "true" : "false"};
|
|
248
174
|
override ADDITIVE: bool = ${src.blend === "additive" ? "true" : "false"};\n` +
|
|
249
|
-
|
|
175
|
+
PARTICLE_STRUCT_WGSL +
|
|
250
176
|
CAMERA_STRUCT +
|
|
251
177
|
PARTICLE_UNIFORMS +
|
|
252
178
|
`
|
|
@@ -257,6 +183,8 @@ override ADDITIVE: bool = ${src.blend === "additive" ? "true" : "false"};\n` +
|
|
|
257
183
|
` +
|
|
258
184
|
castApi(cast) +
|
|
259
185
|
audioApi(0, 4) +
|
|
186
|
+
midiApi(0, 5) +
|
|
187
|
+
lyricsApi(0, 6) +
|
|
260
188
|
PRELUDE +
|
|
261
189
|
"\n// ── user effect ──\n" +
|
|
262
190
|
src.wgsl +
|
|
@@ -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"}
|