reze-engine 0.55.16 → 0.56.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/overlay.d.ts CHANGED
@@ -120,11 +120,6 @@ export declare const OVERLAY_STYLE: {
120
120
  readonly jointSize: 0.006;
121
121
  /** Stroke for the material borders drawn over the mesh wireframe. */
122
122
  readonly seamStrokePx: 3;
123
- /** Stroke for the material under the pointer — the same self-occluding reveal
124
- * a pick draws, layered over the section-wide view rather than replacing it,
125
- * and heavier than either a pick's own edges or a seam so it still pops out
126
- * of a view that already has two other things drawn in it. */
127
- readonly hoverStrokePx: 4;
128
123
  /** How strongly the triangulation draws under those borders.
129
124
  *
130
125
  * Both at full strength is one undifferentiated field of yellow — forty
@@ -1 +1 @@
1
- {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../src/overlay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../src/overlay.ts"],"names":[],"mappings":"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"}
package/dist/overlay.js CHANGED
@@ -405,11 +405,6 @@ export const OVERLAY_STYLE = {
405
405
  jointSize: 0.006,
406
406
  /** Stroke for the material borders drawn over the mesh wireframe. */
407
407
  seamStrokePx: 3,
408
- /** Stroke for the material under the pointer — the same self-occluding reveal
409
- * a pick draws, layered over the section-wide view rather than replacing it,
410
- * and heavier than either a pick's own edges or a seam so it still pops out
411
- * of a view that already has two other things drawn in it. */
412
- hoverStrokePx: 4,
413
408
  /** How strongly the triangulation draws under those borders.
414
409
  *
415
410
  * Both at full strength is one undifferentiated field of yellow — forty
@@ -7,11 +7,24 @@
7
7
  */
8
8
  export declare const REFLECTION_DEBUG_WGSL = "\n@group(0) @binding(0) var t: texture_2d<f32>;\n@group(0) @binding(1) var s: sampler;\n\nstruct VSOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f, };\n\n@vertex fn vs(@builtin(vertex_index) i: u32) -> VSOut {\n var out: VSOut;\n let x = f32(i32(i / 2u) * 4 - 1);\n let y = f32(i32(i % 2u) * 4 - 1);\n out.pos = vec4f(x, y, 0.0, 1.0);\n out.uv = vec2f(x * 0.5 + 0.5, 0.5 - y * 0.5);\n return out;\n}\n\n@fragment fn fs(in: VSOut) -> @location(0) vec4f {\n let c = textureSample(t, s, in.uv).rgb;\n return vec4f(sqrt(c / (vec3f(1.0) + c)), 1.0);\n}\n";
9
9
  /**
10
- * Reflection about the horizontal plane y = h, column-major.
10
+ * A plane as the mirror wants it: 4 numbers, (nx, ny, nz, d), surface at
11
+ * n·p + d = 0.
11
12
  *
12
- * p' = (x, 2h - y, z)
13
+ * Written into a caller-owned array rather than returned, because the engine
14
+ * holds one per mirror and rebuilds it whenever the mirror moves — which is
15
+ * every frame a gizmo is being dragged.
16
+ *
17
+ * `n` is normalized here rather than trusted: it arrives from a rotated quad's
18
+ * basis, and a scale baked into that basis would otherwise scale `d` against
19
+ * it and put the plane somewhere else entirely.
20
+ */
21
+ export declare function planeFromPointNormal(px: number, py: number, pz: number, nx: number, ny: number, nz: number, out: Float32Array): Float32Array;
22
+ /**
23
+ * The Householder reflection about (n, d), column-major.
24
+ *
25
+ * p' = p - 2 (n·p + d) n
13
26
  */
14
- export declare function reflectionAboutY(h: number): Float32Array;
27
+ export declare function reflectionAboutPlane(plane: ArrayLike<number>, out?: Float32Array): Float32Array;
15
28
  /**
16
29
  * Fill a camera-uniform block for the mirror pass from the live one.
17
30
  *
@@ -23,5 +36,5 @@ export declare function reflectionAboutY(h: number): Float32Array;
23
36
  * view' = view × R (column-vector convention, matching `projection * view *
24
37
  * pos` in the vertex shaders); projection unchanged; eye reflected.
25
38
  */
26
- export declare function buildMirrorCamera(camera: Float32Array, planeY: number, out: Float32Array): Float32Array;
39
+ export declare function buildMirrorCamera(camera: Float32Array, plane: ArrayLike<number>, out: Float32Array): Float32Array;
27
40
  //# sourceMappingURL=reflection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reflection.d.ts","sourceRoot":"","sources":["../src/reflection.ts"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,8jBAmBjC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAQxD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,YAAY,CAsBvG"}
1
+ {"version":3,"file":"reflection.d.ts","sourceRoot":"","sources":["../src/reflection.ts"],"names":[],"mappings":"AAiCA;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,8jBAmBjC,CAAA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,GAAG,EAAE,YAAY,GAChB,YAAY,CAWd;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,YAAY,GAAG,YAAY,CAoB/F;AAOD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,EACxB,GAAG,EAAE,YAAY,GAChB,YAAY,CAsBd"}
@@ -2,18 +2,34 @@
2
2
  // shadow-cascades precedent.
3
3
  //
4
4
  // A planar reflection is not a second camera aimed by hand; it is the SAME
5
- // camera with the world reflected about the floor plane. Fold the reflection
5
+ // camera with the world reflected about the mirror's plane. Fold the reflection
6
6
  // into the view matrix and everything downstream is untouched: world positions
7
7
  // stay TRUE world positions, so sun, shadows and positional lights evaluate at
8
8
  // the unmirrored point — which is exactly what a mirror shows, an object lit
9
9
  // as it is, seen from a mirrored eye. The only other value that must mirror is
10
10
  // the eye itself, because specular reads the view direction from it.
11
11
  //
12
- // Winding: a reflection has determinant -1, so triangle orientation flips.
13
- // Every scene-pass pipeline that draws into the mirror culls "none", which is
14
- // what makes this legal without a flipped-frontFace pipeline set. The OUTLINE
15
- // culls "back" and is therefore skipped in the mirror — its hull would face
16
- // the wrong way and ink over the model.
12
+ // The plane is carried as (n, d) with n a UNIT normal and the surface at
13
+ // n·p + d = 0 — general from the start, because a mirror standing on the floor
14
+ // is the ordinary case and a floor is only the one plane that happens to be
15
+ // horizontal. The ground is (0, 1, 0, 0).
16
+ //
17
+ // WINDING: a reflection has determinant -1, so triangle orientation flips, and
18
+ // any pipeline that culls a face culls the WRONG one in the mirror pass.
19
+ //
20
+ // This note used to claim every scene-pass pipeline culls "none". That was
21
+ // never true, and each exception cost a round of "the mirror looks broken":
22
+ //
23
+ // ground culls "back" -> the floor vanished whole, and with it the grid
24
+ // and the received shadow, since the shadow is a
25
+ // layer of the ground shader
26
+ // outline culls "back" -> no ink line on the reflection at all
27
+ // eye culls "front" -> the back of the eyeball kept, its front discarded
28
+ //
29
+ // All three now build a mirror twin with the cull answered (the ground goes to
30
+ // "none", the outline to "front", the eye to "back"). Anything ADDED to the
31
+ // scene pass with a cullMode must do the same — grep cullMode in engine.ts,
32
+ // which is the whole list.
17
33
  /**
18
34
  * The debug view: the reflection target drawn over the finished frame — the
19
35
  * only way to SEE whether the mirror pass is right before anything consumes
@@ -42,19 +58,59 @@ struct VSOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f, };
42
58
  }
43
59
  `;
44
60
  /**
45
- * Reflection about the horizontal plane y = h, column-major.
61
+ * A plane as the mirror wants it: 4 numbers, (nx, ny, nz, d), surface at
62
+ * n·p + d = 0.
63
+ *
64
+ * Written into a caller-owned array rather than returned, because the engine
65
+ * holds one per mirror and rebuilds it whenever the mirror moves — which is
66
+ * every frame a gizmo is being dragged.
67
+ *
68
+ * `n` is normalized here rather than trusted: it arrives from a rotated quad's
69
+ * basis, and a scale baked into that basis would otherwise scale `d` against
70
+ * it and put the plane somewhere else entirely.
71
+ */
72
+ export function planeFromPointNormal(px, py, pz, nx, ny, nz, out) {
73
+ const len = Math.hypot(nx, ny, nz) || 1;
74
+ const x = nx / len;
75
+ const y = ny / len;
76
+ const z = nz / len;
77
+ out[0] = x;
78
+ out[1] = y;
79
+ out[2] = z;
80
+ // d places the plane through the point: n·p + d = 0.
81
+ out[3] = -(x * px + y * py + z * pz);
82
+ return out;
83
+ }
84
+ /**
85
+ * The Householder reflection about (n, d), column-major.
46
86
  *
47
- * p' = (x, 2h - y, z)
87
+ * p' = p - 2 (n·p + d) n
48
88
  */
49
- export function reflectionAboutY(h) {
50
- // prettier-ignore
51
- return new Float32Array([
52
- 1, 0, 0, 0,
53
- 0, -1, 0, 0,
54
- 0, 0, 1, 0,
55
- 0, 2 * h, 0, 1,
56
- ]);
89
+ export function reflectionAboutPlane(plane, out) {
90
+ const nx = plane[0], ny = plane[1], nz = plane[2], d = plane[3];
91
+ const m = out ?? new Float32Array(16);
92
+ m[0] = 1 - 2 * nx * nx;
93
+ m[1] = -2 * ny * nx;
94
+ m[2] = -2 * nz * nx;
95
+ m[3] = 0;
96
+ m[4] = -2 * nx * ny;
97
+ m[5] = 1 - 2 * ny * ny;
98
+ m[6] = -2 * nz * ny;
99
+ m[7] = 0;
100
+ m[8] = -2 * nx * nz;
101
+ m[9] = -2 * ny * nz;
102
+ m[10] = 1 - 2 * nz * nz;
103
+ m[11] = 0;
104
+ m[12] = -2 * d * nx;
105
+ m[13] = -2 * d * ny;
106
+ m[14] = -2 * d * nz;
107
+ m[15] = 1;
108
+ return m;
57
109
  }
110
+ /** Scratch for the product below — this module is called once per mirror per
111
+ * frame and allocating a matrix each time is a garbage-collector visit for
112
+ * nothing. */
113
+ const R = new Float32Array(16);
58
114
  /**
59
115
  * Fill a camera-uniform block for the mirror pass from the live one.
60
116
  *
@@ -66,28 +122,27 @@ export function reflectionAboutY(h) {
66
122
  * view' = view × R (column-vector convention, matching `projection * view *
67
123
  * pos` in the vertex shaders); projection unchanged; eye reflected.
68
124
  */
69
- export function buildMirrorCamera(camera, planeY, out) {
70
- const v = camera;
71
- const h = planeY;
72
- // view × R where R = reflectionAboutY(h). R only touches column 1 (scaled by
73
- // -1) and adds 2h·col1 to the translation — write the product directly
74
- // rather than through a generic multiply, so the arithmetic is exact and the
75
- // cost is a handful of ops.
76
- for (let i = 0; i < 16; i++)
77
- out[i] = v[i];
78
- out[4] = -v[4];
79
- out[5] = -v[5];
80
- out[6] = -v[6];
81
- out[7] = -v[7];
82
- out[12] = v[12] + 2 * h * v[4];
83
- out[13] = v[13] + 2 * h * v[5];
84
- out[14] = v[14] + 2 * h * v[6];
85
- out[15] = v[15] + 2 * h * v[7];
125
+ export function buildMirrorCamera(camera, plane, out) {
126
+ reflectionAboutPlane(plane, R);
127
+ // view × R, column-major: out.col_j = Σ_k view.col_k · R[j*4 + k].
128
+ for (let j = 0; j < 4; j++) {
129
+ const b0 = R[j * 4 + 0], b1 = R[j * 4 + 1], b2 = R[j * 4 + 2], b3 = R[j * 4 + 3];
130
+ for (let r = 0; r < 4; r++) {
131
+ out[j * 4 + r] =
132
+ camera[0 + r] * b0 + camera[4 + r] * b1 + camera[8 + r] * b2 + camera[12 + r] * b3;
133
+ }
134
+ }
86
135
  for (let i = 16; i < 32; i++)
87
- out[i] = v[i];
88
- out[32] = v[32];
89
- out[33] = 2 * h - v[33];
90
- out[34] = v[34];
91
- out[35] = v[35];
136
+ out[i] = camera[i];
137
+ // The eye through the same reflection, so specular reads the mirrored view
138
+ // direction. Written from the plane directly rather than through R: it is one
139
+ // dot product, and going via the matrix would round-trip it through nine
140
+ // multiplies to reach the same number.
141
+ const nx = plane[0], ny = plane[1], nz = plane[2], d = plane[3];
142
+ const t = 2 * (nx * camera[32] + ny * camera[33] + nz * camera[34] + d);
143
+ out[32] = camera[32] - t * nx;
144
+ out[33] = camera[33] - t * ny;
145
+ out[34] = camera[34] - t * nz;
146
+ out[35] = camera[35];
92
147
  return out;
93
148
  }
@@ -27,6 +27,21 @@ export type EffectDirectives = {
27
27
  bloom: boolean;
28
28
  /** This effect takes the cast apart — the host reads the timing. */
29
29
  dissolve: boolean;
30
+ /**
31
+ * This effect IS a mirror: a plane in the scene showing a true reflection of
32
+ * it, folded and re-rendered by the engine.
33
+ *
34
+ * A mount with no shader, and the second of them — `#lights` was the first.
35
+ * The reasoning is identical: a planar reflection re-renders every model from
36
+ * a folded camera, which is a PASS, and no mount is a pass. So the effect
37
+ * declares the mirror and the engine reflects, exactly as a lighting rig
38
+ * declares lamps and the engine shades.
39
+ *
40
+ * The plane comes from the effect's own `#param` dials, by name — see the
41
+ * engine's MIRROR_DIALS. That is what makes it an effect rather than a switch:
42
+ * it publishes, forks, schedules and fades like every other one.
43
+ */
44
+ mirror: boolean;
30
45
  /**
31
46
  * How long ONE firing of this effect lasts, in seconds. 0 = undeclared.
32
47
  *
@@ -1 +1 @@
1
- {"version":3,"file":"directives.d.ts","sourceRoot":"","sources":["../../src/shaders/directives.ts"],"names":[],"mappings":"AA8BA,oEAAoE;AACpE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;IAChC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IACjD,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6EAA6E;IAC7E,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAC3C,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,0EAA0E;IAC1E,UAAU,EAAE,CAAC,GAAG,CAAC,CAAA;IACjB,8DAA8D;IAC9D,aAAa,EAAE,OAAO,CAAA;IACtB,wEAAwE;IACxE,aAAa,EAAE,OAAO,GAAG,UAAU,CAAA;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAiBD;;;+BAG+B;AAC/B,eAAO,MAAM,cAAc,QAAmC,CAAA;AAG9D;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,QAA8B,CAAA;AAmBzD,MAAM,MAAM,eAAe,GAAG;IAAE,UAAU,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEhF,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAqJ7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpD"}
1
+ {"version":3,"file":"directives.d.ts","sourceRoot":"","sources":["../../src/shaders/directives.ts"],"names":[],"mappings":"AA8BA,oEAAoE;AACpE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;IAChC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IACjD,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6EAA6E;IAC7E,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAC3C,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,0EAA0E;IAC1E,UAAU,EAAE,CAAC,GAAG,CAAC,CAAA;IACjB,8DAA8D;IAC9D,aAAa,EAAE,OAAO,CAAA;IACtB,wEAAwE;IACxE,aAAa,EAAE,OAAO,GAAG,UAAU,CAAA;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,MAAM,EAAE,OAAO,CAAA;IACf;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAkBD;;;+BAG+B;AAC/B,eAAO,MAAM,cAAc,QAAmC,CAAA;AAG9D;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,QAA8B,CAAA;AAmBzD,MAAM,MAAM,eAAe,GAAG;IAAE,UAAU,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEhF,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAyJ7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpD"}
@@ -39,6 +39,7 @@ const SPEC = {
39
39
  grid: 1,
40
40
  bloom: 0,
41
41
  dissolve: 0,
42
+ mirror: 0,
42
43
  duration: 1,
43
44
  };
44
45
  /** A line that declares something, and what it declares. Exported because an
@@ -88,6 +89,7 @@ export function parseDirectives(wgsl) {
88
89
  grid: 0,
89
90
  bloom: false,
90
91
  dissolve: false,
92
+ mirror: false,
91
93
  duration: 0,
92
94
  };
93
95
  const errors = [];
@@ -183,6 +185,9 @@ export function parseDirectives(wgsl) {
183
185
  case "dissolve":
184
186
  d.dissolve = true;
185
187
  return;
188
+ case "mirror":
189
+ d.mirror = true;
190
+ return;
186
191
  case "duration": {
187
192
  // SECONDS, like every other time a directive states (see #dissolve).
188
193
  // The document above works in frames and converts once; an author
@@ -1 +1 @@
1
- {"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"AAgFA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,UAAQ,GAAG,MAAM,CA+TrD;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,QAAS,CAAA;AACxC,eAAO,MAAM,gBAAgB,QAAS,CAAA;AAEtC;;;oEAGoE;AACpE,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAErC;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,+oCAiClC,CAAA"}
1
+ {"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"AAgFA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,UAAQ,GAAG,MAAM,CAmWrD;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,QAAS,CAAA;AACxC,eAAO,MAAM,gBAAgB,QAAS,CAAA;AAEtC;;;oEAGoE;AACpE,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAErC;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,+oCAiClC,CAAA"}
@@ -119,6 +119,26 @@ struct MirrorVP { viewProj: mat4x4f, params: vec4f, };
119
119
  // The frost noise, PRE-BAKED — see GROUND_NOISE_BAKE_WGSL below. Sampled with
120
120
  // the repeat sampler already at binding 10.
121
121
  @group(0) @binding(12) var noiseTex: texture_2d<f32>;
122
+ // The mirror pass's resolved aux. .g is accumulated alpha — the reflection's
123
+ // coverage. It lives here rather than in the colour target's own alpha because
124
+ // the HDR format is rg11b10ufloat wherever the device allows it, and that
125
+ // format has no alpha channel at all.
126
+ @group(0) @binding(13) var mirrorMask: texture_2d<f32>;
127
+ // THE MIRROR'S OWN PLANE, and whether this draw is the one inside the mirror
128
+ // pass. A mirror shows nothing behind itself, and the floor is the geometry
129
+ // most often behind one: stand a pane up and half the floor is behind it, lay
130
+ // it facing UP and the whole floor is. Without this the reflection carries a
131
+ // grid that the mirror cannot see.
132
+ //
133
+ // A uniform rather than a branch on some global, because the two ground bind
134
+ // groups already differ — the camera's binds a block set to 0 and the
135
+ // mirror's binds the live plane. No per-pass write, and nothing to get wrong
136
+ // about which pass is running.
137
+ // The flag is named on, not active: WGSL RESERVES active, and a reserved
138
+ // word in a struct field fails the whole module — taking the ground
139
+ // pipeline, and every style group and effect compiled after it, with it.
140
+ struct MirrorClip { plane: vec4f, on: f32, _p0: f32, _p1: f32, _p2: f32, };
141
+ @group(0) @binding(14) var<uniform> clip: MirrorClip;
122
142
  ${WORLD_AMBIENT_WGSL}
123
143
  ${lightsApi(0, 6)}
124
144
 
@@ -133,6 +153,14 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
133
153
  let gp = i.worldPos.xz / material.gridSpacing;
134
154
  let gridDeriv = fwidth(gp);
135
155
 
156
+ // The clip, straight after the derivatives and before anything else branches.
157
+ // EPSILON rather than zero: a pane lying flat on the floor is coplanar with
158
+ // it, and a floor reflecting itself at zero distance is z-fighting, not a
159
+ // reflection.
160
+ if (clip.on > 0.5 && dot(clip.plane.xyz, i.worldPos) + clip.plane.w < 0.01) {
161
+ discard;
162
+ }
163
+
136
164
  var out: FSOut;
137
165
  let n = normalize(i.normal);
138
166
  let centerDist = length(i.worldPos.xz);
@@ -309,6 +337,11 @@ ${pcfWgsl("shadowMap", "suv_c", "material.pcfTexel", "compareZ", "acc", " ",
309
337
  // independence the grid won earlier. The branch is on a uniform, so the
310
338
  // whole cost vanishes for the scenes that leave it off.
311
339
  var reflShadowed = vec3f(0.0);
340
+ // How much of this texel the reflection pass actually covered. The target
341
+ // clears TRANSPARENT, so this is 1 where something reflected and 0 over the
342
+ // empty sky — which is what stops a floor mirror from painting the backdrop
343
+ // onto itself and wiping out the ground's own colour.
344
+ var reflA = 0.0;
312
345
  if (material.mirror > 0.0) {
313
346
  let mc = mirrorVP.viewProj * vec4f(i.worldPos, 1.0);
314
347
  let mndc = mc.xyz / max(mc.w, 1e-6);
@@ -343,10 +376,11 @@ ${pcfWgsl("shadowMap", "suv_c", "material.pcfTexel", "compareZ", "acc", " ",
343
376
  let BLUR_SPAN = 14.0;
344
377
  lod = material.mirrorBlur * 5.0 * clamp(height / BLUR_SPAN, 0.0, 1.0);
345
378
  }
346
- let refl = textureSampleLevel(mirrorTex, linearSampler, muv, lod).rgb;
379
+ let refl = textureSampleLevel(mirrorTex, linearSampler, muv, lod);
347
380
  // Still under the received shadow: a polished floor in shade shows a dim
348
381
  // reflection, and a mirror that ignored the shadow would glow in it.
349
- reflShadowed = refl * (1.0 - dark * 0.65);
382
+ reflShadowed = refl.rgb * (1.0 - dark * 0.65);
383
+ reflA = textureSampleLevel(mirrorMask, linearSampler, muv, 0.0).g;
350
384
  }
351
385
  // THREE LAYERS, composited premultiplied, bottom to top: the shadow the
352
386
  // catcher receives, the ground surface, and the grid.
@@ -358,15 +392,17 @@ ${pcfWgsl("shadowMap", "suv_c", "material.pcfTexel", "compareZ", "acc", " ",
358
392
  // alpha, and the grid is simply on or off.
359
393
  let surfA = edgeFade * material.opacity;
360
394
  let gridA = gridLine * material.gridLineOpacity * edgeFade;
361
- // The mirror is opaque across the ground's extent when on — it always shows
362
- // SOMETHING, because the backdrop rides in the reflection target itself.
363
- let mirrA = material.mirror * edgeFade;
395
+ // The mirror covers only what it actually REFLECTS. The backdrop no longer
396
+ // rides in the reflection target — it clears transparent — so the floor keeps
397
+ // its own colour everywhere the cast is not standing, which is the difference
398
+ // between an MMD stage floor and a mirror of the sky.
399
+ let mirrA = material.mirror * edgeFade * reflA;
364
400
  // As opacity drops the received shadow becomes a translucent dark layer
365
401
  // (Blender's Shadow Catcher), so models still feel grounded on a photo or a
366
402
  // 360 backdrop. Colourless by construction: it darkens by covering. The
367
403
  // MIRROR subsumes it: the reflection already carries the received shadow,
368
404
  // and a catcher stacked on top would darken the same shadow twice.
369
- let catchA = dark * 0.65 * edgeFade * (1.0 - material.opacity) * (1.0 - material.mirror);
405
+ let catchA = dark * 0.65 * edgeFade * (1.0 - material.opacity) * (1.0 - mirrA);
370
406
 
371
407
  // FOUR LAYERS, premultiplied, bottom to top: reflection, received shadow,
372
408
  // ground surface, grid. Each with its own coverage, none scaling another —
@@ -0,0 +1,32 @@
1
+ /** Bytes in MirrorMat below: mat4x4f + three 16-byte rows. */
2
+ export declare const MIRROR_MAT_BYTES: number;
3
+ export declare function mirrorShaderWgsl(): string;
4
+ /**
5
+ * The mirror in the SHADOW pass — the frame throwing shade on the floor.
6
+ *
7
+ * Its own shader because the mirror has no vertex buffer and no skeleton: the
8
+ * quad is six generated vertices and the model matrix is the whole of it, while
9
+ * the ordinary shadow shader is built for skinned geometry with an alpha test.
10
+ *
11
+ * Depth only, no fragment stage. The pane is opaque across its whole rectangle
12
+ * — glass included, because this glass is not see-through — so there is nothing
13
+ * to alpha-test and nothing to colour.
14
+ *
15
+ * The cascade index arrives in its own tiny uniform rather than as a dynamic
16
+ * offset: the cascade matrices sit 64 bytes apart in one array, and a dynamic
17
+ * uniform offset has to be 256-aligned.
18
+ */
19
+ /**
20
+ * Downsample for the reflection's COVERAGE chain.
21
+ *
22
+ * Colour and coverage are a premultiplied PAIR, and a blur has to move them
23
+ * together. Blurring colour alone drags it toward zero wherever it spreads into
24
+ * the empty part of the reflection, while a coverage still sampled sharp says
25
+ * "fully covered" — so the reflection simply got darker as the dial came up.
26
+ *
27
+ * One bilinear tap at the destination texel centre averages the source's 2x2
28
+ * exactly, which is the box filter this wants and the cheapest way to write it.
29
+ */
30
+ export declare const MIRROR_MASK_DOWNSAMPLE_WGSL = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1) var samp: sampler;\n\nstruct VSOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f, };\n\n@vertex fn vs(@builtin(vertex_index) i: u32) -> VSOut {\n var out: VSOut;\n let x = f32(i32(i / 2u) * 4 - 1);\n let y = f32(i32(i % 2u) * 4 - 1);\n out.pos = vec4f(x, y, 0.0, 1.0);\n out.uv = vec2f(x * 0.5 + 0.5, 0.5 - y * 0.5);\n return out;\n}\n\n@fragment fn fs(in: VSOut) -> @location(0) vec4f {\n return textureSampleLevel(src, samp, in.uv, 0.0);\n}\n";
31
+ export declare function mirrorShadowWgsl(cascades: number): string;
32
+ //# sourceMappingURL=mirror.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/mirror.ts"],"names":[],"mappings":"AAqBA,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,QAAc,CAAA;AAE3C,wBAAgB,gBAAgB,IAAI,MAAM,CAiMzC;AAED;;;;;;;;;;;;;;GAcG;AACH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,2hBAkBvC,CAAA;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAuBzD"}