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
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { sceneFsOutWgsl } from "../passes/scene-contract";
|
|
2
|
+
import { lightsApi } from "../lights";
|
|
3
|
+
import { SHADOW_CASCADES } from "../../shadow-cascades";
|
|
4
|
+
import { WORLD_AMBIENT_WGSL } from "../lights";
|
|
1
5
|
// Shared WGSL blocks concatenated by every material shader.
|
|
2
6
|
// Splits the boilerplate (uniform structs, bind group layout, skinning VS, PCF shadow)
|
|
3
7
|
// away from the per-material fragment code so each material file only contains what
|
|
@@ -35,6 +39,8 @@ struct Light {
|
|
|
35
39
|
struct LightUniforms {
|
|
36
40
|
ambientColor: vec4f,
|
|
37
41
|
lights: array<Light, 4>,
|
|
42
|
+
/** Irradiance SH of the HDRI world; [0].w is the on-flag. */
|
|
43
|
+
sh: array<vec4f, 9>,
|
|
38
44
|
};
|
|
39
45
|
|
|
40
46
|
// Per-material uniforms. Every material binds this layout even if it ignores fields;
|
|
@@ -51,8 +57,15 @@ struct MaterialUniforms {
|
|
|
51
57
|
// Skeleton index of the 頭 (head) bone, or -1. Lets the eye shader gate
|
|
52
58
|
// the post-alpha-eye stencil by camera-vs-face hemisphere.
|
|
53
59
|
headBoneIndex: f32,
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
// The draw's identity, for the id attachment: which material, which object.
|
|
61
|
+
// They ride HERE, in padding this struct already carried, so the buffer's
|
|
62
|
+
// size and layout are untouched and the indirect-draw path keeps working —
|
|
63
|
+
// the ids arrive with the same per-draw uniform the material already binds,
|
|
64
|
+
// rather than needing a channel of their own. Zero while ids are off, and
|
|
65
|
+
// zero is the reserved "nothing" value, so reading them then is not wrong,
|
|
66
|
+
// just empty. f32 because the buffer is written as floats; the shader casts.
|
|
67
|
+
materialId: f32,
|
|
68
|
+
objectId: f32,
|
|
56
69
|
_pad2: f32,
|
|
57
70
|
};
|
|
58
71
|
|
|
@@ -70,7 +83,9 @@ struct VertexOutput {
|
|
|
70
83
|
@location(3) restPos: vec3f,
|
|
71
84
|
};
|
|
72
85
|
|
|
73
|
-
|
|
86
|
+
// One view-projection per shadow cascade, inner to outer — the volumes built
|
|
87
|
+
// by shadow-cascades.ts, in the same order.
|
|
88
|
+
struct LightVP { viewProj: array<mat4x4f, ${SHADOW_CASCADES.length}>, };
|
|
74
89
|
|
|
75
90
|
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
76
91
|
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
@@ -78,6 +93,10 @@ struct LightVP { viewProj: mat4x4f, };
|
|
|
78
93
|
@group(0) @binding(3) var shadowMap: texture_depth_2d;
|
|
79
94
|
@group(0) @binding(4) var shadowSampler: sampler_comparison;
|
|
80
95
|
@group(0) @binding(5) var<uniform> lightVP: LightVP;
|
|
96
|
+
// The far cascade's map — coarser texels over a much wider box, so the stage
|
|
97
|
+
// keeps its shadows when the crisp near volume ends. Binding 7: 6 is the
|
|
98
|
+
// positional lights, 9 the BRDF LUT.
|
|
99
|
+
@group(0) @binding(7) var shadowMapFar: texture_depth_2d;
|
|
81
100
|
// binding(9) brdfLut is declared inside NODES_WGSL (nodes.ts).
|
|
82
101
|
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
83
102
|
@group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
|
|
@@ -108,32 +127,60 @@ fn safe_normal(nIn: vec3f) -> vec3f {
|
|
|
108
127
|
}
|
|
109
128
|
|
|
110
129
|
`;
|
|
111
|
-
// ─── Shadow sampler (3×3 PCF)
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
// PCF taps landed TWO texels apart
|
|
116
|
-
//
|
|
117
|
-
|
|
130
|
+
// ─── Shadow sampler (3×3 PCF, cascade-selected) ─────────────────────
|
|
131
|
+
// Normal-bias 0.08, depth-bias 0.001 NDC. Unrolled — Safari's Metal backend
|
|
132
|
+
// doesn't unroll nested shadow loops reliably. Texel sizes are interpolated
|
|
133
|
+
// from SHADOW_CASCADES so they cannot go stale the way the hardcoded 1/2048
|
|
134
|
+
// once did (PCF taps landed TWO texels apart after the map grew to 4096,
|
|
135
|
+
// quantizing self-shadow edges into crawling gray squares).
|
|
136
|
+
//
|
|
137
|
+
// Selection: the crisp near cascade wherever its box covers the point — with a
|
|
138
|
+
// margin that keeps the whole PCF kernel inside the map, so selection never
|
|
139
|
+
// mixes maps mid-kernel — else the far cascade, else LIT. Lit is a fix, not
|
|
140
|
+
// merely a default: the old single-volume path clamped, and past the light's
|
|
141
|
+
// far plane the comparison failed against every stored depth, silently
|
|
142
|
+
// shadowing any stage deeper than the box.
|
|
143
|
+
const pcf9 = (map, ts) => /* wgsl */ `
|
|
144
|
+
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
145
|
+
let cmpZ = ndc.z - 0.001;
|
|
146
|
+
let ts = ${ts};
|
|
147
|
+
let s00 = textureSampleCompareLevel(${map}, shadowSampler, suv + vec2f(-ts, -ts), cmpZ);
|
|
148
|
+
let s10 = textureSampleCompareLevel(${map}, shadowSampler, suv + vec2f(0.0, -ts), cmpZ);
|
|
149
|
+
let s20 = textureSampleCompareLevel(${map}, shadowSampler, suv + vec2f( ts, -ts), cmpZ);
|
|
150
|
+
let s01 = textureSampleCompareLevel(${map}, shadowSampler, suv + vec2f(-ts, 0.0), cmpZ);
|
|
151
|
+
let s11 = textureSampleCompareLevel(${map}, shadowSampler, suv, cmpZ);
|
|
152
|
+
let s21 = textureSampleCompareLevel(${map}, shadowSampler, suv + vec2f( ts, 0.0), cmpZ);
|
|
153
|
+
let s02 = textureSampleCompareLevel(${map}, shadowSampler, suv + vec2f(-ts, ts), cmpZ);
|
|
154
|
+
let s12 = textureSampleCompareLevel(${map}, shadowSampler, suv + vec2f(0.0, ts), cmpZ);
|
|
155
|
+
let s22 = textureSampleCompareLevel(${map}, shadowSampler, suv + vec2f( ts, ts), cmpZ);
|
|
156
|
+
return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
|
|
157
|
+
`;
|
|
158
|
+
const SAMPLE_SHADOW_WGSL = /* wgsl */ `
|
|
159
|
+
|
|
160
|
+
fn sampleShadowNear(ndc: vec3f) -> f32 {
|
|
161
|
+
${pcf9("shadowMap", `1.0 / ${SHADOW_CASCADES[0].mapSize}.0`)}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn sampleShadowFar(ndc: vec3f) -> f32 {
|
|
165
|
+
${pcf9("shadowMapFar", `1.0 / ${SHADOW_CASCADES[SHADOW_CASCADES.length - 1].mapSize}.0`)}
|
|
166
|
+
}
|
|
118
167
|
|
|
119
168
|
fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
|
|
120
169
|
if (dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
|
|
121
170
|
let biasedPos = worldPos + n * 0.08;
|
|
122
|
-
let
|
|
123
|
-
let
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
let
|
|
128
|
-
let
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
let s22 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, ts), cmpZ);
|
|
136
|
-
return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
|
|
171
|
+
let c0 = lightVP.viewProj[0] * vec4f(biasedPos, 1.0);
|
|
172
|
+
let n0 = c0.xyz / max(c0.w, 1e-6);
|
|
173
|
+
if (all(abs(n0.xy) < vec2f(0.98)) && n0.z > 0.0 && n0.z < 1.0) {
|
|
174
|
+
return sampleShadowNear(n0);
|
|
175
|
+
}
|
|
176
|
+
let c1 = lightVP.viewProj[1] * vec4f(biasedPos, 1.0);
|
|
177
|
+
let n1 = c1.xyz / max(c1.w, 1e-6);
|
|
178
|
+
if (all(abs(n1.xy) < vec2f(0.98)) && n1.z > 0.0 && n1.z < 1.0) {
|
|
179
|
+
return sampleShadowFar(n1);
|
|
180
|
+
}
|
|
181
|
+
// Outside every cascade there is no occlusion information; lit is the only
|
|
182
|
+
// honest answer.
|
|
183
|
+
return 1.0;
|
|
137
184
|
}
|
|
138
185
|
|
|
139
186
|
`;
|
|
@@ -142,7 +189,7 @@ fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
|
|
|
142
189
|
// (PMX models occasionally ship with unnormalized weights on extras like hair tips).
|
|
143
190
|
// VS normalize on the outgoing normal is skipped — interpolation denormalizes it
|
|
144
191
|
// anyway and every fragment shader does `normalize(input.normal)` as its first line.
|
|
145
|
-
|
|
192
|
+
const COMMON_VS_WGSL = /* wgsl */ `
|
|
146
193
|
|
|
147
194
|
@vertex fn vs(
|
|
148
195
|
@location(0) position: vec3f,
|
|
@@ -192,15 +239,20 @@ export const COMMON_VS_WGSL = /* wgsl */ `
|
|
|
192
239
|
// src-alpha blending enabled:
|
|
193
240
|
// out.r = mask_r · src.a + dst.r · (1-src.a) (bloom mask, weighted by alpha)
|
|
194
241
|
// out.g = 1.0 · src.a + dst.g · (1-src.a) (canonical premultiplied alpha-over)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
242
|
+
// The struct itself comes from scene-contract, which owns what the scene pass's
|
|
243
|
+
// attachments ARE — this is one of the two shaders that gains an output when
|
|
244
|
+
// one is added, and the graph generator emits against this same declaration.
|
|
245
|
+
//
|
|
246
|
+
// A FUNCTION, and no longer part of the prelude constant below, because the id
|
|
247
|
+
// attachment is a device capability probed at init: a struct baked at import
|
|
248
|
+
// time would be built before the answer exists. The graph appends it where the
|
|
249
|
+
// constant used to end, so the assembled module is unchanged.
|
|
250
|
+
export function commonFsOutWgsl() {
|
|
251
|
+
return `\n\n${sceneFsOutWgsl()}\n`;
|
|
252
|
+
}
|
|
203
253
|
// ─── Convenience: full shared prelude ───────────────────────────────
|
|
204
254
|
// Material files compose this as `${NODES_WGSL}${COMMON_MATERIAL_PRELUDE_WGSL}` to
|
|
205
255
|
// pull in everything structural. Each material then adds its own constants + fs().
|
|
206
|
-
|
|
256
|
+
// The FSOut struct is NOT in here any more — see commonFsOutWgsl above. Every
|
|
257
|
+
// consumer of this constant appends it immediately, which is where it was.
|
|
258
|
+
export const COMMON_MATERIAL_PRELUDE_WGSL = COMMON_BINDINGS_WGSL + WORLD_AMBIENT_WGSL + lightsApi(0, 6) + SAMPLE_SHADOW_WGSL + COMMON_VS_WGSL;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Floats before the key map: count, pitch range, clock, duration, release. */
|
|
2
|
+
export declare const MIDI_HEADER = 8;
|
|
3
|
+
/** One energy slot per MIDI pitch. 128 is the MIDI range, not a cap we chose. */
|
|
4
|
+
export declare const MIDI_KEYS = 128;
|
|
5
|
+
/** Where the note records begin. */
|
|
6
|
+
export declare const MIDI_NOTES: number;
|
|
7
|
+
/** Floats per note: start, duration, pitch, velocity. */
|
|
8
|
+
export declare const MIDI_STRIDE = 4;
|
|
9
|
+
/** The rzNote*/ export declare function midiApi(group: number, binding: number): string;
|
|
10
|
+
//# sourceMappingURL=midi-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"midi-api.d.ts","sourceRoot":"","sources":["../../src/shaders/midi-api.ts"],"names":[],"mappings":"AA8BA,+EAA+E;AAC/E,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,iFAAiF;AACjF,eAAO,MAAM,SAAS,MAAM,CAAA;AAC5B,oCAAoC;AACpC,eAAO,MAAM,UAAU,QAA0B,CAAA;AACjD,yDAAyD;AACzD,eAAO,MAAM,WAAW,IAAI,CAAA;AAE5B,gBAAgB,CAChB,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA2E9D"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// The score data interface, as WGSL — shared verbatim by every effect module.
|
|
2
|
+
//
|
|
3
|
+
// The sibling of the audio interface, and precomputed for the same reason: an
|
|
4
|
+
// export steps the engine frame by frame rather than playing in real time, so
|
|
5
|
+
// anything read live from a player would render empty into the exported video.
|
|
6
|
+
// A note list is inherently precomputed, so it cannot even be tempted.
|
|
7
|
+
//
|
|
8
|
+
// WHY THIS EXISTS BESIDE rzAudio* RATHER THAN INSTEAD OF IT. The audio interface
|
|
9
|
+
// answers "how loud is the bass right now" — a spectrum, smeared across
|
|
10
|
+
// frequency and time. That is the right shape for a reactive backdrop and the
|
|
11
|
+
// wrong shape for a note: you cannot recover a discrete onset, a pitch and a
|
|
12
|
+
// duration from band energy, and the crispness of a falling note is the whole
|
|
13
|
+
// point of the look. The two are complementary — a scene can pulse its fog on
|
|
14
|
+
// rzAudioOnset while its notes fall on rzNoteAge.
|
|
15
|
+
//
|
|
16
|
+
// LAYOUT. An 8-float header, then a 128-float live key map, then the notes at 4
|
|
17
|
+
// floats each (start, duration, pitch, velocity). `time` and `playing` are
|
|
18
|
+
// rewritten every frame by whoever owns playback, exactly as the audio header
|
|
19
|
+
// is, so "now" is always the clock actually running.
|
|
20
|
+
//
|
|
21
|
+
// THE KEY MAP IS THE POINT OF THE DESIGN. Falling notes index the note list
|
|
22
|
+
// directly — one particle per note, which is what makes the geometry trivial.
|
|
23
|
+
// But a keyboard glow asks the opposite question, per pixel: is anything
|
|
24
|
+
// sounding at THIS pitch? Answering that by scanning the note list would be
|
|
25
|
+
// thousands of iterations per fragment. So the engine keeps a 128-entry
|
|
26
|
+
// per-pitch energy map, updated once per frame on the CPU, and the glow becomes
|
|
27
|
+
// one lookup — the same trade the audio interface makes by precomputing onset
|
|
28
|
+
// so "a beat-triggered effect is one comparison instead of a per-pixel history
|
|
29
|
+
// scan".
|
|
30
|
+
/** Floats before the key map: count, pitch range, clock, duration, release. */
|
|
31
|
+
export const MIDI_HEADER = 8;
|
|
32
|
+
/** One energy slot per MIDI pitch. 128 is the MIDI range, not a cap we chose. */
|
|
33
|
+
export const MIDI_KEYS = 128;
|
|
34
|
+
/** Where the note records begin. */
|
|
35
|
+
export const MIDI_NOTES = MIDI_HEADER + MIDI_KEYS;
|
|
36
|
+
/** Floats per note: start, duration, pitch, velocity. */
|
|
37
|
+
export const MIDI_STRIDE = 4;
|
|
38
|
+
/** The rzNote*/ /* rzKey* accessors, with the buffer declared at the given binding. */
|
|
39
|
+
export function midiApi(group, binding) {
|
|
40
|
+
return /* wgsl */ `
|
|
41
|
+
@group(${group}) @binding(${binding}) var<storage, read> _rzMidi: array<f32>;
|
|
42
|
+
|
|
43
|
+
/** Notes in the score; 0 when none is loaded, which every accessor below
|
|
44
|
+
* tolerates by returning zero rather than reading past the end. */
|
|
45
|
+
fn rzNoteCount() -> i32 { return i32(_rzMidi[0]); }
|
|
46
|
+
/** Where the score is NOW, in seconds — the clock every age below hangs from. */
|
|
47
|
+
fn rzMidiTime() -> f32 { return _rzMidi[3]; }
|
|
48
|
+
/** 1 while the score is advancing, 0 paused or absent. */
|
|
49
|
+
fn rzMidiPlaying() -> f32 { return select(0.0, _rzMidi[4], i32(_rzMidi[0]) > 0); }
|
|
50
|
+
/** Last note-off, in seconds — the length of the piece. */
|
|
51
|
+
fn rzMidiDuration() -> f32 { return _rzMidi[5]; }
|
|
52
|
+
/** Lowest and highest pitch the score actually uses. Lay a keyboard out against
|
|
53
|
+
* these rather than against 0..127 and a piano piece fills the screen instead
|
|
54
|
+
* of occupying its middle third. */
|
|
55
|
+
fn rzPitchLow() -> f32 { return _rzMidi[1]; }
|
|
56
|
+
fn rzPitchHigh() -> f32 { return _rzMidi[2]; }
|
|
57
|
+
|
|
58
|
+
fn _rzNoteAt(i: i32, field: i32) -> f32 {
|
|
59
|
+
if (i < 0 || i >= i32(_rzMidi[0])) { return 0.0; }
|
|
60
|
+
return _rzMidi[${MIDI_NOTES} + i * ${MIDI_STRIDE} + field];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** When note i begins, in seconds. */
|
|
64
|
+
fn rzNoteStart(i: i32) -> f32 { return _rzNoteAt(i, 0); }
|
|
65
|
+
/** How long note i sounds, in seconds. */
|
|
66
|
+
fn rzNoteLength(i: i32) -> f32 { return _rzNoteAt(i, 1); }
|
|
67
|
+
/** MIDI pitch of note i — 60 is middle C. */
|
|
68
|
+
fn rzNotePitch(i: i32) -> f32 { return _rzNoteAt(i, 2); }
|
|
69
|
+
/** How hard note i was struck, 0..1. */
|
|
70
|
+
fn rzNoteVelocity(i: i32) -> f32 { return _rzNoteAt(i, 3); }
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Seconds since note i began — NEGATIVE before it does.
|
|
74
|
+
*
|
|
75
|
+
* The falling-note function, and the sign is the whole of it: a note's height is
|
|
76
|
+
* -rzNoteAge(i) * speed, so a note not yet played sits above the keyboard at a
|
|
77
|
+
* distance proportional to how long is left, crosses zero exactly as it sounds,
|
|
78
|
+
* and keeps going. One expression, no state, no spawning — which is why one
|
|
79
|
+
* particle per note is the natural mapping and the pool index IS the note index.
|
|
80
|
+
*/
|
|
81
|
+
fn rzNoteAge(i: i32) -> f32 { return _rzMidi[3] - rzNoteStart(i); }
|
|
82
|
+
|
|
83
|
+
/** 1 while note i is sounding, 0 either side of it. */
|
|
84
|
+
fn rzNoteHeld(i: i32) -> f32 {
|
|
85
|
+
let age = rzNoteAge(i);
|
|
86
|
+
return select(0.0, 1.0, age >= 0.0 && age < rzNoteLength(i));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Energy at a MIDI pitch right now: 1 the instant it is struck, holding while
|
|
91
|
+
* sounding, then falling away over the release set at install time.
|
|
92
|
+
*
|
|
93
|
+
* Per PITCH rather than per note, because that is the question a keyboard asks
|
|
94
|
+
* and a note list cannot answer cheaply — see the header. Fractional pitches
|
|
95
|
+
* read the nearest key, so a caller can hand this a continuous x across the
|
|
96
|
+
* keyboard without rounding first.
|
|
97
|
+
*/
|
|
98
|
+
fn rzKeyEnergy(pitch: f32) -> f32 {
|
|
99
|
+
let k = i32(round(pitch));
|
|
100
|
+
if (k < 0 || k >= ${MIDI_KEYS}) { return 0.0; }
|
|
101
|
+
return _rzMidi[${MIDI_HEADER} + k];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A pitch's place across the score's own range, 0..1 — the x of a falling note
|
|
106
|
+
* and of the key it lands on, which have to agree or the effect is nonsense.
|
|
107
|
+
*/
|
|
108
|
+
fn rzPitchX(pitch: f32) -> f32 {
|
|
109
|
+
let lo = _rzMidi[1];
|
|
110
|
+
let hi = _rzMidi[2];
|
|
111
|
+
return select(0.5, (pitch - lo) / max(hi - lo, 1.0), hi > lo);
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
@@ -54,21 +54,8 @@ export declare function parseEffectAnchors(wgsl: string, max: number): {
|
|
|
54
54
|
bone: string;
|
|
55
55
|
trail: boolean;
|
|
56
56
|
}[];
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* engine that fills it. Interpolated into the WGSL rather than written twice:
|
|
60
|
-
* the layout arithmetic on both sides has to agree exactly, and two literals
|
|
61
|
-
* that must match are two literals that eventually will not.
|
|
62
|
-
*
|
|
63
|
-
* All three are MINIMUMS. Raising one breaks nothing, because effects read
|
|
64
|
-
* through accessors and loop to the count functions; lowering one does.
|
|
65
|
-
*/
|
|
66
|
-
export declare const EFFECT_SUBJECTS = 4;
|
|
67
|
-
export declare const EFFECT_ANCHORS = 8;
|
|
68
|
-
export declare const EFFECT_TRAIL_SAMPLES = 128;
|
|
69
|
-
/** vec4 slot where the trails begin — after the subjects and the anchors. */
|
|
70
|
-
export declare const EFFECT_TRAIL_BASE: number;
|
|
71
|
-
export type CompositeEffectSource = {
|
|
57
|
+
export { EFFECT_ANCHORS, EFFECT_SUBJECTS, EFFECT_TRAIL_BASE, EFFECT_TRAIL_SAMPLES } from "../cast-layout";
|
|
58
|
+
type CompositeEffectSource = {
|
|
72
59
|
/** The user's WGSL verbatim: helpers plus whichever entry points it defines. */
|
|
73
60
|
wgsl: string;
|
|
74
61
|
/** Codegen'd `struct EffectParams {...}` + binding decl; empty when no params. */
|
|
@@ -77,8 +64,37 @@ export type CompositeEffectSource = {
|
|
|
77
64
|
hasBackground: boolean;
|
|
78
65
|
/** Defines `fn foreground(...)` — mount over the finished frame. */
|
|
79
66
|
hasForeground: boolean;
|
|
67
|
+
/** Grid resolution when the effect declared `// @grid`, else 0. */
|
|
68
|
+
gridSize: number;
|
|
69
|
+
/** Whether the scene pass carries the id attachment, so the field module can
|
|
70
|
+
* bind it. False emits accessors that answer 0 rather than nothing at all. */
|
|
71
|
+
ids?: boolean;
|
|
72
|
+
/** How many of this effect's anchors asked for a trail — RZ_TRAIL_SLOTS,
|
|
73
|
+
* which a hosted particle or trail loop reads even in this module. REQUIRED,
|
|
74
|
+
* and deliberately not defaulted: a silent 0 is a ribbon loop that iterates
|
|
75
|
+
* nothing, which looks exactly like an effect that drew nothing. */
|
|
76
|
+
trailCount: number;
|
|
77
|
+
/** This effect's local anchor slot → scene slot, from the shared table.
|
|
78
|
+
* Omitted or identity when it owns the table. The particle and trail modules
|
|
79
|
+
* have always taken this; the field module not taking it was the bug. */
|
|
80
|
+
alias?: number[];
|
|
80
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* The scene half of the effect API — camera, projection, cast, trails.
|
|
84
|
+
*
|
|
85
|
+
* Split out of COMPOSITE_HEAD so the SIM pass can have it too. One effect file
|
|
86
|
+
* is spliced into every module it has a mount in, so a file with both a grid and
|
|
87
|
+
* a foreground compiles its foreground inside the grid shader, where every
|
|
88
|
+
* rzCameraPos() in it has to resolve. Sharing the block is better than stubbing
|
|
89
|
+
* it twice, and it means a kernel can legitimately ask where a bone is — which
|
|
90
|
+
* is exactly what a wake wants.
|
|
91
|
+
*
|
|
92
|
+
* Depends on `viewU` and `_rzCast` being declared by the including module, and
|
|
93
|
+
* on nothing else: no textures, no samplers.
|
|
94
|
+
*/
|
|
95
|
+
export declare const EFFECT_SCENE_API = "\n// \u2500\u2500 The effect API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Named rz*, for the engine. The prefix earns its place twice: user code is\n// concatenated into THIS module, so an unprefixed rzAnchor() would collide with\n// exactly the helper an author would write, and the old bg* prefix stopped being\n// true in 0.41.0 when effects gained a mount over the finished frame.\n//\n// The bg* names below are permanent aliases, not a deprecation with an end date.\n// A published link is immutable, so a scene pinned to a bg* effect has to keep\n// compiling forever. They are one-line and inlined; no new function gets one.\n\n/** Canvas size in pixels \u2014 for aspect correction. */\nfn rzResolution() -> vec2f { return viewU[6].zw; }\n\n/** The camera's world position. */\nfn rzCameraPos() -> vec3f { return viewU[10].xyz; }\n\n/** How many characters are in the scene, up to four. */\nfn rzSubjectCount() -> i32 { return i32(viewU[10].w); }\n\n/**\n * A world point as the camera sees it: xy the uv it lands on, z its distance\n * along the VIEW AXIS in metres.\n *\n * The exact inverse of the ray this pass builds per pixel, so it is the cheap way\n * to work with anything anchored in the world. Marching a curve or a trail in 3D\n * costs a distance evaluation per sample per pixel; projecting its points once\n * and measuring in 2D costs a subtraction, which is the difference between a\n * ribbon that runs at 4K and one that does not.\n *\n * z is directly comparable to the depth handed to foreground(), so occlusion is\n * a single test: draw where your z is nearer than the scene's. It is returned\n * SIGNED and unclamped \u2014 behind the camera is negative, and worth rejecting\n * before you use the uv, which is meaningless there.\n */\nfn rzProject(p: vec3f) -> vec3f {\n let d = p - viewU[10].xyz;\n let z = dot(d, viewU[5].xyz);\n // Guard only the divide. z itself is returned as it is, so the caller can see\n // the sign; clamping it here would put points behind the lens on the horizon.\n let inv = 1.0 / select(z, 1e-4, z < 1e-4);\n let ndc = vec2f(dot(d, viewU[3].xyz) * inv / viewU[3].w, dot(d, viewU[4].xyz) * inv / viewU[4].w);\n return vec3f(ndc * 0.5 + 0.5, z);\n}\n\nfn rzCamPos() -> vec3f { return rzCameraPos(); }\nfn rzCameraRight() -> vec3f { return viewU[3].xyz; }\nfn rzCameraUp() -> vec3f { return viewU[4].xyz; }\nfn rzCameraForward() -> vec3f { return viewU[5].xyz; }\n\n// The cast \u2014 subjects, anchors, trails \u2014 is CAST_API, shared verbatim with the\n// particle and trail modules. It used to be written out here, a second time, and\n// the two copies had drifted: this one had rzAnchor and that one did not.\n\nconst RZ_SUBJECTS: i32 = 4;\nconst RZ_SAMPLES: i32 = 128;\n/** The anchor ADDRESS SPACE \u2014 how many an effect may declare, not how many it\n * did. RZ_TRAIL_SLOTS is the per-effect number and is not this one; the two\n * being one number was the old trail bug. */\nconst RZ_MAX_ANCHORS: i32 = 8;\nconst RZ_TRAIL_SAMPLES: i32 = 128;\n\nstruct RzSubject {\n /** On the FLOOR, under the body \u2014 where a ring or a magic circle belongs. */\n root: vec3f,\n /** At the hips, the middle of the body \u2014 where an aura belongs. */\n center: vec3f,\n /** Bounding sphere: xyz centre, w radius. Deliberately generous \u2014 cull with it. */\n bounds: vec4f,\n /** False past the end of the cast, and every field is then zero. */\n valid: bool,\n}\n\nstruct RzAnchor {\n pos: vec3f,\n /** World units per second, from the previous frame. Direction for a trail,\n * magnitude for anything that should react to how hard someone is moving. */\n vel: vec3f,\n /** The bone's forward axis \u2014 which way a foot points, where a head looks. */\n fwd: vec3f,\n /** False when this rig has no such bone. Check it: the alternative is drawing\n * a hand effect at the world origin on every model that spells it differently. */\n valid: bool,\n}\n\n/** Which model this is, stable across a scene \u2014 for per-subject variation. */\nfn rzSubjectId(i: i32) -> u32 {\n if (i < 0 || i >= rzSubjectCount()) { return 0u; }\n return u32(_rzCast[i * 3 + 1].w);\n}\n\nfn rzSubject(i: i32) -> RzSubject {\n var s: RzSubject;\n s.valid = i >= 0 && i < rzSubjectCount();\n if (!s.valid) { return s; }\n let b = i * 3;\n s.root = _rzCast[b].xyz;\n s.center = _rzCast[b + 1].xyz;\n s.bounds = _rzCast[b + 2];\n return s;\n}\n\n/**\n * Where a named bone is, this frame.\n *\n * The slot is the author's own: the Nth @anchor in their file, in the order\n * they wrote them. _rzSlot turns that into the scene's address, which is what\n * keeps two effects that both anchor to a wrist from reading each other's.\n */\nfn rzAnchor(subject: i32, slot: i32) -> RzAnchor {\n var a: RzAnchor;\n a.valid = false;\n let g = _rzSlot(slot);\n if (subject < 0 || subject >= rzSubjectCount() || g < 0 || g >= RZ_MAX_ANCHORS) { return a; }\n let b = 12 + (g * 4 + subject) * 3;\n a.valid = _rzCast[b].w > 0.5;\n a.pos = _rzCast[b].xyz;\n a.vel = _rzCast[b + 1].xyz;\n a.fwd = _rzCast[b + 2].xyz;\n return a;\n}\n\n/**\n * How many samples of a path are recorded \u2014 0 for an anchor that asked for no\n * trail, and for one that has not moved yet.\n *\n * Bounded by the anchor cap, NOT by how many anchors asked for a trail. Those\n * are different index spaces: storage is addressed by anchor slot, so an\n * untrailed @anchor followed by a trailed one put the trail at index 1 with a\n * bound of 1 and rzTrail returned zero \u2014 a ribbon that silently did not draw.\n */\nfn rzTrailCount(subject: i32, slot: i32) -> i32 {\n let g = _rzSlot(slot);\n if (subject < 0 || subject >= rzSubjectCount() || g < 0 || g >= RZ_MAX_ANCHORS) { return 0; }\n return i32(_rzCast[12 + (g * 4 + subject) * 3 + 2].w);\n}\n\n/** Sample i of a path: xyz where it was, w how many seconds ago. i = 0 is now. */\nfn rzTrail(subject: i32, slot: i32, i: i32) -> vec4f {\n let n = rzTrailCount(subject, slot);\n if (i < 0 || i >= n) { return vec4f(0.0); }\n let base = 108 + (_rzSlot(slot) * 4 + subject) * RZ_TRAIL_SAMPLES;\n return _rzCast[base + i];\n}\n\n\n// The hashes, the noise and rzFalloff \u2014 EFFECT_MATH_API, the same text the\n// particle and trail modules get. The field module used to be missing most of\n// it, so a helper an author wrote for one mount failed to compile in another\n// for no reason visible in the file.\n\nfn rzHash11(x: f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\nfn rzHash21(p: vec2f) -> f32 {\n var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n/** Three independent randoms from one seed \u2014 the usual need when spawning. */\nfn rzHash13(x: f32) -> vec3f {\n return vec3f(rzHash11(x), rzHash11(x + 17.13), rzHash11(x + 41.71));\n}\n/**\n * Compact-support falloff: 1 at the centre, exactly 0 at r, smooth between.\n *\n * It reaches exactly zero rather than merely getting small, because a glow that\n * never quite ends has to be culled somewhere, and culling it wherever it looks\n * close enough is what put a visible hard edge on the first halo effect.\n */\nfn rzFalloff(d: f32, r: f32) -> f32 {\n let x = clamp(d / max(r, 1e-6), 0.0, 1.0);\n let f = 1.0 - x;\n return f * f * f;\n}\nfn rzHash31(p: vec3f) -> f32 {\n var p3 = fract(p * 0.1031);\n p3 = p3 + dot(p3, p3.zyx + 31.32);\n return fract((p3.x + p3.y) * p3.z);\n}\nfn rzValueNoise(p: vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = rzHash31(i);\n let n100 = rzHash31(i + vec3f(1.0, 0.0, 0.0));\n let n010 = rzHash31(i + vec3f(0.0, 1.0, 0.0));\n let n110 = rzHash31(i + vec3f(1.0, 1.0, 0.0));\n let n001 = rzHash31(i + vec3f(0.0, 0.0, 1.0));\n let n101 = rzHash31(i + vec3f(1.0, 0.0, 1.0));\n let n011 = rzHash31(i + vec3f(0.0, 1.0, 1.0));\n let n111 = rzHash31(i + vec3f(1.0, 1.0, 1.0));\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n/** Divergence-free flow \u2014 the field a wisp of smoke follows without a solver. */\nfn rzCurlNoise(p: vec3f) -> vec3f {\n let e = 0.1;\n let dx = vec3f(e, 0.0, 0.0);\n let dy = vec3f(0.0, e, 0.0);\n let dz = vec3f(0.0, 0.0, e);\n let x0 = rzValueNoise(p - dx); let x1 = rzValueNoise(p + dx);\n let y0 = rzValueNoise(p - dy); let y1 = rzValueNoise(p + dy);\n let z0 = rzValueNoise(p - dz); let z1 = rzValueNoise(p + dz);\n return normalize(vec3f((y1 - y0) - (z1 - z0), (z1 - z0) - (x1 - x0), (x1 - x0) - (y1 - y0)) + vec3f(1e-6));\n}\n\nstruct Particle {\n pos: vec3f,\n age: f32,\n vel: vec3f,\n life: f32,\n size: f32,\n rot: f32,\n seed: f32,\n // Aspect along the direction of travel. 1 or less is a square billboard; a\n // raindrop is 10 or 20. Zero-initialised, so an effect that never sets it gets\n // the square it expects.\n stretch: f32,\n}\n\n\n\nfn bgResolution() -> vec2f { return rzResolution(); }\nfn bgCameraPos() -> vec3f { return rzCameraPos(); }\nfn bgSubjectCount() -> i32 { return rzSubjectCount(); }\n\n/**\n * Where a character IS, in world space \u2014 at the hips, not on the floor.\n *\n * An effect that wants to RESPOND to the cast \u2014 a glow that follows someone,\n * dust kicked up where they are \u2014 needs to know where they are, and the ray and\n * the depth cannot tell it: they describe the pixel, not the scene.\n *\n * The value is model.position + \u30BB\u30F3\u30BF\u30FC + \u5168\u3066\u306E\u89AA. \u30BB\u30F3\u30BF\u30FC sits at hip\n * height on every standard MMD rig, so this is a point in the middle of the\n * body. It is NOT the contact point: a ripple drawn here appears at the waist.\n * Ground effects want the .xz of this and their own floor height, which is what\n * the effects that shipped against it already do.\n *\n * The comment here used to claim it was \"between the feet on the floor\", which\n * is where that habit came from. Left as it is regardless of the name: a\n * published link is immutable, so every shared scene pinning an effect that\n * reads this depends on it meaning exactly what it has always meant.\n *\n * Clamped rather than bounds-checked: an effect looping past the count reads the\n * last subject instead of sampling whatever follows the array, which is a wrong\n * ripple rather than an undefined one.\n */\nfn rzSubjectHip(i: i32) -> vec3f { return viewU[11 + clamp(i, 0, 3)].xyz; }\n\nfn bgSubjectPos(i: i32) -> vec3f { return rzSubjectHip(i); }\n\n/** Where in the WORLD the scene drew this pixel \u2014 the depth handed to\n * foreground() turned into a place. Without it an effect can only think in\n * distances from the lens, which is no use to anything that belongs somewhere:\n * fog lying on the ground has to know where the ground is.\n *\n * depth measures along the VIEW AXIS, not along the ray, so it is divided by\n * the ray's projection onto camera-forward before being walked out. At the far\n * plane (nothing drawn) this lands a very long way off, which is what a sky\n * should do to anything reading it. */\nfn rzWorldPos(ray: vec3f, depth: f32) -> vec3f {\n let axis = max(dot(normalize(ray), viewU[5].xyz), 1e-4);\n return rzCameraPos() + normalize(ray) * (depth / axis);\n}\n\nfn bgWorldPos(ray: vec3f, depth: f32) -> vec3f { return rzWorldPos(ray, depth); }\n\n/** What an effect returns for one of its lights. */\nstruct RzLight {\n pos: vec3f,\n color: vec3f,\n intensity: f32,\n radius: f32,\n}\n\n\n/** Color grading, applied to the tonemapped SCENE (not the background \u2014 see the\n * call site). The core is ASC CDL, the film-industry interchange standard:\n *\n * out = (in \u00B7 slope + offset) ^ power then saturation (SOP \u2192 SAT)\n *\n * Using the real standard rather than invented controls means a look authored\n * here maps onto any grading tool. slope/offset/power are derived on the CPU\n * from the UI's shadow/midtone/highlight colors (see setColorGrading), so the\n * per-pixel cost is one mul-add, one pow, one lerp. */\nfn grade(c: vec3f) -> vec3f {\n var x = pow(max(c * viewU[9].xyz + viewU[7].xyz, vec3f(0.0)), viewU[8].xyz);\n // Contrast pivots on 0.5 \u2014 display-referred midpoint, since we grade post-Filmic.\n x = (x - vec3f(0.5)) * viewU[7].w + vec3f(0.5);\n // Rec.709 luma, matching the ASC SAT node.\n let luma = dot(x, vec3f(0.2126, 0.7152, 0.0722));\n return max(mix(vec3f(luma), x, viewU[8].w), vec3f(0.0));\n}\n";
|
|
81
96
|
export declare function buildCompositeShader(effect?: CompositeEffectSource | null): string;
|
|
97
|
+
export declare function buildFieldShader(effect: CompositeEffectSource): string;
|
|
82
98
|
/** Kept for compatibility with existing imports (the base, no-effect shader). */
|
|
83
99
|
export declare const COMPOSITE_SHADER_WGSL: string;
|
|
84
100
|
//# sourceMappingURL=composite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AAwBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAgC+B;AAC/B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAAE,CAIhG;AAID,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAEzG,KAAK,qBAAqB,GAAG;IAC3B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,aAAa,EAAE,OAAO,CAAA;IACtB,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAA;IACtB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB;mFAC+E;IAC/E,GAAG,CAAC,EAAE,OAAO,CAAA;IACb;;;yEAGqE;IACrE,UAAU,EAAE,MAAM,CAAA;IAClB;;8EAE0E;IAC1E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AA6LD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,kqZA+H5B,CAAA;AA4LD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAQlF;AAuDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAqEtE;AAED,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,QAA6B,CAAA"}
|