reze-engine 0.55.8 → 0.55.10

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.
@@ -44,10 +44,41 @@
44
44
  * Clamp-to-edge is what stops a warped offset wrapping the far side of the
45
45
  * screen into the picture.
46
46
  */
47
- export function sceneTapApi(group: number, tex: number, samp: number): string {
47
+ export function sceneTapApi(
48
+ group: number,
49
+ tex: number,
50
+ samp: number,
51
+ layers: readonly [number, number, number, number],
52
+ ): string {
48
53
  return /* wgsl */ `
49
54
  @group(${group}) @binding(${tex}) var _rzSceneTex: texture_2d<f32>;
50
55
  @group(${group}) @binding(${samp}) var _rzSceneSamp: sampler;
56
+ // The field layers as the other effects left them — see rzSceneFrame. Bound to
57
+ // the pair the others drew into for a filter, and to a 1x1 transparent for
58
+ // everything else: an effect cannot sample the pair it is drawing into.
59
+ @group(${group}) @binding(${layers[0]}) var _rzLayerBgFull: texture_2d<f32>;
60
+ @group(${group}) @binding(${layers[1]}) var _rzLayerFgFull: texture_2d<f32>;
61
+ @group(${group}) @binding(${layers[2]}) var _rzLayerBgHalf: texture_2d<f32>;
62
+ @group(${group}) @binding(${layers[3]}) var _rzLayerFgHalf: texture_2d<f32>;
63
+
64
+ /** Premultiplied OVER, top in front — the composite's rzFieldMerge, restated so
65
+ * this file keeps importing nothing. A resolution boundary is a layer
66
+ * boundary: full res over half, exactly as the composite reads them. */
67
+ fn _rzLayerOver(top: vec4f, bot: vec4f) -> vec4f {
68
+ return vec4f(top.rgb + bot.rgb * (1.0 - top.a), top.a + bot.a * (1.0 - top.a));
69
+ }
70
+ fn _rzLayerBg(uv: vec2f) -> vec4f {
71
+ let p = _rzTapUv(uv);
72
+ return _rzLayerOver(
73
+ clamp(textureSampleLevel(_rzLayerBgFull, _rzSceneSamp, p, 0.0), vec4f(0.0), vec4f(1.0)),
74
+ clamp(textureSampleLevel(_rzLayerBgHalf, _rzSceneSamp, p, 0.0), vec4f(0.0), vec4f(1.0)));
75
+ }
76
+ fn _rzLayerFg(uv: vec2f) -> vec4f {
77
+ let p = _rzTapUv(uv);
78
+ return _rzLayerOver(
79
+ clamp(textureSampleLevel(_rzLayerFgFull, _rzSceneSamp, p, 0.0), vec4f(0.0), vec4f(1.0)),
80
+ clamp(textureSampleLevel(_rzLayerFgHalf, _rzSceneSamp, p, 0.0), vec4f(0.0), vec4f(1.0)));
81
+ }
51
82
 
52
83
  /** uv as the mount gives it (y=0 at the BOTTOM, the shadertoy convention the
53
84
  * composite documents) turned into a texture coordinate (y=0 at the top).
@@ -176,25 +207,40 @@ fn rzSceneDisplay(uv: vec2f) -> vec3f {
176
207
  }
177
208
 
178
209
  /**
179
- * The finished pixel: the scene over its background, exactly as the composite
180
- * would lay it down. One call, and the answer already carries the ground's
181
- * fade, its opacity, and the background showing through both.
182
- *
183
- * This is what a FILTER wants — anything re-rendering the whole frame on a tube
184
- * or a low-resolution screen. Reaching for rzSceneDisplay and a depth test
185
- * instead rebuilds this by hand and gets it wrong in one specific way every
186
- * time: coverage is not a yes or no, and treating it as one turns every soft
187
- * edge in the scene into a hard one.
188
- *
189
- * NOT INCLUDED: depth of field, background effects, and an equirect background
190
- * (a 360 dome reads as its average rather than its texture). Foregrounds compose
191
- * after this in any case.
210
+ * The frame as it stands: the scene over its background, and both layers of
211
+ * every other field effect, laid down in the order the composite would lay
212
+ * them. One call, and the answer already carries the ground's fade, its
213
+ * opacity, the background showing through both, and whatever the other
214
+ * effects drew — a waveform, a caption, a glow.
215
+ *
216
+ * CALLING THIS MAKES THE EFFECT A FILTER. A filter runs after every other
217
+ * field effect, at full resolution, and what it returns replaces the layers it
218
+ * read: its own output is composed over them in its pass (buildFieldShader),
219
+ * so nothing drawn before it is lost and nothing is drawn twice. Two filters
220
+ * run in document order, each reading the one before. The layers are a
221
+ * pass-order fact — a filter reads the pair the others drew into while it
222
+ * draws into a page of its own (renderFieldPass).
223
+ *
224
+ * This is what a FILTER wants — anything re-rendering the whole frame on a
225
+ * tube, a low-resolution screen, or a card. Reaching for rzSceneDisplay and a
226
+ * depth test instead rebuilds this by hand and gets it wrong in one specific
227
+ * way every time: coverage is not a yes or no, and treating it as one turns
228
+ * every soft edge in the scene into a hard one.
229
+ *
230
+ * NOT INCLUDED: depth of field, and an equirect background (a 360 dome reads
231
+ * as its average rather than its texture).
192
232
  */
193
233
  fn rzSceneFrame(uv: vec2f) -> vec3f {
194
234
  let a = clamp(rzSceneAlpha(uv), 0.0, 1.0);
195
235
  let bg = viewU[2];
196
- let bgRgb = bg.rgb * select(0.0, 1.0, bg.w > 0.5);
197
- return rzSceneDisplay(uv) * a + bgRgb * (1.0 - a);
236
+ let bgA = select(0.0, 1.0, bg.w > 0.5);
237
+ // The background layer over the base, the scene over both, the foreground
238
+ // layer over the frame — the composite's own order.
239
+ let lb = _rzLayerBg(uv);
240
+ let under = lb.rgb + bg.rgb * bgA * (1.0 - lb.a);
241
+ let frame = rzSceneDisplay(uv) * a + under * (1.0 - a);
242
+ let lf = _rzLayerFg(uv);
243
+ return lf.rgb + frame * (1.0 - lf.a);
198
244
  }
199
245
 
200
246
  /**