reze-engine 0.41.0 → 0.41.2

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.
Files changed (48) hide show
  1. package/README.md +4 -2
  2. package/dist/engine.d.ts +31 -3
  3. package/dist/engine.d.ts.map +1 -1
  4. package/dist/engine.js +190 -18
  5. package/dist/graph/presets/body.js +2 -2
  6. package/dist/graph/presets/cloth_rough.js +2 -2
  7. package/dist/graph/presets/cloth_smooth.js +2 -2
  8. package/dist/graph/presets/default.js +2 -2
  9. package/dist/graph/presets/eye.js +2 -2
  10. package/dist/graph/presets/face.js +2 -2
  11. package/dist/graph/presets/hair.js +2 -2
  12. package/dist/graph/presets/metal.js +2 -2
  13. package/dist/graph/presets/stockings.js +3 -3
  14. package/dist/graph/registry.d.ts.map +1 -1
  15. package/dist/graph/registry.js +426 -10
  16. package/dist/graph/style-group.d.ts +46 -0
  17. package/dist/graph/style-group.d.ts.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/shaders/agx-lut.d.ts +11 -0
  21. package/dist/shaders/agx-lut.d.ts.map +1 -0
  22. package/dist/shaders/agx-lut.js +29 -0
  23. package/dist/shaders/materials/common.d.ts +1 -1
  24. package/dist/shaders/materials/common.d.ts.map +1 -1
  25. package/dist/shaders/materials/common.js +10 -0
  26. package/dist/shaders/materials/nodes.d.ts +1 -1
  27. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  28. package/dist/shaders/materials/nodes.js +957 -576
  29. package/dist/shaders/passes/composite.d.ts.map +1 -1
  30. package/dist/shaders/passes/composite.js +76 -4
  31. package/package.json +1 -1
  32. package/src/engine.ts +211 -17
  33. package/src/graph/presets/body.ts +2 -2
  34. package/src/graph/presets/cloth_rough.ts +2 -2
  35. package/src/graph/presets/cloth_smooth.ts +2 -2
  36. package/src/graph/presets/default.ts +2 -2
  37. package/src/graph/presets/eye.ts +2 -2
  38. package/src/graph/presets/face.ts +2 -2
  39. package/src/graph/presets/hair.ts +2 -2
  40. package/src/graph/presets/metal.ts +2 -2
  41. package/src/graph/presets/stockings.ts +3 -3
  42. package/src/graph/registry.ts +439 -10
  43. package/src/graph/style-group.ts +44 -0
  44. package/src/index.ts +2 -0
  45. package/src/shaders/agx-lut.ts +30 -0
  46. package/src/shaders/materials/common.ts +10 -0
  47. package/src/shaders/materials/nodes.ts +962 -581
  48. package/src/shaders/passes/composite.ts +76 -4
@@ -60,7 +60,7 @@ override APPLY_GAMMA: bool = true;
60
60
  @group(0) @binding(0) var hdrTex: texture_2d<f32>;
61
61
  @group(0) @binding(1) var bloomTex: texture_2d<f32>; // bloomUpTexture mip 0 (full pyramid top)
62
62
  @group(0) @binding(2) var bloomSamp: sampler;
63
- @group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 11>;
63
+ @group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 15>;
64
64
  // Aux mask/alpha texture. .r = bloom mask (unused here; bloom blit uses it).
65
65
  // .g = accumulated canvas alpha (what hdr.a carried before the HDR format
66
66
  // became rg11b10ufloat). We unpremultiply HDR by this alpha for tonemap, then
@@ -84,7 +84,7 @@ override APPLY_GAMMA: bool = true;
84
84
  // the compiled variant IS the flag.
85
85
  // viewU[3] = (camera right, tanHalfFov·aspect); viewU[4] = (camera up, tanHalfFov);
86
86
  // viewU[5] = (camera forward, _) — refreshed per frame while skybox/effect active.
87
- // viewU[6] = (time seconds, _, canvas width, canvas height).
87
+ // viewU[6] = (time seconds, view transform id, canvas width, canvas height).
88
88
  // viewU[7] = (grade offset.rgb, contrast); viewU[8] = (grade power.rgb, saturation);
89
89
  // viewU[9] = (grade slope.rgb, grade on/off) — see grade() below.
90
90
  // viewU[10] = (camera world position, _) — refreshed with the basis above.
@@ -104,6 +104,9 @@ override APPLY_GAMMA: bool = true;
104
104
  // track the camera radius. Cleared depth (1.0) inverts to the far
105
105
  // plane, so empty sky reads as maximally defocused background.
106
106
  @group(0) @binding(9) var<uniform> dofU: array<vec4<f32>, 3>;
107
+ // Blender's AgX, as the 57³ lookup it ships as rather than a reconstruction of
108
+ // it. Sampled in the log-encoded E-Gamut space the cube expects — see agxTransform.
109
+ @group(0) @binding(10) var agxLut: texture_3d<f32>;
107
110
 
108
111
  // Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).
109
112
  const FILMIC_LUT_W: f32 = 256.0;
@@ -147,12 +150,82 @@ fn filmic(x: f32) -> f32 {
147
150
  return textureSampleLevel(filmicLut, bloomSamp, vec2f(u, 0.5), 0.0).r;
148
151
  }
149
152
 
153
+ /** The sRGB display encoding — Blender's "Standard" view transform, which is what
154
+ * NPR work uses: no film curve at all, so the colours a graph computes are the
155
+ * colours that land. Not a 2.2 power law; sRGB has a linear toe. */
156
+ fn srgb_encode(x: f32) -> f32 {
157
+ let c = max(x, 0.0);
158
+ return select(1.055 * pow(c, 1.0 / 2.4) - 0.055, c * 12.92, c <= 0.0031308);
159
+ }
160
+
161
+ /**
162
+ * AgX, the chain config.ocio states for "AgX Base Rec.1886" on an sRGB display:
163
+ *
164
+ * scene-linear Rec.709 → 3x3 into FilmLight E-Gamut
165
+ * → log2 across 25 stops, [-12.47393, +12.5261] → 0..1
166
+ * → the 57³ cube
167
+ * → Rec.1886 (pure γ2.4) decoded, then re-encoded as sRGB for the swapchain
168
+ *
169
+ * The last step is not pedantry: Rec.1886 is a pure power law and sRGB has a
170
+ * linear toe, so skipping it lifts the darkest few code values.
171
+ *
172
+ * Blender interpolates the cube tetrahedrally and a GPU sampler is trilinear.
173
+ * The difference is small and confined to saturated gradients; if it ever shows,
174
+ * tetrahedral is a dozen lines here.
175
+ */
176
+ fn agxTransform(c: vec3f) -> vec3f {
177
+ let m = mat3x3<f32>(
178
+ vec3f(0.55937113, 0.07622071, 0.06552670),
179
+ vec3f(0.30478326, 0.78797183, 0.16454675),
180
+ vec3f(0.13584556, 0.13580748, 0.76992653),
181
+ );
182
+ let e = m * max(c, vec3f(0.0));
183
+ // The cube's domain: an unlit pixel would take log2 to -inf, so floor it at
184
+ // the bottom stop rather than sampling outside the LUT.
185
+ let t = clamp((log2(max(e, vec3f(1e-10))) + vec3f(12.47393)) / 25.0, vec3f(0.0), vec3f(1.0));
186
+ // Half-texel inset, so the end stops are the cube's own values and not a blend
187
+ // with the clamp-to-edge border.
188
+ let n = 57.0;
189
+ let uvw = t * ((n - 1.0) / n) + (0.5 / n);
190
+ let formed = textureSampleLevel(agxLut, bloomSamp, uvw, 0.0).rgb;
191
+ let linear = pow(max(formed, vec3f(0.0)), vec3f(2.4));
192
+ return vec3f(srgb_encode(linear.r), srgb_encode(linear.g), srgb_encode(linear.b));
193
+ }
194
+
195
+ /** Which display transform, chosen per frame at viewU[6].y (0 filmic, 1 standard, 2 agx).
196
+ * A uniform branch rather than a pipeline variant: switching is rare, and both
197
+ * arms are cheap enough that specialising the pipeline would buy nothing. */
198
+ fn viewTransform(c: vec3f) -> vec3f {
199
+ let mode = viewU[6].y;
200
+ if (mode > 1.5) { return agxTransform(c); }
201
+ if (mode > 0.5) { return vec3f(srgb_encode(c.r), srgb_encode(c.g), srgb_encode(c.b)); }
202
+ return vec3f(filmic(c.r), filmic(c.g), filmic(c.b));
203
+ }
204
+
150
205
  /** Canvas size in pixels — for user effects (aspect correction). */
151
206
  fn bgResolution() -> vec2f { return viewU[6].zw; }
152
207
 
153
208
  /** The camera's world position. */
154
209
  fn bgCameraPos() -> vec3f { return viewU[10].xyz; }
155
210
 
211
+ /** How many characters are in the scene, up to four. */
212
+ fn bgSubjectCount() -> i32 { return i32(viewU[10].w); }
213
+
214
+ /**
215
+ * Where a character is standing, in world space.
216
+ *
217
+ * An effect that wants to RESPOND to the cast — ripples under the feet, a glow
218
+ * that follows someone, dust kicked up where they are — needs to know where
219
+ * they are, and the ray and the depth cannot tell it: they describe the pixel,
220
+ * not the scene. This is the model's root, which for a PMX is between the feet
221
+ * on the floor, so it is already the contact point a ripple wants.
222
+ *
223
+ * Clamped rather than bounds-checked: an effect looping past the count reads the
224
+ * last subject instead of sampling whatever follows the array, which is a wrong
225
+ * ripple rather than an undefined one.
226
+ */
227
+ fn bgSubjectPos(i: i32) -> vec3f { return viewU[11 + clamp(i, 0, 3)].xyz; }
228
+
156
229
  /** Where in the WORLD the scene drew this pixel — the depth handed to
157
230
  * foreground() turned into a place. Without it an effect can only think in
158
231
  * distances from the lens, which is no use to anything that belongs somewhere:
@@ -260,8 +333,7 @@ const COMPOSITE_BODY = /* wgsl */ `
260
333
  let sceneStraight = scenePm.rgb / max(sceneAlpha, 1e-6);
261
334
 
262
335
  let exposed = sceneStraight * exp2(viewU[0].x);
263
- let tm = vec3f(filmic(exposed.r), filmic(exposed.g), filmic(exposed.b));
264
- var disp = max(tm, vec3f(0.0));
336
+ var disp = max(viewTransform(exposed), vec3f(0.0));
265
337
  // Grade the SCENE only, before the display gamma. Deliberately not applied to
266
338
  // the background: it keeps a picked background color exactly as picked, and —
267
339
  // load-bearing — leaves green-screen mode's key color unshifted so chroma