reze-engine 0.61.6 → 0.62.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/dist/_dbg-loader.mjs +1100 -0
- package/dist/engine.d.ts +24 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +105 -9
- package/dist/graph/presets/cloth_rough.d.ts.map +1 -1
- package/dist/graph/presets/cloth_rough.js +5 -3
- package/dist/graph/schema.d.ts +1 -1
- package/dist/graph/schema.d.ts.map +1 -1
- package/dist/graph/schema.js +3 -1
- package/dist/physics-debug.d.ts +30 -0
- package/dist/physics-debug.d.ts.map +1 -0
- package/dist/physics-debug.js +526 -0
- package/dist/shaders/directives.d.ts +10 -0
- package/dist/shaders/directives.d.ts.map +1 -1
- package/dist/shaders/directives.js +11 -0
- package/dist/shaders/lights.d.ts.map +1 -1
- package/dist/shaders/lights.js +2 -1
- package/dist/shaders/materials/body.d.ts +2 -0
- package/dist/shaders/materials/body.d.ts.map +1 -0
- package/dist/shaders/materials/body.js +95 -0
- package/dist/shaders/materials/cloth_rough.d.ts +2 -0
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -0
- package/dist/shaders/materials/cloth_rough.js +69 -0
- package/dist/shaders/materials/cloth_smooth.d.ts +2 -0
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -0
- package/dist/shaders/materials/cloth_smooth.js +61 -0
- package/dist/shaders/materials/default.d.ts +2 -0
- package/dist/shaders/materials/default.d.ts.map +1 -0
- package/dist/shaders/materials/default.js +43 -0
- package/dist/shaders/materials/eye.d.ts +2 -0
- package/dist/shaders/materials/eye.d.ts.map +1 -0
- package/dist/shaders/materials/eye.js +60 -0
- package/dist/shaders/materials/face.d.ts +2 -0
- package/dist/shaders/materials/face.d.ts.map +1 -0
- package/dist/shaders/materials/face.js +95 -0
- package/dist/shaders/materials/hair.d.ts +2 -0
- package/dist/shaders/materials/hair.d.ts.map +1 -0
- package/dist/shaders/materials/hair.js +90 -0
- package/dist/shaders/materials/metal.d.ts +2 -0
- package/dist/shaders/materials/metal.d.ts.map +1 -0
- package/dist/shaders/materials/metal.js +77 -0
- package/dist/shaders/materials/mmd_classic.d.ts +2 -0
- package/dist/shaders/materials/mmd_classic.d.ts.map +1 -0
- package/dist/shaders/materials/mmd_classic.js +66 -0
- package/dist/shaders/materials/stockings.d.ts +2 -0
- package/dist/shaders/materials/stockings.d.ts.map +1 -0
- package/dist/shaders/materials/stockings.js +122 -0
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +2 -0
- package/dist/shaders/passes/grid.d.ts.map +1 -1
- package/dist/shaders/passes/grid.js +2 -0
- package/dist/shaders/passes/particles.d.ts +7 -0
- package/dist/shaders/passes/particles.d.ts.map +1 -1
- package/dist/shaders/passes/particles.js +10 -0
- package/dist/shaders/passes/physics-debug.d.ts +2 -0
- package/dist/shaders/passes/physics-debug.d.ts.map +1 -0
- package/dist/shaders/passes/physics-debug.js +69 -0
- package/dist/shaders/passes/scene-contract.d.ts.map +1 -1
- package/dist/shaders/passes/scene-contract.js +6 -2
- package/dist/shaders/passes/trails.d.ts.map +1 -1
- package/dist/shaders/passes/trails.js +2 -1
- package/dist/shaders/texture-api.d.ts +9 -0
- package/dist/shaders/texture-api.d.ts.map +1 -0
- package/dist/shaders/texture-api.js +54 -0
- package/package.json +1 -1
- package/src/engine.ts +119 -5
- package/src/graph/presets/cloth_rough.ts +5 -3
- package/src/graph/schema.ts +3 -1
- package/src/shaders/directives.ts +21 -0
- package/src/shaders/lights.ts +2 -1
- package/src/shaders/passes/composite.ts +2 -0
- package/src/shaders/passes/grid.ts +2 -0
- package/src/shaders/passes/particles.ts +13 -0
- package/src/shaders/passes/scene-contract.ts +6 -2
- package/src/shaders/passes/trails.ts +2 -1
- package/src/shaders/texture-api.ts +56 -0
- package/dist/graph/presets/pool_floor.d.ts +0 -3
- package/dist/graph/presets/pool_floor.d.ts.map +0 -1
- package/dist/graph/presets/pool_floor.js +0 -42
- package/dist/graph/presets/water.d.ts +0 -3
- package/dist/graph/presets/water.d.ts.map +0 -1
- package/dist/graph/presets/water.js +0 -31
- package/dist/physics/autofit.d.ts +0 -147
- package/dist/physics/autofit.d.ts.map +0 -1
- package/dist/physics/autofit.js +0 -501
- package/dist/shaders/passes/field-blit.d.ts +0 -26
- package/dist/shaders/passes/field-blit.d.ts.map +0 -1
- package/dist/shaders/passes/field-blit.js +0 -65
- package/dist/shaders/passes/ghosts.d.ts +0 -22
- package/dist/shaders/passes/ghosts.d.ts.map +0 -1
- package/dist/shaders/passes/ghosts.js +0 -75
- package/dist/shaders/passes/ground-noise.d.ts +0 -7
- package/dist/shaders/passes/ground-noise.d.ts.map +0 -1
- package/dist/shaders/passes/ground-noise.js +0 -88
- package/dist/shaders/passes/sim.d.ts +0 -34
- package/dist/shaders/passes/sim.d.ts.map +0 -1
- package/dist/shaders/passes/sim.js +0 -169
- package/dist/shaders/score-api.d.ts +0 -10
- package/dist/shaders/score-api.d.ts.map +0 -1
- package/dist/shaders/score-api.js +0 -114
- package/dist/shaders/shadow-api.d.ts +0 -29
- package/dist/shaders/shadow-api.d.ts.map +0 -1
- package/dist/shaders/shadow-api.js +0 -112
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Wireframe + solid overlay for physics rigid bodies (sphere / box / capsule).
|
|
2
|
+
// Rendered in its own pass after composite, straight to the swapchain — no
|
|
3
|
+
// MSAA, no depth, no MRT — so it sits cleanly on top of the tonemapped scene
|
|
4
|
+
// regardless of camera angle, model occlusion, or the composite alpha gate.
|
|
5
|
+
//
|
|
6
|
+
// SHAPE_KIND 0 = sphere → unit-sphere line/tri primitive scaled by size.x (radius)
|
|
7
|
+
// 1 = box → unit cube ±1 scaled by size.xyz (half-extents)
|
|
8
|
+
// 2 = capsule → unit cap+ring with per-vertex `axial` (-1/0/+1)
|
|
9
|
+
// that lifts cap rings/arcs ±halfHeight along Y;
|
|
10
|
+
// XZ scales by size.x (radius), Y by size.y/2.
|
|
11
|
+
// SOLID_ALPHA fragment alpha multiplier. Wireframe pipelines leave this at
|
|
12
|
+
// 1.0 for crisp edges; solid pipelines set it to ~0.2 so the fill
|
|
13
|
+
// stays gentle under stacked bodies.
|
|
14
|
+
export const PHYSICS_DEBUG_SHADER_WGSL = /* wgsl */ `
|
|
15
|
+
struct CameraUniforms {
|
|
16
|
+
view: mat4x4f,
|
|
17
|
+
projection: mat4x4f,
|
|
18
|
+
viewPos: vec3f,
|
|
19
|
+
_pad: f32,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
23
|
+
|
|
24
|
+
override SHAPE_KIND: u32 = 0u;
|
|
25
|
+
override SOLID_ALPHA: f32 = 1.0;
|
|
26
|
+
|
|
27
|
+
struct VsIn {
|
|
28
|
+
@location(0) unit: vec4f, // xyz = unit pos; w = capsule axial anchor
|
|
29
|
+
@location(1) modelCol0: vec4f,
|
|
30
|
+
@location(2) modelCol1: vec4f,
|
|
31
|
+
@location(3) modelCol2: vec4f,
|
|
32
|
+
@location(4) modelCol3: vec4f,
|
|
33
|
+
@location(5) size: vec4f, // xyz = size; w unused
|
|
34
|
+
@location(6) color: vec4f,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
struct VsOut {
|
|
38
|
+
@builtin(position) pos: vec4f,
|
|
39
|
+
@location(0) color: vec4f,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
@vertex
|
|
43
|
+
fn vsMain(in: VsIn) -> VsOut {
|
|
44
|
+
let model = mat4x4f(in.modelCol0, in.modelCol1, in.modelCol2, in.modelCol3);
|
|
45
|
+
var local: vec3f;
|
|
46
|
+
if (SHAPE_KIND == 1u) {
|
|
47
|
+
local = in.unit.xyz * in.size.xyz;
|
|
48
|
+
} else if (SHAPE_KIND == 2u) {
|
|
49
|
+
// PMX capsule: size.x = radius, size.y = full cylinder height. Bullet 2.x's
|
|
50
|
+
// btCapsuleShape(radius, height) stores height/2 internally, so the actual
|
|
51
|
+
// collision cylinder spans ±size.y/2 — halve here to match.
|
|
52
|
+
let r = in.size.x;
|
|
53
|
+
let halfH = in.size.y * 0.5;
|
|
54
|
+
local = vec3f(in.unit.x * r, in.unit.y * r + halfH * in.unit.w, in.unit.z * r);
|
|
55
|
+
} else {
|
|
56
|
+
local = in.unit.xyz * in.size.x;
|
|
57
|
+
}
|
|
58
|
+
let world = model * vec4f(local, 1.0);
|
|
59
|
+
var out: VsOut;
|
|
60
|
+
out.pos = camera.projection * camera.view * world;
|
|
61
|
+
out.color = in.color;
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@fragment
|
|
66
|
+
fn fsMain(in: VsOut) -> @location(0) vec4f {
|
|
67
|
+
return vec4f(in.color.rgb, in.color.a * SOLID_ALPHA);
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
@@ -1 +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;;;;6EAI6E;GAC3E,QAAQ;AACV;;;;;;yEAMyE;GACvE,mBAAmB;AACrB,iEAAiE;GAC/D,UAAU;AACZ;;;mDAGmD;GACjD,mBAAmB;AACrB;;;oCAGoC;GAClC,OAAO;AACT;;;kDAGkD;GAChD,eAAe,CAAA;
|
|
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;;;;6EAI6E;GAC3E,QAAQ;AACV;;;;;;yEAMyE;GACvE,mBAAmB;AACrB,iEAAiE;GAC/D,UAAU;AACZ;;;mDAGmD;GACjD,mBAAmB;AACrB;;;oCAGoC;GAClC,OAAO;AACT;;;kDAGkD;GAChD,eAAe,CAAA;AAyFnB;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,GAAG,mBAAmB,EAAE,CAoBhG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAE3E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;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;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
|
@@ -72,7 +72,7 @@ const ADD_BOTH = {
|
|
|
72
72
|
* OVER for something that arrives ALREADY premultiplied: take the source whole
|
|
73
73
|
* and let it displace the destination by its own coverage.
|
|
74
74
|
*
|
|
75
|
-
* The ground
|
|
75
|
+
* The ground, the mirror and particles need it. The other classes write a
|
|
76
76
|
* straight colour and an alpha, and the src-alpha factor premultiplies it once
|
|
77
77
|
* on the way in. The ground cannot: its coverage is the SUM of a lit surface
|
|
78
78
|
* and a colourless shadow-catcher layer, so it has to weight its own colour by
|
|
@@ -125,7 +125,11 @@ const BLENDS = {
|
|
|
125
125
|
// everything else, and coverage is what the blend applies.
|
|
126
126
|
ground: [PREMULTIPLIED_OVER, ALPHA_OVER],
|
|
127
127
|
outline: [ALPHA_OVER, ALPHA_OVER],
|
|
128
|
-
|
|
128
|
+
// PREMULTIPLIED colour: the particle fragment weights its colour by its own
|
|
129
|
+
// alpha before it writes (passes/particles.ts), so the src-alpha blend would
|
|
130
|
+
// weight it a second time — a splash at alpha 0.1 arrived at a hundredth of
|
|
131
|
+
// its colour instead of a tenth. The aux mask is written straight, as ever.
|
|
132
|
+
particle: [PREMULTIPLIED_OVER, ALPHA_OVER],
|
|
129
133
|
"particle-additive": [ADD_KEEP_ALPHA, ADD_BOTH],
|
|
130
134
|
trail: [ADD_PREMULTIPLIED, ALPHA_OVER],
|
|
131
135
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trails.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/trails.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"trails.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/trails.ts"],"names":[],"mappings":"AAgCA,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,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;IACd;sFACkF;IAClF,QAAQ,EAAE,MAAM,CAAA;CACjB,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,CA6ZR"}
|
|
@@ -12,6 +12,7 @@ import { sceneIdFieldWgsl, sceneIdPadWgsl } from "./scene-contract";
|
|
|
12
12
|
import { idApi } from "../id-api";
|
|
13
13
|
import { sceneLightApi } from "../scene-light-api";
|
|
14
14
|
import { pointsApi } from "../points-api";
|
|
15
|
+
import { textureApi } from "../texture-api";
|
|
15
16
|
/**
|
|
16
17
|
* Sub-segments drawn between each pair of recorded samples.
|
|
17
18
|
*
|
|
@@ -249,7 +250,7 @@ fn rzTurnRadius(a: vec3f, b: vec3f, c: vec3f) -> f32 {
|
|
|
249
250
|
gridReadApi(0, 8, 9, src.gridSize) +
|
|
250
251
|
// Stubbed: this module cannot read an attachment the scene pass writes — see
|
|
251
252
|
// id-api.ts. The author's whole file compiles here, so the names must exist.
|
|
252
|
-
idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) + pointsApi(false) +
|
|
253
|
+
idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) + pointsApi(false) + textureApi(0) +
|
|
253
254
|
"\n// ── user effect ──\n" +
|
|
254
255
|
src.wgsl +
|
|
255
256
|
/* wgsl */ `
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** How many pictures one effect can read. */
|
|
2
|
+
export declare const MAX_EFFECT_TEXTURES = 4;
|
|
3
|
+
/**
|
|
4
|
+
* rzTexture / rzTextureLod. `count` is the effect's `#textures` (0 = stubs);
|
|
5
|
+
* the textures take bindings first..first+count-1 in `group` and the one
|
|
6
|
+
* sampler `sampler`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function textureApi(count: number, group?: number, first?: number, sampler?: number): string;
|
|
9
|
+
//# sourceMappingURL=texture-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"texture-api.d.ts","sourceRoot":"","sources":["../../src/shaders/texture-api.ts"],"names":[],"mappings":"AAkBA,6CAA6C;AAC7C,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAI,EAAE,KAAK,SAAI,EAAE,OAAO,SAAI,GAAG,MAAM,CA6BnF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Pictures an effect is handed by its host — `#textures N`.
|
|
2
|
+
//
|
|
3
|
+
// #textures 2
|
|
4
|
+
//
|
|
5
|
+
// gives the PARTICLE SHADING rzTexture(i, uv) and rzTextureLod(i, uv, lod) over
|
|
6
|
+
// N images the host passes with the install (setEffects' `textures`, slot i =
|
|
7
|
+
// picture i). uv (0,0) is the image's top-left row, the style graph's
|
|
8
|
+
// tex_image convention, and it repeats. A slot the host left empty reads white.
|
|
9
|
+
//
|
|
10
|
+
// WHY: a game's splash is a picture on a card, and an effect that may only draw
|
|
11
|
+
// procedurally can only ever guess at it — shape, coverage, veins, each a guess
|
|
12
|
+
// to be corrected. A converted stage carries the game's own textures, and this
|
|
13
|
+
// is how its particles draw them.
|
|
14
|
+
//
|
|
15
|
+
// Fragment-only, because textureSample is: the compute stage and every other
|
|
16
|
+
// module the author's file is spliced into get STUBS returning zero, so one
|
|
17
|
+
// effect file compiles in every mount it has.
|
|
18
|
+
/** How many pictures one effect can read. */
|
|
19
|
+
export const MAX_EFFECT_TEXTURES = 4;
|
|
20
|
+
/**
|
|
21
|
+
* rzTexture / rzTextureLod. `count` is the effect's `#textures` (0 = stubs);
|
|
22
|
+
* the textures take bindings first..first+count-1 in `group` and the one
|
|
23
|
+
* sampler `sampler`.
|
|
24
|
+
*/
|
|
25
|
+
export function textureApi(count, group = 0, first = 0, sampler = 0) {
|
|
26
|
+
if (count <= 0) {
|
|
27
|
+
return /* wgsl */ `
|
|
28
|
+
fn rzTexture(i: u32, uv: vec2f) -> vec4f { return vec4f(0.0); }
|
|
29
|
+
fn rzTextureLod(i: u32, uv: vec2f, lod: f32) -> vec4f { return vec4f(0.0); }
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
const n = Math.min(count, MAX_EFFECT_TEXTURES);
|
|
33
|
+
let decl = `@group(${group}) @binding(${sampler}) var _rzTexSampler: sampler;\n`;
|
|
34
|
+
for (let k = 0; k < n; k++)
|
|
35
|
+
decl += `@group(${group}) @binding(${first + k}) var _rzTex${k}: texture_2d<f32>;\n`;
|
|
36
|
+
// Every slot is sampled unconditionally and the one asked for is selected:
|
|
37
|
+
// textureSample must be reached in uniform control flow, and a switch on a
|
|
38
|
+
// per-fragment index is not.
|
|
39
|
+
const pick = (call) => {
|
|
40
|
+
let body = " var c = vec4f(0.0);\n";
|
|
41
|
+
for (let k = 0; k < n; k++)
|
|
42
|
+
body += ` let s${k} = ${call(k)};\n if (i == ${k}u) { c = s${k}; }\n`;
|
|
43
|
+
return body + " return c;\n";
|
|
44
|
+
};
|
|
45
|
+
return (decl +
|
|
46
|
+
/* wgsl */ `
|
|
47
|
+
/** Picture i of the effect's #textures, at uv ((0,0) top-left, repeating). */
|
|
48
|
+
fn rzTexture(i: u32, uv: vec2f) -> vec4f {
|
|
49
|
+
${pick((k) => `textureSample(_rzTex${k}, _rzTexSampler, uv)`)}}
|
|
50
|
+
/** The same at an explicit mip level — usable where derivatives are not. */
|
|
51
|
+
fn rzTextureLod(i: u32, uv: vec2f, lod: f32) -> vec4f {
|
|
52
|
+
${pick((k) => `textureSampleLevel(_rzTex${k}, _rzTexSampler, uv, lod)`)}}
|
|
53
|
+
`);
|
|
54
|
+
}
|
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -118,6 +118,8 @@ import {
|
|
|
118
118
|
buildParticleRenderShader,
|
|
119
119
|
PARTICLE_LIGHT_BINDING,
|
|
120
120
|
PARTICLE_POINTS_BINDING,
|
|
121
|
+
PARTICLE_TEXTURE_BINDING,
|
|
122
|
+
PARTICLE_TEXTURE_SAMPLER_BINDING,
|
|
121
123
|
particleEntryPoints,
|
|
122
124
|
PARTICLE_INDIRECT_BINDING,
|
|
123
125
|
PARTICLE_INDIRECT_BYTES,
|
|
@@ -183,10 +185,17 @@ export type MaterialPresetMap = Partial<Record<MaterialPreset, string[]>>
|
|
|
183
185
|
|
|
184
186
|
// Substring hints mapping common PMX material names (JP/CN/EN) to a style category,
|
|
185
187
|
// tried when a material isn't in the caller's explicit override map. Ordered: more
|
|
186
|
-
// specific families first (靴下 must hit
|
|
188
|
+
// specific families first (靴下 must hit socks before 靴 hits cloth). A material
|
|
187
189
|
// matching nothing resolves to null — it stays ungrouped (neutral default).
|
|
190
|
+
//
|
|
191
|
+
// SOCKS ARE CLOTH, STOCKINGS ARE SHEER. The game draws a white sock as opaque
|
|
192
|
+
// fabric on the ordinary cloth ramp; only a sheer stocking gets the stockings
|
|
193
|
+
// look, whose see-through blend made white socks glow flat or wash out. So the
|
|
194
|
+
// sock words go to rough cloth ahead of the stocking words — 袜子 before the
|
|
195
|
+
// bare 袜 that 丝袜 (silk stockings) still reaches.
|
|
188
196
|
const PRESET_NAME_HINTS: Array<[MaterialPreset, string[]]> = [
|
|
189
|
-
["
|
|
197
|
+
["cloth_rough", ["靴下", "ソックス", "ニーソ", "袜子", "短袜", "棉袜", "socks", "sock"]],
|
|
198
|
+
["stockings", ["タイツ", "ストッキング", "袜", "stocking", "tights", "pantyhose"]],
|
|
190
199
|
[
|
|
191
200
|
"eye",
|
|
192
201
|
["白目", "目影", "二重", "睫", "まつげ", "まゆ", "眉", "目", "瞳", "眼", "eye", "iris", "pupil", "lash", "brow"],
|
|
@@ -481,6 +490,17 @@ export type EffectParamValue = number | { x: number; y: number; z: number }
|
|
|
481
490
|
/** A vector by shape rather than by class — Vec3 satisfies it, and so does a
|
|
482
491
|
* JSON object out of a scene document or a literal typed into a console. */
|
|
483
492
|
export type XYZ = { x: number; y: number; z: number }
|
|
493
|
+
/**
|
|
494
|
+
* One picture an effect's `#textures` reads, as the host holds it — slot i of
|
|
495
|
+
* the install's `textures` is rzTexture(i, uv). `srgb` for a colour picture
|
|
496
|
+
* (decoded to linear on sample), false for data. Null, or a slot past the end,
|
|
497
|
+
* reads white. The engine never fetches: images arrive decoded, as models do.
|
|
498
|
+
*/
|
|
499
|
+
export type EffectTextureInput = {
|
|
500
|
+
source: ImageBitmap | HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageData
|
|
501
|
+
srgb: boolean
|
|
502
|
+
} | null
|
|
503
|
+
|
|
484
504
|
export type EffectResult = {
|
|
485
505
|
ok: boolean
|
|
486
506
|
/** Compile/validation errors, line:col relative to the USER's WGSL. Also
|
|
@@ -1506,6 +1526,9 @@ interface EffectParticles {
|
|
|
1506
1526
|
* indirect arguments it writes them to. Null when the effect declared none —
|
|
1507
1527
|
* the whole pool is then stepped and drawn. */
|
|
1508
1528
|
live: { pipeline: GPUComputePipeline; indirect: GPUBuffer; binds: [GPUBindGroup, GPUBindGroup] } | null
|
|
1529
|
+
/** `#textures`: the pictures the host handed over, uploaded for this effect
|
|
1530
|
+
* alone and destroyed with it. Empty when it declared none. */
|
|
1531
|
+
textures: GPUTexture[]
|
|
1509
1532
|
}
|
|
1510
1533
|
|
|
1511
1534
|
/**
|
|
@@ -4773,6 +4796,8 @@ export class Engine {
|
|
|
4773
4796
|
anchors: { bone: string; trail: boolean }[],
|
|
4774
4797
|
/** Its row of that table: local slot → scene slot. */
|
|
4775
4798
|
alias: number[],
|
|
4799
|
+
/** The pictures for `#textures`, as the host passed them. */
|
|
4800
|
+
textures?: EffectTextureInput[],
|
|
4776
4801
|
): Promise<{ ok: true; instance: EffectInstance; warnings: string[] } | EffectResult> {
|
|
4777
4802
|
const noMounts = { background: false, foreground: false }
|
|
4778
4803
|
if (!this.device) return { ok: false, diagnostics: ["setEffect requires init() to have run"], mounts: noMounts, params: [], duration: 0, readsCast: false }
|
|
@@ -5093,6 +5118,7 @@ export class Engine {
|
|
|
5093
5118
|
particles?.uniform.destroy()
|
|
5094
5119
|
particles?.points?.buffer.destroy()
|
|
5095
5120
|
particles?.live?.indirect.destroy()
|
|
5121
|
+
for (const t of particles?.textures ?? []) t.destroy()
|
|
5096
5122
|
grid?.textures[0].destroy()
|
|
5097
5123
|
grid?.textures[1].destroy()
|
|
5098
5124
|
grid?.uniform.destroy()
|
|
@@ -5108,7 +5134,7 @@ export class Engine {
|
|
|
5108
5134
|
grid = built.state
|
|
5109
5135
|
}
|
|
5110
5136
|
if (wantsParticles) {
|
|
5111
|
-
const built = await this.buildParticles(wgsl, d, anchors, alias, paramsFor, grid)
|
|
5137
|
+
const built = await this.buildParticles(wgsl, d, anchors, alias, paramsFor, grid, textures)
|
|
5112
5138
|
if (!built.ok) return abandon(built.diagnostics)
|
|
5113
5139
|
particles = built.state
|
|
5114
5140
|
}
|
|
@@ -5269,6 +5295,9 @@ export class Engine {
|
|
|
5269
5295
|
* aimed at one dancer should not spend its first frame on all four,
|
|
5270
5296
|
* which on a ribbon or a sigil reads as a flash. */
|
|
5271
5297
|
subjects?: readonly string[] | null
|
|
5298
|
+
/** `#textures`: slot i is the effect's rzTexture(i, uv). See
|
|
5299
|
+
* EffectTextureInput. Ignored by an effect that declares none. */
|
|
5300
|
+
textures?: EffectTextureInput[]
|
|
5272
5301
|
}[]
|
|
5273
5302
|
| null,
|
|
5274
5303
|
): Promise<EffectResult[]> {
|
|
@@ -5322,6 +5351,7 @@ export class Engine {
|
|
|
5322
5351
|
requested[i].params,
|
|
5323
5352
|
perEffectAnchors[i],
|
|
5324
5353
|
table.alias[i],
|
|
5354
|
+
requested[i].textures,
|
|
5325
5355
|
)
|
|
5326
5356
|
if (!("instance" in built)) {
|
|
5327
5357
|
// Contained: this one is out, the others carry on.
|
|
@@ -5437,13 +5467,13 @@ export class Engine {
|
|
|
5437
5467
|
* Install ONE effect — the singleton API, kept because most scenes are one
|
|
5438
5468
|
* effect and every existing caller uses it. A one-element setEffects.
|
|
5439
5469
|
*/
|
|
5440
|
-
async setEffect(wgsl: string | null, params?: Record<string, EffectParamValue
|
|
5470
|
+
async setEffect(wgsl: string | null, params?: Record<string, EffectParamValue>, textures?: EffectTextureInput[]): Promise<EffectResult> {
|
|
5441
5471
|
const noMounts = { background: false, foreground: false }
|
|
5442
5472
|
if (wgsl === null) {
|
|
5443
5473
|
await this.setEffects(null)
|
|
5444
5474
|
return { ok: true, diagnostics: [], mounts: noMounts, params: [], duration: 0, readsCast: false }
|
|
5445
5475
|
}
|
|
5446
|
-
const [result] = await this.setEffects([{ wgsl, params }])
|
|
5476
|
+
const [result] = await this.setEffects([{ wgsl, params, textures }])
|
|
5447
5477
|
return result ?? { ok: false, diagnostics: ["effect failed to install"], mounts: noMounts, params: [], duration: 0, readsCast: false }
|
|
5448
5478
|
}
|
|
5449
5479
|
|
|
@@ -5464,6 +5494,8 @@ export class Engine {
|
|
|
5464
5494
|
/** This effect's own grid, already built, or null when it declared none.
|
|
5465
5495
|
* Read-only here: the pool samples it, the grid pass owns it. */
|
|
5466
5496
|
grid: EffectGrid | null,
|
|
5497
|
+
/** `#textures`: the host's pictures, slot for slot. */
|
|
5498
|
+
textureInputs?: EffectTextureInput[],
|
|
5467
5499
|
): Promise<{ ok: true; state: EffectParticles } | { ok: false; diagnostics: string[] }> {
|
|
5468
5500
|
// No pragma means "some": an author who wrote the trio clearly wants
|
|
5469
5501
|
// particles, and failing over a missing comment would be pedantry.
|
|
@@ -5477,8 +5509,15 @@ export class Engine {
|
|
|
5477
5509
|
gridSize: grid?.size ?? 0,
|
|
5478
5510
|
cover: particleEntryPoints(wgsl).cover,
|
|
5479
5511
|
live: particleEntryPoints(wgsl).count,
|
|
5512
|
+
textures: d.textures,
|
|
5480
5513
|
}
|
|
5481
5514
|
const prepass = src.blend === "cutout" && src.cover
|
|
5515
|
+
// `#textures`: uploaded now, each with its mips, for this effect alone. A
|
|
5516
|
+
// slot the host left empty is null here and binds the white fallback.
|
|
5517
|
+
const textures = this.uploadEffectTextures(textureInputs, d.textures)
|
|
5518
|
+
const dropTextures = () => {
|
|
5519
|
+
for (const t of textures) t?.destroy()
|
|
5520
|
+
}
|
|
5482
5521
|
const points = d.points
|
|
5483
5522
|
? {
|
|
5484
5523
|
prefix: d.points,
|
|
@@ -5518,11 +5557,13 @@ export class Engine {
|
|
|
5518
5557
|
const computeModule = await compile(buildParticleComputeShader(src, cast), "particle compute")
|
|
5519
5558
|
if (Array.isArray(computeModule)) {
|
|
5520
5559
|
points?.buffer.destroy()
|
|
5560
|
+
dropTextures()
|
|
5521
5561
|
return { ok: false, diagnostics: computeModule }
|
|
5522
5562
|
}
|
|
5523
5563
|
const renderModule = await compile(buildParticleRenderShader(src, cast), "particle render")
|
|
5524
5564
|
if (Array.isArray(renderModule)) {
|
|
5525
5565
|
points?.buffer.destroy()
|
|
5566
|
+
dropTextures()
|
|
5526
5567
|
return { ok: false, diagnostics: renderModule }
|
|
5527
5568
|
}
|
|
5528
5569
|
|
|
@@ -5602,6 +5643,18 @@ export class Engine {
|
|
|
5602
5643
|
...(withIndirect
|
|
5603
5644
|
? [{ binding: PARTICLE_INDIRECT_BINDING, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" as const } }]
|
|
5604
5645
|
: []),
|
|
5646
|
+
// `#textures`, the shading stage only and only when declared — an
|
|
5647
|
+
// effect without them keeps exactly the layout it always had.
|
|
5648
|
+
...(shadow && textures.length
|
|
5649
|
+
? [
|
|
5650
|
+
...textures.map((_, k) => ({
|
|
5651
|
+
binding: PARTICLE_TEXTURE_BINDING + k,
|
|
5652
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
5653
|
+
texture: { sampleType: "float" as const },
|
|
5654
|
+
})),
|
|
5655
|
+
{ binding: PARTICLE_TEXTURE_SAMPLER_BINDING, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" as const } },
|
|
5656
|
+
]
|
|
5657
|
+
: []),
|
|
5605
5658
|
],
|
|
5606
5659
|
})
|
|
5607
5660
|
/** One per GRID PARITY, for the reason rebuildFieldBindGroup gives: the grid
|
|
@@ -5634,6 +5687,15 @@ export class Engine {
|
|
|
5634
5687
|
]
|
|
5635
5688
|
: []),
|
|
5636
5689
|
...(withIndirect && indirect ? [{ binding: PARTICLE_INDIRECT_BINDING, resource: { buffer: indirect } }] : []),
|
|
5690
|
+
...(shadow && textures.length
|
|
5691
|
+
? [
|
|
5692
|
+
...textures.map((t, k) => ({
|
|
5693
|
+
binding: PARTICLE_TEXTURE_BINDING + k,
|
|
5694
|
+
resource: (t ?? this.fallbackMaterialTexture).createView(),
|
|
5695
|
+
})),
|
|
5696
|
+
{ binding: PARTICLE_TEXTURE_SAMPLER_BINDING, resource: this.effectTextureSampler() },
|
|
5697
|
+
]
|
|
5698
|
+
: []),
|
|
5637
5699
|
],
|
|
5638
5700
|
}),
|
|
5639
5701
|
) as [GPUBindGroup, GPUBindGroup]
|
|
@@ -5716,6 +5778,7 @@ export class Engine {
|
|
|
5716
5778
|
uniform.destroy()
|
|
5717
5779
|
points?.buffer.destroy()
|
|
5718
5780
|
indirect?.destroy()
|
|
5781
|
+
dropTextures()
|
|
5719
5782
|
return { ok: false, diagnostics: [scoped.message] }
|
|
5720
5783
|
}
|
|
5721
5784
|
return {
|
|
@@ -5745,6 +5808,7 @@ export class Engine {
|
|
|
5745
5808
|
}),
|
|
5746
5809
|
points,
|
|
5747
5810
|
live,
|
|
5811
|
+
textures: textures.filter((t): t is GPUTexture => t !== null),
|
|
5748
5812
|
},
|
|
5749
5813
|
}
|
|
5750
5814
|
} catch (e) {
|
|
@@ -5753,6 +5817,7 @@ export class Engine {
|
|
|
5753
5817
|
uniform.destroy()
|
|
5754
5818
|
points?.buffer.destroy()
|
|
5755
5819
|
indirect?.destroy()
|
|
5820
|
+
dropTextures()
|
|
5756
5821
|
return { ok: false, diagnostics: [e instanceof Error ? e.message : String(e)] }
|
|
5757
5822
|
}
|
|
5758
5823
|
}
|
|
@@ -6521,6 +6586,7 @@ export class Engine {
|
|
|
6521
6586
|
e.particles?.uniform.destroy()
|
|
6522
6587
|
e.particles?.points?.buffer.destroy()
|
|
6523
6588
|
e.particles?.live?.indirect.destroy()
|
|
6589
|
+
for (const t of e.particles?.textures ?? []) t.destroy()
|
|
6524
6590
|
e.particles = null
|
|
6525
6591
|
}
|
|
6526
6592
|
}
|
|
@@ -12389,6 +12455,54 @@ export class Engine {
|
|
|
12389
12455
|
for (const set of Object.values(install.imagesByMaterial ?? {})) for (const tex of set) tex?.destroy()
|
|
12390
12456
|
}
|
|
12391
12457
|
|
|
12458
|
+
/**
|
|
12459
|
+
* An effect's `#textures`, uploaded: `count` slots, each with a full mip chain
|
|
12460
|
+
* (a splash card seen from across a stage is a few pixels, and unmipped it
|
|
12461
|
+
* sparkles). Slot k is null where the host gave nothing — the bind group then
|
|
12462
|
+
* reads the white fallback.
|
|
12463
|
+
*/
|
|
12464
|
+
private uploadEffectTextures(inputs: EffectTextureInput[] | undefined, count: number): (GPUTexture | null)[] {
|
|
12465
|
+
const out: (GPUTexture | null)[] = []
|
|
12466
|
+
for (let k = 0; k < count; k++) {
|
|
12467
|
+
const entry = inputs?.[k]
|
|
12468
|
+
if (!entry) {
|
|
12469
|
+
out.push(null)
|
|
12470
|
+
continue
|
|
12471
|
+
}
|
|
12472
|
+
const src = entry.source
|
|
12473
|
+
const width = Math.max(1, "naturalWidth" in src ? src.naturalWidth : src.width)
|
|
12474
|
+
const height = Math.max(1, "naturalHeight" in src ? src.naturalHeight : src.height)
|
|
12475
|
+
const mipLevelCount = Math.floor(Math.log2(Math.max(width, height))) + 1
|
|
12476
|
+
const tex = this.device.createTexture({
|
|
12477
|
+
label: `effect texture ${k}`,
|
|
12478
|
+
size: [width, height],
|
|
12479
|
+
mipLevelCount,
|
|
12480
|
+
format: entry.srgb ? "rgba8unorm-srgb" : "rgba8unorm",
|
|
12481
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
12482
|
+
})
|
|
12483
|
+
this.device.queue.copyExternalImageToTexture({ source: src }, { texture: tex }, [width, height])
|
|
12484
|
+
if (mipLevelCount > 1) this.generateMipmaps(tex, mipLevelCount)
|
|
12485
|
+
out.push(tex)
|
|
12486
|
+
}
|
|
12487
|
+
return out
|
|
12488
|
+
}
|
|
12489
|
+
|
|
12490
|
+
private effectTextureSamplerCache: GPUSampler | null = null
|
|
12491
|
+
/** Linear, mipmapped, repeating — one for every effect's pictures. */
|
|
12492
|
+
private effectTextureSampler(): GPUSampler {
|
|
12493
|
+
if (!this.effectTextureSamplerCache) {
|
|
12494
|
+
this.effectTextureSamplerCache = this.device.createSampler({
|
|
12495
|
+
label: "effect texture sampler",
|
|
12496
|
+
magFilter: "linear",
|
|
12497
|
+
minFilter: "linear",
|
|
12498
|
+
mipmapFilter: "linear",
|
|
12499
|
+
addressModeU: "repeat",
|
|
12500
|
+
addressModeV: "repeat",
|
|
12501
|
+
})
|
|
12502
|
+
}
|
|
12503
|
+
return this.effectTextureSamplerCache
|
|
12504
|
+
}
|
|
12505
|
+
|
|
12392
12506
|
/** Upload a group's image maps. Sources are decoded images the host already
|
|
12393
12507
|
* holds; the engine never fetches, matching how models and motions arrive. */
|
|
12394
12508
|
private uploadGroupImages(group: StyleGroup, slots = group.images): (GPUTexture | null)[] | undefined {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// M_Rough_Cloth as a ShaderGraph — port of shaders/materials/cloth_rough.ts.
|
|
2
2
|
// NPR graph identical to M_Smooth_Cloth, but the noise bump subtree IS live on
|
|
3
|
-
// Principled.Normal (weave bump in rest space) and
|
|
3
|
+
// Principled.Normal (weave bump in rest space), and it is MATTE where smooth cloth
|
|
4
|
+
// shines: roughness 1, specular 0.25, and a wider toon band (0.20-0.45) so the
|
|
5
|
+
// light rolls off cotton instead of cutting. Smooth keeps 0.5 / 0.8 / 0.25-0.35.
|
|
4
6
|
// The tex_noise node hits the detail=2 peephole → tex_noise_d2.
|
|
5
7
|
|
|
6
8
|
import type { ShaderGraph } from "../schema"
|
|
@@ -19,7 +21,7 @@ export const CLOTH_ROUGH_GRAPH: ShaderGraph = {
|
|
|
19
21
|
{ id: "tex_base", type: "mix/multiply", inputs: { fac: 1.0 } },
|
|
20
22
|
{ id: "geo", type: "geometry" },
|
|
21
23
|
{ id: "str", type: "shader_to_rgb_diffuse" },
|
|
22
|
-
{ id: "ramp_008", type: "ramp_cardinal", inputs: { pos0: 0.
|
|
24
|
+
{ id: "ramp_008", type: "ramp_cardinal", inputs: { pos0: 0.2, pos1: 0.45 } },
|
|
23
25
|
{ id: "mix04_fac", type: "math/multiply", inputs: { b: 0.5 } },
|
|
24
26
|
{ id: "dark_tex", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 0.19999998807907104, fac: 1.0 } },
|
|
25
27
|
{ id: "mix_004", type: "mix/blend" },
|
|
@@ -36,7 +38,7 @@ export const CLOTH_ROUGH_GRAPH: ShaderGraph = {
|
|
|
36
38
|
{
|
|
37
39
|
id: "principled",
|
|
38
40
|
type: "principled",
|
|
39
|
-
inputs: { metallic: 0.0, specular_ior_level: 0.
|
|
41
|
+
inputs: { metallic: 0.0, specular_ior_level: 0.25, roughness: 1.0, spec_clamp: 10.0, sheen_weight: 0.0, sheen_tint: 0.0 },
|
|
40
42
|
},
|
|
41
43
|
{ id: "mix_shader_001", type: "mix_shader", inputs: { fac: 0.8999999761581421 } },
|
|
42
44
|
],
|
package/src/graph/schema.ts
CHANGED
|
@@ -74,5 +74,7 @@ export type Diagnostic = {
|
|
|
74
74
|
message: string
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
// A sanity bound, not a GPU one: a graph compiles to one fragment function and
|
|
78
|
+
// a longer one is only more WGSL. The game ports (X348's sea and water) run 60+.
|
|
79
|
+
export const MAX_NODES = 128
|
|
78
80
|
export const MAX_PARAMS = 16
|
|
@@ -60,6 +60,16 @@ export type EffectDirectives = {
|
|
|
60
60
|
/** `#points <prefix>`: every bone on every model whose name starts with it,
|
|
61
61
|
* as rzPoint(i) in the particle stages. Null when the file declares none. */
|
|
62
62
|
points: string | null
|
|
63
|
+
/**
|
|
64
|
+
* `#textures N` (1-4): the particle shading reads N pictures the HOST hands
|
|
65
|
+
* over with the install (setEffects' `textures`), as rzTexture(i, uv) — uv
|
|
66
|
+
* (0,0) at the image's top-left, repeating. A game's own splash or spray is a
|
|
67
|
+
* picture on a card, and drawn procedurally it can only ever be a guess at
|
|
68
|
+
* it; this is how a converted stage's particles carry the real one. The
|
|
69
|
+
* other modules an effect is spliced into get the names as stubs returning
|
|
70
|
+
* zero. 0 when undeclared.
|
|
71
|
+
*/
|
|
72
|
+
textures: number
|
|
63
73
|
bloom: boolean
|
|
64
74
|
/** This effect takes the cast apart — the host reads the timing. */
|
|
65
75
|
dissolve: boolean
|
|
@@ -124,6 +134,7 @@ const SPEC = {
|
|
|
124
134
|
lights: 1,
|
|
125
135
|
grid: 1,
|
|
126
136
|
points: 1,
|
|
137
|
+
textures: 1,
|
|
127
138
|
bloom: 0,
|
|
128
139
|
dissolve: 0,
|
|
129
140
|
mirror: 0,
|
|
@@ -183,6 +194,7 @@ export function parseDirectives(wgsl: string): DirectiveResult {
|
|
|
183
194
|
lights: 0,
|
|
184
195
|
grid: 0,
|
|
185
196
|
points: null,
|
|
197
|
+
textures: 0,
|
|
186
198
|
bloom: false,
|
|
187
199
|
dissolve: false,
|
|
188
200
|
ground: null,
|
|
@@ -304,6 +316,15 @@ export function parseDirectives(wgsl: string): DirectiveResult {
|
|
|
304
316
|
case "points":
|
|
305
317
|
d.points = args[0]
|
|
306
318
|
return
|
|
319
|
+
case "textures": {
|
|
320
|
+
const n = num(args[0])
|
|
321
|
+
if (n === null || !Number.isInteger(n) || n < 1 || n > 4) {
|
|
322
|
+
errors.push(`${at}: #textures takes a count from 1 to 4`)
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
d.textures = n
|
|
326
|
+
return
|
|
327
|
+
}
|
|
307
328
|
case "mirror":
|
|
308
329
|
d.mirror = true
|
|
309
330
|
return
|
package/src/shaders/lights.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { clockApi, trailSlotsApi, viewportApi } from "./passes/hosted-api"
|
|
|
49
49
|
import { idApi } from "./id-api"
|
|
50
50
|
import { sceneLightApi, worldAmbientWgsl } from "./scene-light-api"
|
|
51
51
|
import { pointsApi } from "./points-api"
|
|
52
|
+
import { textureApi } from "./texture-api"
|
|
52
53
|
|
|
53
54
|
/** Words before the first record: the counts, the grid's placement and the
|
|
54
55
|
* outside mask, four vec4s — see the layout above. */
|
|
@@ -179,7 +180,7 @@ ${viewportApi("viewU[6].w")}
|
|
|
179
180
|
${trailSlotsApi(cast.trailCount)}
|
|
180
181
|
// The id accessors, stubbed: this module cannot read an attachment the
|
|
181
182
|
// scene pass writes. See id-api.ts — the author's whole file compiles here.
|
|
182
|
-
${idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) + pointsApi(false)}
|
|
183
|
+
${idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) + pointsApi(false) + textureApi(0)}
|
|
183
184
|
// The dials the author declared, if any. A lamp is exactly the thing someone
|
|
184
185
|
// retunes — its colour and its reach — so an emitter reads params like every
|
|
185
186
|
// other mount rather than being the one place a #param resolves to nothing.
|
|
@@ -8,6 +8,7 @@ import { audioApi } from "../audio-api"
|
|
|
8
8
|
import { idApi } from "../id-api"
|
|
9
9
|
import { sceneLightApi } from "../scene-light-api"
|
|
10
10
|
import { pointsApi } from "../points-api"
|
|
11
|
+
import { textureApi } from "../texture-api"
|
|
11
12
|
import { castDistanceApi, CAST_FIELD_DIV } from "./cast-distance"
|
|
12
13
|
import { lyricsApi, lyricsTextApi } from "../lyrics-api"
|
|
13
14
|
import { midiApi } from "../midi-api"
|
|
@@ -758,6 +759,7 @@ export function buildFieldShader(effect: CompositeEffectSource): string {
|
|
|
758
759
|
idApi(effect.ids === true, 0, 23) +
|
|
759
760
|
sceneLightApi(false, 0, 0) +
|
|
760
761
|
pointsApi(false) +
|
|
762
|
+
textureApi(0) +
|
|
761
763
|
// Distance to the cast, in SCREEN pixels: the field is half-res, so a field
|
|
762
764
|
// texel is CAST_FIELD_DIV of them and the accessor scales on the way out.
|
|
763
765
|
// An author writes the width they mean and never learns how it is built.
|
|
@@ -10,6 +10,7 @@ import { clockApi, trailSlotsApi, viewportApi } from "./hosted-api"
|
|
|
10
10
|
import { idApi } from "../id-api"
|
|
11
11
|
import { sceneLightApi } from "../scene-light-api"
|
|
12
12
|
import { pointsApi } from "../points-api"
|
|
13
|
+
import { textureApi } from "../texture-api"
|
|
13
14
|
|
|
14
15
|
// A persistent grid an effect can step and read: the one thing an effect could
|
|
15
16
|
// not have before, which is MEMORY.
|
|
@@ -172,6 +173,7 @@ fn rzGrid(uv: vec2f) -> vec4f { return rzGridPrev(uv); }
|
|
|
172
173
|
idApi(false, 0, 0) +
|
|
173
174
|
sceneLightApi(false, 0, 0) +
|
|
174
175
|
pointsApi(false) +
|
|
176
|
+
textureApi(0) +
|
|
175
177
|
// Inlined rather than imported from cast-distance, and NOT for tidiness.
|
|
176
178
|
//
|
|
177
179
|
// This module and composite.ts import each other — grid takes
|