reze-engine 0.23.0 → 0.23.1

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/engine.d.ts CHANGED
@@ -90,6 +90,9 @@ export type EngineOptions = {
90
90
  world?: WorldOptions;
91
91
  sun?: SunOptions;
92
92
  camera?: CameraOptions;
93
+ /** Canvas background (display-space sRGB 0–1), composited under the scene after
94
+ * tonemapping. Omit/null = transparent canvas (see setBackgroundColor). */
95
+ background?: Vec3 | null;
93
96
  /** Initial EEVEE-style bloom; tune at runtime with `setBloomOptions`. */
94
97
  bloom?: Partial<BloomOptions>;
95
98
  /** View transform (exposure/gamma) applied in composite before/after Filmic. */
@@ -212,6 +215,13 @@ export declare class Engine {
212
215
  private compositeBindGroup;
213
216
  private compositeUniformBuffer;
214
217
  private readonly compositeUniformData;
218
+ /** Composite background (display-space sRGB 0–1) — null = transparent canvas. */
219
+ private backgroundColor;
220
+ private backdropEquirectTexture;
221
+ private backdropEquirectView;
222
+ private fallbackEquirectTexture;
223
+ private fallbackEquirectView;
224
+ private compositeBloomView;
215
225
  private bloomSampler;
216
226
  private bloomBlitUniformBuffer;
217
227
  private bloomUpsampleUniformBuffer;
@@ -300,6 +310,23 @@ export declare class Engine {
300
310
  getViewTransformOptions(): ViewTransformOptions;
301
311
  setViewTransformOptions(patch: Partial<ViewTransformOptions>): void;
302
312
  private writeCompositeViewUniforms;
313
+ /**
314
+ * Set the canvas background color (display-space sRGB, 0–1 per channel — the
315
+ * same value a CSS background of that color shows, applied after tonemapping).
316
+ * Pass null for a transparent canvas (the page/DOM shows through — e.g. when a
317
+ * backdrop image layer sits behind the canvas). Applies on the next frame.
318
+ * A 360 backdrop (setBackdropEquirect) takes precedence while set.
319
+ */
320
+ setBackgroundColor(color: Vec3 | null): void;
321
+ private rebuildCompositeBindGroup;
322
+ /**
323
+ * Set a 360° backdrop from an equirectangular (2:1) image — a PhotoDome-style
324
+ * skybox at infinity, sampled per-pixel by view direction so it follows the
325
+ * camera. Display-only: composited in display space behind the scene, it never
326
+ * affects lighting, bloom, or tonemapping. Pass null to remove (the background
327
+ * color, or transparency, takes over again).
328
+ */
329
+ setBackdropEquirect(source: ImageBitmap | HTMLImageElement | HTMLCanvasElement | null): void;
303
330
  /** Patch bloom; GPU uniforms update immediately if `init()` has run. */
304
331
  setBloomOptions(patch: Partial<BloomOptions>): void;
305
332
  private writeBloomUniforms;
@@ -386,6 +413,8 @@ export declare class Engine {
386
413
  gridLineOpacity?: number;
387
414
  gridLineColor?: Vec3;
388
415
  noiseStrength?: number;
416
+ /** Whole-ground opacity, 0–1 (multiplies the radial edge fade). Default 1. */
417
+ opacity?: number;
389
418
  }): void;
390
419
  private updateLightBuffer;
391
420
  getStats(): EngineStats;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,EAAE,KAAK,EAAiB,MAAM,SAAS,CAAA;AAO9C,OAAO,EAQL,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAA;AAgBvB,OAAO,EAAgB,KAAK,cAAc,EAAkB,MAAM,iBAAiB,CAAA;AAGnF,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EAEtB,UAAU,EACX,MAAM,qBAAqB,CAAA;AAa5B,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,MAAM,GACN,MAAM,GACN,MAAM,GACN,KAAK,GACL,WAAW,GACX,OAAO,GACP,cAAc,GACd,aAAa,CAAA;AAEjB,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;AAqHzE,MAAM,MAAM,eAAe,GAAG,CAC5B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,KACZ,IAAI,CAAA;AAET,kHAAkH;AAClH,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE,CAAA;IACxB,OAAO,CAAC,EAAE,IAAI,CAAA;CACf,CAAA;AAID,MAAM,MAAM,YAAY,GAAG;IACzB,8FAA8F;IAC9F,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;qDACqD;AACrD,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,IAAI,CAAA;IACd,QAAQ,EAAE,IAAI,CAAA;IACd,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yFAAyF;IACzF,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,MAAM,CAAC,EAAE,IAAI,CAAA;IACb,yCAAyC;IACzC,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,wFAAwF;AACxF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,IAAI,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,eAAO,MAAM,qBAAqB,EAAE,YAQnC,CAAA;AAED,4HAA4H;AAC5H,MAAM,MAAM,oBAAoB,GAAG;IACjC,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAA;IAChB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,SAAS,GAAG,sBAAsB,CAAA;CACzC,CAAA;AAID,eAAO,MAAM,sBAAsB,EAAE,oBAIpC,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,WAAW,CAAA;AAElD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,kGAAkG;IAClG,aAAa,EAAE,IAAI,CAAA;IACnB,gBAAgB,EAAE,IAAI,CAAA;IACtB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;CACxB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;AAE/D,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB,GAAG,CAAC,EAAE,UAAU,CAAA;IAChB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB;gFAC4E;IAC5E,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,yEAAyE;IACzE,KAAK,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;IAC7B,gFAAgF;IAChF,IAAI,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAA;IACpC,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,qCAAqC;IACrC,WAAW,CAAC,EAAE,iBAAiB,CAAA;CAChC,CAAA;AAED,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;CAKlC,CAAA;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;CACf;AAkED,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAsB;IAE7C,MAAM,CAAC,WAAW,IAAI,MAAM;IAO5B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,kBAAkB,CAAmB;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,mBAAmB,CAAY;IACvC,OAAO,CAAC,gBAAgB,CAAuB;IAE/C,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,GAAG,CAAqD;IAChE,OAAO,CAAC,YAAY,CAAkD;IACtE,OAAO,CAAC,kBAAkB,CAAY;IACtC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,YAAY,CAAa;IAGjC,OAAO,CAAC,eAAe,CAAoB;IAK3C,OAAO,CAAC,eAAe,CAAY;IAEnC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,uBAAuB,CAA0B;IAGzD,OAAO,CAAC,sBAAsB,CAAa;IAC3C,OAAO,CAAC,oBAAoB,CAAoB;IAChD,OAAO,CAAC,2BAA2B,CAAqB;IACxD,OAAO,CAAC,eAAe,CAAoB;IAC3C,OAAO,CAAC,gBAAgB,CAA2D;IACnF,OAAO,CAAC,oBAAoB,CAAC,CAAY;IACzC,OAAO,CAAC,iBAAiB,CAAC,CAAgB;IAC1C,OAAO,CAAC,qBAAqB,CAAoB;IACjD,OAAO,CAAC,2BAA2B,CAA0B;IAC7D,OAAO,CAAC,qBAAqB,CAAoB;IACjD,OAAO,CAAC,4BAA4B,CAAqB;IACzD,OAAO,CAAC,sBAAsB,CAAC,CAAc;IAC7C,OAAO,CAAC,0BAA0B,CAAY;IAC9C,OAAO,CAAC,2BAA2B,CAA0B;IAC7D,OAAO,CAAC,gBAAgB,CAAa;IAGrC,OAAO,CAAC,YAAY,CAA0E;IAC9F,OAAO,CAAC,iBAAiB,CAAY;IACrC,OAAO,CAAC,oBAAoB,CAAY;IACxC,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,eAAe,CAAe;IACtC,OAAO,CAAC,oBAAoB,CAAqB;IACjD,OAAO,CAAC,mBAAmB,CAA0B;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAK;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAM;IAG/C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IAIhD,OAAO,CAAC,UAAU,CAAoD;IACtE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAC9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAO;IACjD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAO;IAOtD,OAAO,CAAC,SAAS,CAcF;IACf,OAAO,CAAC,2BAA2B,CAAqB;IACxD,OAAO,CAAC,8BAA8B,CAAqB;IAC3D,OAAO,CAAC,8BAA8B,CAAqB;IAC3D,OAAO,CAAC,8BAA8B,CAAqB;IAC3D,OAAO,CAAC,iCAAiC,CAAqB;IAC9D,OAAO,CAAC,iBAAiB,CAAe;IACxC,OAAO,CAAC,wBAAwB,CAAe;IAC/C,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAI;IAiB7C,OAAO,CAAC,SAAS,CAAkC;IACnD;0FACsF;IACtF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAI;IAC7C;;;;;;;gGAO4F;IAC5F,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAA+B;IACxE,OAAO,CAAC,sBAAsB,CAAa;IAC3C,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,oBAAoB,CAA0B;IACtD,OAAO,CAAC,uBAAuB,CAA0B;IAIzD,OAAO,CAAC,yBAAyB,CAAoB;IACrD,OAAO,CAAC,sBAAsB,CAAoB;IAClD,OAAO,CAAC,oBAAoB,CAAqB;IACjD,OAAO,CAAC,2BAA2B,CAAqB;IACxD,OAAO,CAAC,wBAAwB,CAAqB;IACrD,OAAO,CAAC,kBAAkB,CAAe;IACzC,OAAO,CAAC,sBAAsB,CAAY;IAE1C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAuB;IAC5D,iFAAiF;IACjF,OAAO,CAAC,eAAe,CAAoB;IAE3C,OAAO,CAAC,uBAAuB,CAA0B;IACzD,OAAO,CAAC,oBAAoB,CAA8B;IAC1D,OAAO,CAAC,uBAAuB,CAAa;IAC5C,OAAO,CAAC,oBAAoB,CAAiB;IAC7C,OAAO,CAAC,kBAAkB,CAA8B;IAQxD,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,sBAAsB,CAAY;IAC1C,OAAO,CAAC,0BAA0B,CAAY;IAC9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAsB;IAC3D,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAsB;IAC/D,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,uBAAuB,CAAoB;IACnD,OAAO,CAAC,qBAAqB,CAAoB;IACjD,OAAO,CAAC,wBAAwB,CAAqB;IACrD,OAAO,CAAC,8BAA8B,CAAqB;IAC3D,OAAO,CAAC,4BAA4B,CAAqB;IACzD,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,kBAAkB,CAAe;IACzC,OAAO,CAAC,yBAAyB,CAAqB;IACtD,OAAO,CAAC,uBAAuB,CAAqB;IACpD,2EAA2E;IAC3E,OAAO,CAAC,mBAAmB,CAA0B;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAI;IAG5C,OAAO,CAAC,kBAAkB,CAAC,CAAW;IACtC,OAAO,CAAC,iBAAiB,CAAC,CAAW;IACrC,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,kBAAkB,CAAiB;IAC3C,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,aAAa,CAAiB;IAEtC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAC9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAO;IAC9C,OAAO,CAAC,mBAAmB,CAAoB;IAC/C,OAAO,CAAC,mBAAmB,CAAY;IACvC,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,qBAAqB,CAAC,CAAc;IAC5C,OAAO,CAAC,uBAAuB,CAAa;IAC5C,OAAO,CAAC,0BAA0B,CAAC,CAAW;IAC9C,OAAO,CAAC,cAAc,CAAwB;IAE9C,OAAO,CAAC,SAAS,CAAC,CAAiB;IACnC,OAAO,CAAC,WAAW,CAAC,CAAmB;IACvC,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAEvC,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,2BAA2B,CAAqB;IACxD,OAAO,CAAC,8BAA8B,CAAqB;IAC3D,OAAO,CAAC,8BAA8B,CAAqB;IAC3D,OAAO,CAAC,qBAAqB,CAAe;IAC5C,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,kBAAkB,CAAY;IACtC,OAAO,CAAC,WAAW,CAAwC;IAE3D,OAAO,CAAC,cAAc,CAAmC;IACzD,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,uBAAuB,CAAa;IAC5C,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,mBAAmB,CAAI;IAG/B,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,cAAc,CAAO;IAE7B,OAAO,CAAC,YAAY,CAAO;IAI3B,OAAO,CAAC,eAAe,CAA+B;IAGtD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,kBAAkB,CAA0B;IAEpD,OAAO,CAAC,aAAa,CAAoB;IAGzC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAM;IAC1C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAC9C,OAAO,CAAC,cAAc,CAAwC;IAC9D,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,KAAK,CAMZ;IACD,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,aAAa,CAAuB;gBAEhC,MAAM,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,aAAa;IAyB9D,qEAAqE;IACrE,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY;IAcxE,MAAM,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,oBAAoB;IAShG,uEAAuE;IACvE,eAAe,IAAI,YAAY;IAa/B,uBAAuB,IAAI,oBAAoB;IAK/C,uBAAuB,CAAC,KAAK,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,IAAI;IAUnE,OAAO,CAAC,0BAA0B;IA4BlC;;;;;;OAMG;IACH,kBAAkB,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI;IAK5C,OAAO,CAAC,yBAAyB;IAiBjC;;;;;;OAMG;IACH,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,IAAI,GAAG,IAAI;IAqC5F,wEAAwE;IACxE,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;IAoBnD,OAAO,CAAC,kBAAkB;IAsBpB,IAAI;IAyCV,OAAO,CAAC,WAAW;IAiFnB,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,oBAAoB;IAiC5B,OAAO,CAAC,eAAe;IAgqBvB,OAAO,CAAC,WAAW;IAsBnB;oEACgE;IAChE,OAAO,CAAC,eAAe,CAAiD;IAExE;;;;;;;;OAQG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAClD,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAU/B,OAAO,CAAC,YAAY;IAsPpB,OAAO,CAAC,UAAU;IAgKlB,OAAO,CAAC,WAAW;IAmBnB,iFAAiF;IACjF,eAAe,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI;IAC9B,gGAAgG;IAChG,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI;IAoB3E,mIAAmI;IACnI,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI;IAgB5E;6FACyF;IACnF,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/C,uFAAuF;IACvF,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAMlD,8FAA8F;IAC9F,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAI3C,qEAAqE;IACrE,kBAAkB,IAAI,OAAO;IAI7B,+EAA+E;IAC/E,YAAY,IAAI,OAAO;IAIvB,8DAA8D;IAC9D,cAAc,IAAI,IAAI;IAQtB,OAAO,CAAC,eAAe;IAUvB,iBAAiB,IAAI,MAAM;IAG3B,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAGlC,cAAc,IAAI,MAAM;IAGxB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAG/B,aAAa,IAAI,MAAM;IAGvB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAK9B,OAAO,CAAC,aAAa;IAYrB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IASlB,qFAAqF;IACrF,OAAO,CAAC,QAAQ;IAgBhB,gGAAgG;IAChG,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAMrC,0FAA0F;IAC1F,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAUjC,QAAQ,IAAI,QAAQ,CAAC;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAGvD,MAAM,IAAI,QAAQ,CAAC;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,CAAA;KAAE,CAAC;IAItE,SAAS,CAAC,OAAO,CAAC,EAAE;QAClB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,YAAY,CAAC,EAAE,IAAI,CAAA;QACnB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,aAAa,CAAC,EAAE,IAAI,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,8EAA8E;QAC9E,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,GAAG,IAAI;IA6BR,OAAO,CAAC,iBAAiB;IAIzB,QAAQ,IAAI,WAAW;IAIvB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI;IAgBnC,cAAc;IAQd,OAAO;IAgCD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IACvC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IACrD,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,KAAK,CAAC;IAyB3E,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAcxG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAmB/B,aAAa,IAAI,MAAM,EAAE;IAIzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAIpC;;;;;;OAMG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;IASzE,kFAAkF;IAClF,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAYtD,qBAAqB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAe9D,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIhF,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAgBxE,OAAO,CAAC,uBAAuB;IAQ/B,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAOnF,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAOpE,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAKnE,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKpC,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAI3C,YAAY,IAAI,OAAO;IAIvB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIzC,iBAAiB,IAAI,OAAO;IAI5B,YAAY,IAAI,IAAI;IAWpB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,kBAAkB;IA+B1B,OAAO,CAAC,oBAAoB;YAgBd,kBAAkB;IA0HhC,OAAO,CAAC,cAAc;IAoEtB,OAAO,CAAC,oBAAoB;IAwE5B,OAAO,CAAC,2BAA2B;IA8DnC,OAAO,CAAC,kBAAkB,CAAO;IACjC,OAAO,CAAC,mBAAmB;YAeb,yBAAyB;IAuIvC,OAAO,CAAC,2BAA2B;IAyBnC,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,oBAAoB;YAId,4BAA4B;IA+E1C,OAAO,CAAC,eAAe;IA6CvB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,uBAAuB,CAI9B;IAED,OAAO,CAAC,iBAAiB,CA0BxB;IAED,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,qBAAqB;IAsC7B,OAAO,CAAC,eAAe;IA8DvB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,YAAY;IAqDpB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,oBAAoB,CAoG3B;IAED,OAAO,CAAC,oBAAoB,CA+C3B;IAED,OAAO,CAAC,kBAAkB,CAsBzB;IAED,OAAO,CAAC,cAAc;YAgDR,iBAAiB;IAkD/B,MAAM;IASN;;;;;;;OAOG;IACH,WAAW,CAAC,YAAY,EAAE,MAAM;IAMhC,OAAO,CAAC,eAAe;IAsIvB,OAAO,CAAC,kBAAkB;IAiB1B;6FACyF;IACzF,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,EAAE;IAM/C;;;;;;;OAOG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA8BxG;;;;;OAKG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA0ChG;iDAC6C;IACvC,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,qBAAqB,CAAC;IASnH,oFAAoF;IACpF,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAU1D,0FAA0F;IAC1F,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAWzC,oFAAoF;IACpF,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GACvC,OAAO;IAiBV,OAAO,CAAC,aAAa;YASP,sBAAsB;IA2EpC,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,aAAa;IAcrB;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IA2DjC,OAAO,CAAC,mBAAmB;IAQ3B;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAoBrB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAepB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAiBtB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,oBAAoB;IAqC5B,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,WAAW;CA0CpB"}
package/dist/engine.js CHANGED
@@ -188,7 +188,13 @@ export class Engine {
188
188
  // unchanged).
189
189
  this.hdrFormat = "rgba16float";
190
190
  // [exposure, invGamma, _, _, bloomTint.x, bloomTint.y, bloomTint.z, bloomIntensity]
191
- this.compositeUniformData = new Float32Array(8);
191
+ this.compositeUniformData = new Float32Array(24);
192
+ /** Composite background (display-space sRGB 0–1) — null = transparent canvas. */
193
+ this.backgroundColor = null;
194
+ // 360 backdrop (equirectangular skybox, sampled by view ray in composite).
195
+ this.backdropEquirectTexture = null;
196
+ this.backdropEquirectView = null;
197
+ this.compositeBloomView = null;
192
198
  this.bloomBlitUniformData = new Float32Array(4);
193
199
  this.bloomUpsampleUniformData = new Float32Array(4);
194
200
  this.bloomMipCount = 0;
@@ -453,6 +459,8 @@ export class Engine {
453
459
  this.onGizmoDrag = options?.onGizmoDrag;
454
460
  this.bloomSettings = Engine.mergeBloomDefaults(options?.bloom);
455
461
  this.viewTransform = Engine.mergeViewTransformDefaults(options?.view);
462
+ const bg = options?.background;
463
+ this.backgroundColor = bg ? new Vec3(bg.x, bg.y, bg.z) : null;
456
464
  }
457
465
  /** Merge partial bloom with EEVEE defaults (same as constructor). */
458
466
  static mergeBloomDefaults(partial) {
@@ -521,8 +529,90 @@ export class Engine {
521
529
  u[5] = b.color.y;
522
530
  u[6] = b.color.z;
523
531
  u[7] = effIntensity;
532
+ // Background composited UNDER the scene in display space (post-tonemap), so it
533
+ // matches a CSS color of the same value exactly. Mode (u[11]): 0 = transparent
534
+ // (DOM shows), 1 = solid color, 2 = 360 equirect (sampled by view ray; the
535
+ // camera basis at u[12..23] is refreshed per frame by updateCameraUniforms).
536
+ const bg = this.backgroundColor;
537
+ u[8] = bg?.x ?? 0;
538
+ u[9] = bg?.y ?? 0;
539
+ u[10] = bg?.z ?? 0;
540
+ u[11] = this.backdropEquirectView ? 2 : bg ? 1 : 0;
524
541
  this.device.queue.writeBuffer(this.compositeUniformBuffer, 0, u);
525
542
  }
543
+ /**
544
+ * Set the canvas background color (display-space sRGB, 0–1 per channel — the
545
+ * same value a CSS background of that color shows, applied after tonemapping).
546
+ * Pass null for a transparent canvas (the page/DOM shows through — e.g. when a
547
+ * backdrop image layer sits behind the canvas). Applies on the next frame.
548
+ * A 360 backdrop (setBackdropEquirect) takes precedence while set.
549
+ */
550
+ setBackgroundColor(color) {
551
+ this.backgroundColor = color ? new Vec3(color.x, color.y, color.z) : null;
552
+ if (this.device && this.compositeUniformBuffer)
553
+ this.writeCompositeViewUniforms();
554
+ }
555
+ rebuildCompositeBindGroup() {
556
+ if (!this.device || !this.hdrResolveTexture || !this.compositeBloomView)
557
+ return;
558
+ this.compositeBindGroup = this.device.createBindGroup({
559
+ label: "composite bind group",
560
+ layout: this.compositeBindGroupLayout,
561
+ entries: [
562
+ { binding: 0, resource: this.hdrResolveTexture.createView() },
563
+ { binding: 1, resource: this.compositeBloomView },
564
+ { binding: 2, resource: this.bloomSampler },
565
+ { binding: 3, resource: { buffer: this.compositeUniformBuffer } },
566
+ { binding: 4, resource: this.maskResolveView },
567
+ { binding: 5, resource: this.filmicLutView },
568
+ { binding: 6, resource: this.backdropEquirectView ?? this.fallbackEquirectView },
569
+ ],
570
+ });
571
+ }
572
+ /**
573
+ * Set a 360° backdrop from an equirectangular (2:1) image — a PhotoDome-style
574
+ * skybox at infinity, sampled per-pixel by view direction so it follows the
575
+ * camera. Display-only: composited in display space behind the scene, it never
576
+ * affects lighting, bloom, or tonemapping. Pass null to remove (the background
577
+ * color, or transparency, takes over again).
578
+ */
579
+ setBackdropEquirect(source) {
580
+ this.backdropEquirectTexture?.destroy();
581
+ this.backdropEquirectTexture = null;
582
+ this.backdropEquirectView = null;
583
+ if (source && this.device) {
584
+ let width = Math.max(1, "naturalWidth" in source ? source.naturalWidth : source.width);
585
+ let height = Math.max(1, "naturalHeight" in source ? source.naturalHeight : source.height);
586
+ let upload = source;
587
+ // Panoramas routinely exceed maxTextureDimension2D (e.g. 10000×5000 vs the
588
+ // default 8192) — quietly downscale to fit rather than surfacing an error.
589
+ const limit = this.device.limits.maxTextureDimension2D;
590
+ if (width > limit || height > limit) {
591
+ const scale = Math.min(limit / width, limit / height);
592
+ const w = Math.max(1, Math.floor(width * scale));
593
+ const h = Math.max(1, Math.floor(height * scale));
594
+ const canvas = new OffscreenCanvas(w, h);
595
+ const cx = canvas.getContext("2d");
596
+ cx.imageSmoothingQuality = "high";
597
+ cx.drawImage(source, 0, 0, w, h);
598
+ upload = canvas;
599
+ width = w;
600
+ height = h;
601
+ }
602
+ const tex = this.device.createTexture({
603
+ label: "backdrop equirect",
604
+ size: [width, height],
605
+ format: "rgba8unorm",
606
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
607
+ });
608
+ this.device.queue.copyExternalImageToTexture({ source: upload }, { texture: tex }, [width, height]);
609
+ this.backdropEquirectTexture = tex;
610
+ this.backdropEquirectView = tex.createView();
611
+ }
612
+ this.rebuildCompositeBindGroup();
613
+ if (this.device && this.compositeUniformBuffer)
614
+ this.writeCompositeViewUniforms();
615
+ }
526
616
  /** Patch bloom; GPU uniforms update immediately if `init()` has run. */
527
617
  setBloomOptions(patch) {
528
618
  const b = this.bloomSettings;
@@ -1260,7 +1350,9 @@ export class Engine {
1260
1350
  // mirroring EEVEE where bloom color/intensity are combine-stage params, not prefilter).
1261
1351
  this.compositeUniformBuffer = this.device.createBuffer({
1262
1352
  label: "composite view uniforms",
1263
- size: 32,
1353
+ // 6 × vec4f: (exposure, invGamma, _, _) · (bloom tint, intensity) ·
1354
+ // (bg rgb, mode) · camera right/up/forward basis for the 360 skybox ray.
1355
+ size: 96,
1264
1356
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
1265
1357
  });
1266
1358
  this.compositeBindGroupLayout = this.device.createBindGroupLayout({
@@ -1275,8 +1367,17 @@ export class Engine {
1275
1367
  { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: {} },
1276
1368
  // Filmic tone LUT (r16float, filterable) — sampled with the binding-2 sampler.
1277
1369
  { binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: {} },
1370
+ // 360 backdrop equirect (PhotoDome-style skybox) — 1×1 fallback when unset.
1371
+ { binding: 6, visibility: GPUShaderStage.FRAGMENT, texture: {} },
1278
1372
  ],
1279
1373
  });
1374
+ this.fallbackEquirectTexture = this.device.createTexture({
1375
+ label: "equirect fallback",
1376
+ size: [1, 1],
1377
+ format: "rgba8unorm",
1378
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
1379
+ });
1380
+ this.fallbackEquirectView = this.fallbackEquirectTexture.createView();
1280
1381
  const compositeShader = this.device.createShaderModule({
1281
1382
  label: "composite shader",
1282
1383
  code: COMPOSITE_SHADER_WGSL,
@@ -1615,19 +1716,8 @@ export class Engine {
1615
1716
  }));
1616
1717
  }
1617
1718
  // Composite reads bloomUp mip 0 (full pyramid collapsed); fallback to bloomDown mip 0 if no upsample level.
1618
- const compositeBloomView = this.bloomMipCount > 1 ? this.bloomUpMipViews[0] : this.bloomDownMipViews[0];
1619
- this.compositeBindGroup = this.device.createBindGroup({
1620
- label: "composite bind group",
1621
- layout: this.compositeBindGroupLayout,
1622
- entries: [
1623
- { binding: 0, resource: this.hdrResolveTexture.createView() },
1624
- { binding: 1, resource: compositeBloomView },
1625
- { binding: 2, resource: this.bloomSampler },
1626
- { binding: 3, resource: { buffer: this.compositeUniformBuffer } },
1627
- { binding: 4, resource: this.maskResolveView },
1628
- { binding: 5, resource: this.filmicLutView },
1629
- ],
1630
- });
1719
+ this.compositeBloomView = this.bloomMipCount > 1 ? this.bloomUpMipViews[0] : this.bloomDownMipViews[0];
1720
+ this.rebuildCompositeBindGroup();
1631
1721
  }
1632
1722
  this.writeCompositeViewUniforms();
1633
1723
  this.camera.aspect = width / height;
@@ -1991,6 +2081,7 @@ export class Engine {
1991
2081
  gridLineOpacity: 0.4,
1992
2082
  gridLineColor: new Vec3(0.85, 0.85, 0.85),
1993
2083
  noiseStrength: 0.05,
2084
+ opacity: 1.0,
1994
2085
  ...options,
1995
2086
  };
1996
2087
  this.createGroundGeometry(opts.width, opts.height);
@@ -2532,7 +2623,7 @@ export class Engine {
2532
2623
  this.device.queue.writeBuffer(this.groundIndexBuffer, 0, indices);
2533
2624
  }
2534
2625
  createShadowGroundResources(opts) {
2535
- const { diffuseColor, fadeStart, fadeEnd, shadowStrength, gridSpacing, gridLineWidth, gridLineOpacity, gridLineColor, noiseStrength, } = opts;
2626
+ const { diffuseColor, fadeStart, fadeEnd, shadowStrength, gridSpacing, gridLineWidth, gridLineOpacity, gridLineColor, noiseStrength, opacity, } = opts;
2536
2627
  // Shadow map is already created in setupPipelines()
2537
2628
  const gb = new Float32Array(16);
2538
2629
  gb[0] = diffuseColor.x;
@@ -2546,7 +2637,7 @@ export class Engine {
2546
2637
  gb[8] = gridLineWidth;
2547
2638
  gb[9] = gridLineOpacity;
2548
2639
  gb[10] = noiseStrength;
2549
- gb[11] = 0;
2640
+ gb[11] = opacity;
2550
2641
  gb[12] = gridLineColor.x;
2551
2642
  gb[13] = gridLineColor.y;
2552
2643
  gb[14] = gridLineColor.z;
@@ -3796,6 +3887,30 @@ export class Engine {
3796
3887
  this.cameraMatrixData[33] = cameraPos.y;
3797
3888
  this.cameraMatrixData[34] = cameraPos.z;
3798
3889
  this.device.queue.writeBuffer(this.cameraUniformBuffer, 0, this.cameraMatrixData);
3890
+ // 360 backdrop: the composite reconstructs each pixel's view ray from the
3891
+ // camera basis — refresh it every frame the skybox is active. The view matrix
3892
+ // is LEFT-HANDED (+Z forward, see Mat4.lookAtInto), so the world-space
3893
+ // right/up/FORWARD vectors are rows 0/1/2 of its rotation block directly
3894
+ // (column-major storage: row i = values[i], values[i+4], values[i+8]).
3895
+ if (this.backdropEquirectView && this.compositeUniformBuffer) {
3896
+ const v = viewMatrix.values;
3897
+ const u = this.compositeUniformData;
3898
+ const tanHalf = Math.tan((this.camera.fov ?? Math.PI / 4) / 2);
3899
+ const aspect = this.canvas.width / Math.max(1, this.canvas.height);
3900
+ u[12] = v[0];
3901
+ u[13] = v[4];
3902
+ u[14] = v[8];
3903
+ u[15] = tanHalf * aspect;
3904
+ u[16] = v[1];
3905
+ u[17] = v[5];
3906
+ u[18] = v[9];
3907
+ u[19] = tanHalf;
3908
+ u[20] = v[2];
3909
+ u[21] = v[6];
3910
+ u[22] = v[10];
3911
+ u[23] = 0;
3912
+ this.device.queue.writeBuffer(this.compositeUniformBuffer, 0, u);
3913
+ }
3799
3914
  }
3800
3915
  updateSkinMatrices() {
3801
3916
  this.forEachInstance((inst) => {
@@ -1,2 +1,2 @@
1
- export declare const COMPOSITE_SHADER_WGSL = "\n// Pipeline-override constant: the engine creates two composite pipelines, one\n// with APPLY_GAMMA=false (gamma=1 fast path) and one with APPLY_GAMMA=true.\n// The 'if (APPLY_GAMMA)' below is resolved at pipeline-compile time \u2014 the\n// dead branch is dropped by the shader compiler (no runtime branch, no pow\n// invocation on Safari's Metal backend in the common case).\noverride APPLY_GAMMA: bool = true;\n\n@group(0) @binding(0) var hdrTex: texture_2d<f32>;\n@group(0) @binding(1) var bloomTex: texture_2d<f32>; // bloomUpTexture mip 0 (full pyramid top)\n@group(0) @binding(2) var bloomSamp: sampler;\n@group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 2>;\n// Aux mask/alpha texture. .r = bloom mask (unused here; bloom blit uses it).\n// .g = accumulated canvas alpha (what hdr.a carried before the HDR format\n// became rg11b10ufloat). We unpremultiply HDR by this alpha for tonemap, then\n// re-premultiply the tonemapped color for output so the premultiplied canvas\n// alphaMode composites the WebGPU surface over the page background correctly.\n@group(0) @binding(4) var maskTex: texture_2d<f32>;\n// Filmic tone curve baked to a WIDTH\u00D71 r16float LUT (bakeFilmicLut on the CPU side).\n// Domain: log2(linear) mapped to [0,13]. Replaces the old array<f32,14> that was indexed\n// by a runtime u32 (a Metal-backend smell \u2014 dynamic local-array indexing lowers to a\n// per-invocation copy/switch) and interpolated piecewise-linearly (C0, so segment slope\n// discontinuities showed as Mach bands in smooth skin/shadow gradients). The LUT is a\n// monotone-cubic (Fritsch\u2013Carlson) fit through the same 14 anchors \u2014 same values, C1\n// continuity kills the banding \u2014 sampled with hardware linear filtering.\n@group(0) @binding(5) var filmicLut: texture_2d<f32>;\n// viewU[0] = (exposure, invGamma, _, _); viewU[1] = (tint.rgb, intensity)\n// invGamma = 1/gamma precomputed on CPU \u2014 avoids a per-pixel divide.\n\n// Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).\nconst FILMIC_LUT_W: f32 = 256.0;\n\nfn filmic(x: f32) -> f32 {\n // Reference checkpoints (Blender 3.6 Filmic MHC, sobotka/filmic-blender\n // look_medium-high-contrast.spi1d): linear 0.18 \u2192 ~0.395, linear 1.0 \u2192 ~0.83.\n // NOTE: version-pinned to Blender 3.6 \u2014 4.x defaults to AgX, not Filmic.\n let t = clamp(log2(max(x, 1e-10)) + 10.0, 0.0, 13.0);\n // Map t\u2208[0,13] to the texel-center of baked sample j = t\u00B7(W-1)/13.\n let u = (t * (FILMIC_LUT_W - 1.0) / 13.0 + 0.5) / FILMIC_LUT_W;\n // textureSampleLevel (explicit LOD, no derivatives) is legal in non-uniform flow.\n return textureSampleLevel(filmicLut, bloomSamp, vec2f(u, 0.5), 0.0).r;\n}\n\n@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n let x = f32((vi & 1u) << 2u) - 1.0;\n let y = f32((vi & 2u) << 1u) - 1.0;\n return vec4f(x, y, 0.0, 1.0);\n}\n\n@fragment fn fs(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {\n let coord = vec2<i32>(fragCoord.xy);\n let hdr = textureLoad(hdrTex, coord, 0);\n let alpha = textureLoad(maskTex, coord, 0).g;\n let a = max(alpha, 1e-6);\n let straight = hdr.rgb / a;\n let fullSz = vec2f(textureDimensions(hdrTex));\n // Bloom is at half-res (pyramid mip 0). Sampler interpolates back to full-res UVs.\n // fragCoord.xy is already at pixel center (e.g. 0.5, 0.5 for first pixel).\n let bloomUv = fragCoord.xy / max(fullSz, vec2f(1.0));\n let tint = viewU[1].xyz;\n let intensity = viewU[1].w;\n let bloom = textureSampleLevel(bloomTex, bloomSamp, bloomUv, 0.0).rgb * tint * intensity;\n let combined = straight + bloom;\n let exposed = combined * exp2(viewU[0].x);\n let tm = vec3f(filmic(exposed.r), filmic(exposed.g), filmic(exposed.b));\n var disp = max(tm, vec3f(0.0));\n if (APPLY_GAMMA) {\n disp = pow(disp, vec3f(viewU[0].y));\n }\n return vec4f(disp * alpha, alpha);\n}\n";
1
+ export declare const COMPOSITE_SHADER_WGSL = "\n// Pipeline-override constant: the engine creates two composite pipelines, one\n// with APPLY_GAMMA=false (gamma=1 fast path) and one with APPLY_GAMMA=true.\n// The 'if (APPLY_GAMMA)' below is resolved at pipeline-compile time \u2014 the\n// dead branch is dropped by the shader compiler (no runtime branch, no pow\n// invocation on Safari's Metal backend in the common case).\noverride APPLY_GAMMA: bool = true;\n\n@group(0) @binding(0) var hdrTex: texture_2d<f32>;\n@group(0) @binding(1) var bloomTex: texture_2d<f32>; // bloomUpTexture mip 0 (full pyramid top)\n@group(0) @binding(2) var bloomSamp: sampler;\n@group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 6>;\n// Aux mask/alpha texture. .r = bloom mask (unused here; bloom blit uses it).\n// .g = accumulated canvas alpha (what hdr.a carried before the HDR format\n// became rg11b10ufloat). We unpremultiply HDR by this alpha for tonemap, then\n// re-premultiply the tonemapped color for output so the premultiplied canvas\n// alphaMode composites the WebGPU surface over the page background correctly.\n@group(0) @binding(4) var maskTex: texture_2d<f32>;\n// Filmic tone curve baked to a WIDTH\u00D71 r16float LUT (bakeFilmicLut on the CPU side).\n// Domain: log2(linear) mapped to [0,13]. Replaces the old array<f32,14> that was indexed\n// by a runtime u32 (a Metal-backend smell \u2014 dynamic local-array indexing lowers to a\n// per-invocation copy/switch) and interpolated piecewise-linearly (C0, so segment slope\n// discontinuities showed as Mach bands in smooth skin/shadow gradients). The LUT is a\n// monotone-cubic (Fritsch\u2013Carlson) fit through the same 14 anchors \u2014 same values, C1\n// continuity kills the banding \u2014 sampled with hardware linear filtering.\n@group(0) @binding(5) var filmicLut: texture_2d<f32>;\n// viewU[0] = (exposure, invGamma, _, _); viewU[1] = (tint.rgb, intensity)\n// viewU[2] = (background.rgb, mode) \u2014 display-space sRGB, composited UNDER the\n// scene post-tonemap. mode: 0 transparent (DOM shows), 1 solid color,\n// 2 = 360 equirect skybox sampled by view ray.\n// viewU[3] = (camera right, tanHalfFov\u00B7aspect); viewU[4] = (camera up, tanHalfFov);\n// viewU[5] = (camera forward, _) \u2014 refreshed per frame while the skybox is active.\n// invGamma = 1/gamma precomputed on CPU \u2014 avoids a per-pixel divide.\n@group(0) @binding(6) var bgEquirect: texture_2d<f32>;\n\n// Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).\nconst FILMIC_LUT_W: f32 = 256.0;\n\nfn filmic(x: f32) -> f32 {\n // Reference checkpoints (Blender 3.6 Filmic MHC, sobotka/filmic-blender\n // look_medium-high-contrast.spi1d): linear 0.18 \u2192 ~0.395, linear 1.0 \u2192 ~0.83.\n // NOTE: version-pinned to Blender 3.6 \u2014 4.x defaults to AgX, not Filmic.\n let t = clamp(log2(max(x, 1e-10)) + 10.0, 0.0, 13.0);\n // Map t\u2208[0,13] to the texel-center of baked sample j = t\u00B7(W-1)/13.\n let u = (t * (FILMIC_LUT_W - 1.0) / 13.0 + 0.5) / FILMIC_LUT_W;\n // textureSampleLevel (explicit LOD, no derivatives) is legal in non-uniform flow.\n return textureSampleLevel(filmicLut, bloomSamp, vec2f(u, 0.5), 0.0).r;\n}\n\n@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n let x = f32((vi & 1u) << 2u) - 1.0;\n let y = f32((vi & 2u) << 1u) - 1.0;\n return vec4f(x, y, 0.0, 1.0);\n}\n\n@fragment fn fs(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {\n let coord = vec2<i32>(fragCoord.xy);\n let hdr = textureLoad(hdrTex, coord, 0);\n let alpha = textureLoad(maskTex, coord, 0).g;\n let a = max(alpha, 1e-6);\n let straight = hdr.rgb / a;\n let fullSz = vec2f(textureDimensions(hdrTex));\n // Bloom is at half-res (pyramid mip 0). Sampler interpolates back to full-res UVs.\n // fragCoord.xy is already at pixel center (e.g. 0.5, 0.5 for first pixel).\n let bloomUv = fragCoord.xy / max(fullSz, vec2f(1.0));\n let tint = viewU[1].xyz;\n let intensity = viewU[1].w;\n let bloom = textureSampleLevel(bloomTex, bloomSamp, bloomUv, 0.0).rgb * tint * intensity;\n let combined = straight + bloom;\n let exposed = combined * exp2(viewU[0].x);\n let tm = vec3f(filmic(exposed.r), filmic(exposed.g), filmic(exposed.b));\n var disp = max(tm, vec3f(0.0));\n if (APPLY_GAMMA) {\n disp = pow(disp, vec3f(viewU[0].y));\n }\n // Composite over the background in display space (premultiplied out).\n let bg = viewU[2];\n var bgRgb = bg.rgb;\n var bgA = select(0.0, 1.0, bg.w > 0.5);\n if (bg.w > 1.5) {\n // 360 equirect: rebuild this pixel's world-space view ray from the camera\n // basis, then latitude/longitude-map into the panorama. The dome sits at\n // infinity (no parallax) \u2014 PhotoDome-style, display-only.\n let ndc = vec2f(fragCoord.x / fullSz.x * 2.0 - 1.0, 1.0 - fragCoord.y / fullSz.y * 2.0);\n let dir = normalize(viewU[5].xyz + ndc.x * viewU[3].w * viewU[3].xyz + ndc.y * viewU[4].w * viewU[4].xyz);\n // LH world (+Z forward): longitude = atan2(x, z), Babylon-PhotoDome convention.\n let su = 0.5 + atan2(dir.x, dir.z) * 0.15915494309; // 1/(2\u03C0)\n let sv = 0.5 - asin(clamp(dir.y, -1.0, 1.0)) * 0.31830988618; // 1/\u03C0\n bgRgb = textureSampleLevel(bgEquirect, bloomSamp, vec2f(su, sv), 0.0).rgb;\n }\n return vec4f(disp * alpha + bgRgb * bgA * (1.0 - alpha), alpha + bgA * (1.0 - alpha));\n}\n";
2
2
  //# sourceMappingURL=composite.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,qBAAqB,s0HAuEjC,CAAA"}
1
+ {"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,qBAAqB,yvKA4FjC,CAAA"}
@@ -11,7 +11,7 @@ override APPLY_GAMMA: bool = true;
11
11
  @group(0) @binding(0) var hdrTex: texture_2d<f32>;
12
12
  @group(0) @binding(1) var bloomTex: texture_2d<f32>; // bloomUpTexture mip 0 (full pyramid top)
13
13
  @group(0) @binding(2) var bloomSamp: sampler;
14
- @group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 2>;
14
+ @group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 6>;
15
15
  // Aux mask/alpha texture. .r = bloom mask (unused here; bloom blit uses it).
16
16
  // .g = accumulated canvas alpha (what hdr.a carried before the HDR format
17
17
  // became rg11b10ufloat). We unpremultiply HDR by this alpha for tonemap, then
@@ -27,7 +27,13 @@ override APPLY_GAMMA: bool = true;
27
27
  // continuity kills the banding — sampled with hardware linear filtering.
28
28
  @group(0) @binding(5) var filmicLut: texture_2d<f32>;
29
29
  // viewU[0] = (exposure, invGamma, _, _); viewU[1] = (tint.rgb, intensity)
30
+ // viewU[2] = (background.rgb, mode) — display-space sRGB, composited UNDER the
31
+ // scene post-tonemap. mode: 0 transparent (DOM shows), 1 solid color,
32
+ // 2 = 360 equirect skybox sampled by view ray.
33
+ // viewU[3] = (camera right, tanHalfFov·aspect); viewU[4] = (camera up, tanHalfFov);
34
+ // viewU[5] = (camera forward, _) — refreshed per frame while the skybox is active.
30
35
  // invGamma = 1/gamma precomputed on CPU — avoids a per-pixel divide.
36
+ @group(0) @binding(6) var bgEquirect: texture_2d<f32>;
31
37
 
32
38
  // Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).
33
39
  const FILMIC_LUT_W: f32 = 256.0;
@@ -69,6 +75,21 @@ fn filmic(x: f32) -> f32 {
69
75
  if (APPLY_GAMMA) {
70
76
  disp = pow(disp, vec3f(viewU[0].y));
71
77
  }
72
- return vec4f(disp * alpha, alpha);
78
+ // Composite over the background in display space (premultiplied out).
79
+ let bg = viewU[2];
80
+ var bgRgb = bg.rgb;
81
+ var bgA = select(0.0, 1.0, bg.w > 0.5);
82
+ if (bg.w > 1.5) {
83
+ // 360 equirect: rebuild this pixel's world-space view ray from the camera
84
+ // basis, then latitude/longitude-map into the panorama. The dome sits at
85
+ // infinity (no parallax) — PhotoDome-style, display-only.
86
+ let ndc = vec2f(fragCoord.x / fullSz.x * 2.0 - 1.0, 1.0 - fragCoord.y / fullSz.y * 2.0);
87
+ let dir = normalize(viewU[5].xyz + ndc.x * viewU[3].w * viewU[3].xyz + ndc.y * viewU[4].w * viewU[4].xyz);
88
+ // LH world (+Z forward): longitude = atan2(x, z), Babylon-PhotoDome convention.
89
+ let su = 0.5 + atan2(dir.x, dir.z) * 0.15915494309; // 1/(2π)
90
+ let sv = 0.5 - asin(clamp(dir.y, -1.0, 1.0)) * 0.31830988618; // 1/π
91
+ bgRgb = textureSampleLevel(bgEquirect, bloomSamp, vec2f(su, sv), 0.0).rgb;
92
+ }
93
+ return vec4f(disp * alpha + bgRgb * bgA * (1.0 - alpha), alpha + bgA * (1.0 - alpha));
73
94
  }
74
95
  `;
@@ -1,2 +1,2 @@
1
- export declare const GROUND_SHADOW_SHADER_WGSL = "\nstruct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };\nstruct Light { direction: vec4f, color: vec4f, };\nstruct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };\nstruct GroundShadowMat {\n diffuseColor: vec3f, fadeStart: f32,\n fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,\n gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, _pad: f32,\n gridLineColor: vec3f, _pad2: f32,\n};\nstruct LightVP { viewProj: mat4x4f, };\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n@group(0) @binding(1) var<uniform> light: LightUniforms;\n@group(0) @binding(2) var shadowMap: texture_depth_2d;\n@group(0) @binding(3) var shadowSampler: sampler_comparison;\n@group(0) @binding(4) var<uniform> material: GroundShadowMat;\n@group(0) @binding(5) var<uniform> lightVP: LightVP;\n\nfn hash2(p: vec2f) -> f32 {\n var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);\n p3 += dot(p3, vec3f(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));\n return fract((p3.x + p3.y) * p3.z);\n}\nfn valueNoise(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x),\n mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);\n}\nfn fbmNoise(p: vec2f) -> f32 {\n var v = 0.0;\n var a = 0.5;\n var pp = p;\n for (var i = 0; i < 4; i++) {\n v += a * valueNoise(pp);\n pp *= 2.0;\n a *= 0.5;\n }\n return v;\n}\n\nstruct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };\n@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {\n var o: VO; o.worldPos = position; o.normal = normal;\n o.position = camera.projection * camera.view * vec4f(position, 1.0); return o;\n}\nstruct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };\n@fragment fn fs(i: VO) -> FSOut {\n let n = normalize(i.normal);\n let centerDist = length(i.worldPos.xz);\n let edgeFade = 1.0 - smoothstep(0.0, 1.0, clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0));\n\n let lclip = lightVP.viewProj * vec4f(i.worldPos, 1.0);\n let ndc = lclip.xyz / max(lclip.w, 1e-6);\n let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\n let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));\n let st = material.pcfTexel;\n let compareZ = ndc.z - 0.0035;\n var vis = 0.0;\n for (var y = -2; y <= 2; y++) {\n for (var x = -2; x <= 2; x++) {\n vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(f32(x), f32(y)) * st, compareZ);\n }\n }\n vis *= 0.04;\n\n // Frosted/matte micro-texture\n let noiseVal = fbmNoise(i.worldPos.xz * 3.0);\n let noiseTint = 1.0 + (noiseVal - 0.5) * material.noiseStrength;\n\n // Grid lines \u2014 anti-aliased via screen-space derivatives\n let gp = i.worldPos.xz / material.gridSpacing;\n let gridFrac = abs(fract(gp - 0.5) - 0.5);\n let gridDeriv = fwidth(gp);\n let halfLine = material.gridLineWidth * 0.5;\n let gridLine = 1.0 - min(\n smoothstep(halfLine - gridDeriv.x, halfLine + gridDeriv.x, gridFrac.x),\n smoothstep(halfLine - gridDeriv.y, halfLine + gridDeriv.y, gridFrac.y)\n );\n let sun = light.ambientColor.xyz + light.lights[0].color.xyz * light.lights[0].color.w * max(dot(n, -light.lights[0].direction.xyz), 0.0);\n let dark = (1.0 - vis) * material.shadowStrength;\n var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);\n baseColor *= noiseTint;\n let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);\n var out: FSOut;\n out.color = vec4f(finalColor * edgeFade, edgeFade);\n // mask.r = 0: ground never contributes to bloom. mask.g = 1.0 with src.a =\n // edgeFade turns the aux blend into alpha-over, so the drawable alpha fades\n // from edgeFade at the center to 0 at the radial edge \u2014 letting the page\n // background show through under the premultiplied canvas alphaMode.\n out.mask = vec4f(0.0, 1.0, 0.0, edgeFade);\n return out;\n}\n";
1
+ export declare const GROUND_SHADOW_SHADER_WGSL = "\nstruct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };\nstruct Light { direction: vec4f, color: vec4f, };\nstruct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };\nstruct GroundShadowMat {\n diffuseColor: vec3f, fadeStart: f32,\n fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,\n gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, opacity: f32,\n gridLineColor: vec3f, _pad2: f32,\n};\nstruct LightVP { viewProj: mat4x4f, };\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n@group(0) @binding(1) var<uniform> light: LightUniforms;\n@group(0) @binding(2) var shadowMap: texture_depth_2d;\n@group(0) @binding(3) var shadowSampler: sampler_comparison;\n@group(0) @binding(4) var<uniform> material: GroundShadowMat;\n@group(0) @binding(5) var<uniform> lightVP: LightVP;\n\nfn hash2(p: vec2f) -> f32 {\n var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);\n p3 += dot(p3, vec3f(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));\n return fract((p3.x + p3.y) * p3.z);\n}\nfn valueNoise(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x),\n mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);\n}\nfn fbmNoise(p: vec2f) -> f32 {\n var v = 0.0;\n var a = 0.5;\n var pp = p;\n for (var i = 0; i < 4; i++) {\n v += a * valueNoise(pp);\n pp *= 2.0;\n a *= 0.5;\n }\n return v;\n}\n\nstruct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };\n@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {\n var o: VO; o.worldPos = position; o.normal = normal;\n o.position = camera.projection * camera.view * vec4f(position, 1.0); return o;\n}\nstruct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };\n@fragment fn fs(i: VO) -> FSOut {\n let n = normalize(i.normal);\n let centerDist = length(i.worldPos.xz);\n let edgeFade = 1.0 - smoothstep(0.0, 1.0, clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0));\n\n let lclip = lightVP.viewProj * vec4f(i.worldPos, 1.0);\n let ndc = lclip.xyz / max(lclip.w, 1e-6);\n let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\n let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));\n let st = material.pcfTexel;\n let compareZ = ndc.z - 0.0035;\n var vis = 0.0;\n for (var y = -2; y <= 2; y++) {\n for (var x = -2; x <= 2; x++) {\n vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(f32(x), f32(y)) * st, compareZ);\n }\n }\n vis *= 0.04;\n\n // Frosted/matte micro-texture\n let noiseVal = fbmNoise(i.worldPos.xz * 3.0);\n let noiseTint = 1.0 + (noiseVal - 0.5) * material.noiseStrength;\n\n // Grid lines \u2014 anti-aliased via screen-space derivatives\n let gp = i.worldPos.xz / material.gridSpacing;\n let gridFrac = abs(fract(gp - 0.5) - 0.5);\n let gridDeriv = fwidth(gp);\n let halfLine = material.gridLineWidth * 0.5;\n let gridLine = 1.0 - min(\n smoothstep(halfLine - gridDeriv.x, halfLine + gridDeriv.x, gridFrac.x),\n smoothstep(halfLine - gridDeriv.y, halfLine + gridDeriv.y, gridFrac.y)\n );\n let sun = light.ambientColor.xyz + light.lights[0].color.xyz * light.lights[0].color.w * max(dot(n, -light.lights[0].direction.xyz), 0.0);\n let dark = (1.0 - vis) * material.shadowStrength;\n var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);\n baseColor *= noiseTint;\n let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);\n // Whole-ground opacity fades the SURFACE (color, grid) but the shadow stays \u2014\n // as opacity drops, the received shadow becomes a translucent dark layer\n // (Blender's Shadow Catcher), so models still feel grounded on a photo or\n // 360 backdrop. At opacity 1 this reduces exactly to the plain surface.\n let surfA = edgeFade * material.opacity;\n let catchA = dark * 0.65 * edgeFade * (1.0 - material.opacity);\n let outA = surfA + catchA;\n var out: FSOut;\n out.color = vec4f(finalColor * surfA, outA);\n // mask.r = 0: ground never contributes to bloom. mask.g = 1.0 with src.a =\n // outA turns the aux blend into alpha-over, so the drawable alpha goes\n // from outA at the center to 0 at the radial edge \u2014 letting the page\n // background show through under the premultiplied canvas alphaMode.\n out.mask = vec4f(0.0, 1.0, 0.0, outA);\n return out;\n}\n";
2
2
  //# sourceMappingURL=ground.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,ijIA8FrC,CAAA"}
1
+ {"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,2+IAqGrC,CAAA"}
@@ -7,7 +7,7 @@ struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };
7
7
  struct GroundShadowMat {
8
8
  diffuseColor: vec3f, fadeStart: f32,
9
9
  fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,
10
- gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, _pad: f32,
10
+ gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, opacity: f32,
11
11
  gridLineColor: vec3f, _pad2: f32,
12
12
  };
13
13
  struct LightVP { viewProj: mat4x4f, };
@@ -85,13 +85,20 @@ struct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };
85
85
  var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);
86
86
  baseColor *= noiseTint;
87
87
  let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);
88
+ // Whole-ground opacity fades the SURFACE (color, grid) but the shadow stays —
89
+ // as opacity drops, the received shadow becomes a translucent dark layer
90
+ // (Blender's Shadow Catcher), so models still feel grounded on a photo or
91
+ // 360 backdrop. At opacity 1 this reduces exactly to the plain surface.
92
+ let surfA = edgeFade * material.opacity;
93
+ let catchA = dark * 0.65 * edgeFade * (1.0 - material.opacity);
94
+ let outA = surfA + catchA;
88
95
  var out: FSOut;
89
- out.color = vec4f(finalColor * edgeFade, edgeFade);
96
+ out.color = vec4f(finalColor * surfA, outA);
90
97
  // mask.r = 0: ground never contributes to bloom. mask.g = 1.0 with src.a =
91
- // edgeFade turns the aux blend into alpha-over, so the drawable alpha fades
92
- // from edgeFade at the center to 0 at the radial edge — letting the page
98
+ // outA turns the aux blend into alpha-over, so the drawable alpha goes
99
+ // from outA at the center to 0 at the radial edge — letting the page
93
100
  // background show through under the premultiplied canvas alphaMode.
94
- out.mask = vec4f(0.0, 1.0, 0.0, edgeFade);
101
+ out.mask = vec4f(0.0, 1.0, 0.0, outA);
95
102
  return out;
96
103
  }
97
104
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "description": "A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/engine.ts CHANGED
@@ -301,6 +301,9 @@ export type EngineOptions = {
301
301
  world?: WorldOptions
302
302
  sun?: SunOptions
303
303
  camera?: CameraOptions
304
+ /** Canvas background (display-space sRGB 0–1), composited under the scene after
305
+ * tonemapping. Omit/null = transparent canvas (see setBackgroundColor). */
306
+ background?: Vec3 | null
304
307
  /** Initial EEVEE-style bloom; tune at runtime with `setBloomOptions`. */
305
308
  bloom?: Partial<BloomOptions>
306
309
  /** View transform (exposure/gamma) applied in composite before/after Filmic. */
@@ -542,7 +545,15 @@ export class Engine {
542
545
  private compositeBindGroup!: GPUBindGroup
543
546
  private compositeUniformBuffer!: GPUBuffer
544
547
  // [exposure, invGamma, _, _, bloomTint.x, bloomTint.y, bloomTint.z, bloomIntensity]
545
- private readonly compositeUniformData = new Float32Array(8)
548
+ private readonly compositeUniformData = new Float32Array(24)
549
+ /** Composite background (display-space sRGB 0–1) — null = transparent canvas. */
550
+ private backgroundColor: Vec3 | null = null
551
+ // 360 backdrop (equirectangular skybox, sampled by view ray in composite).
552
+ private backdropEquirectTexture: GPUTexture | null = null
553
+ private backdropEquirectView: GPUTextureView | null = null
554
+ private fallbackEquirectTexture!: GPUTexture
555
+ private fallbackEquirectView!: GPUTextureView
556
+ private compositeBloomView: GPUTextureView | null = null
546
557
 
547
558
  // EEVEE-style bloom pyramid (mirrors Blender 3.6 effect_bloom_frag.glsl):
548
559
  // blit (HDR → half-res, 4-tap Karis + soft threshold/knee)
@@ -674,6 +685,8 @@ export class Engine {
674
685
  this.onGizmoDrag = options?.onGizmoDrag
675
686
  this.bloomSettings = Engine.mergeBloomDefaults(options?.bloom)
676
687
  this.viewTransform = Engine.mergeViewTransformDefaults(options?.view)
688
+ const bg = options?.background
689
+ this.backgroundColor = bg ? new Vec3(bg.x, bg.y, bg.z) : null
677
690
  }
678
691
 
679
692
  /** Merge partial bloom with EEVEE defaults (same as constructor). */
@@ -745,9 +758,91 @@ export class Engine {
745
758
  u[5] = b.color.y
746
759
  u[6] = b.color.z
747
760
  u[7] = effIntensity
761
+ // Background composited UNDER the scene in display space (post-tonemap), so it
762
+ // matches a CSS color of the same value exactly. Mode (u[11]): 0 = transparent
763
+ // (DOM shows), 1 = solid color, 2 = 360 equirect (sampled by view ray; the
764
+ // camera basis at u[12..23] is refreshed per frame by updateCameraUniforms).
765
+ const bg = this.backgroundColor
766
+ u[8] = bg?.x ?? 0
767
+ u[9] = bg?.y ?? 0
768
+ u[10] = bg?.z ?? 0
769
+ u[11] = this.backdropEquirectView ? 2 : bg ? 1 : 0
748
770
  this.device.queue.writeBuffer(this.compositeUniformBuffer, 0, u)
749
771
  }
750
772
 
773
+ /**
774
+ * Set the canvas background color (display-space sRGB, 0–1 per channel — the
775
+ * same value a CSS background of that color shows, applied after tonemapping).
776
+ * Pass null for a transparent canvas (the page/DOM shows through — e.g. when a
777
+ * backdrop image layer sits behind the canvas). Applies on the next frame.
778
+ * A 360 backdrop (setBackdropEquirect) takes precedence while set.
779
+ */
780
+ setBackgroundColor(color: Vec3 | null): void {
781
+ this.backgroundColor = color ? new Vec3(color.x, color.y, color.z) : null
782
+ if (this.device && this.compositeUniformBuffer) this.writeCompositeViewUniforms()
783
+ }
784
+
785
+ private rebuildCompositeBindGroup(): void {
786
+ if (!this.device || !this.hdrResolveTexture || !this.compositeBloomView) return
787
+ this.compositeBindGroup = this.device.createBindGroup({
788
+ label: "composite bind group",
789
+ layout: this.compositeBindGroupLayout,
790
+ entries: [
791
+ { binding: 0, resource: this.hdrResolveTexture.createView() },
792
+ { binding: 1, resource: this.compositeBloomView },
793
+ { binding: 2, resource: this.bloomSampler },
794
+ { binding: 3, resource: { buffer: this.compositeUniformBuffer } },
795
+ { binding: 4, resource: this.maskResolveView },
796
+ { binding: 5, resource: this.filmicLutView },
797
+ { binding: 6, resource: this.backdropEquirectView ?? this.fallbackEquirectView },
798
+ ],
799
+ })
800
+ }
801
+
802
+ /**
803
+ * Set a 360° backdrop from an equirectangular (2:1) image — a PhotoDome-style
804
+ * skybox at infinity, sampled per-pixel by view direction so it follows the
805
+ * camera. Display-only: composited in display space behind the scene, it never
806
+ * affects lighting, bloom, or tonemapping. Pass null to remove (the background
807
+ * color, or transparency, takes over again).
808
+ */
809
+ setBackdropEquirect(source: ImageBitmap | HTMLImageElement | HTMLCanvasElement | null): void {
810
+ this.backdropEquirectTexture?.destroy()
811
+ this.backdropEquirectTexture = null
812
+ this.backdropEquirectView = null
813
+ if (source && this.device) {
814
+ let width = Math.max(1, "naturalWidth" in source ? source.naturalWidth : source.width)
815
+ let height = Math.max(1, "naturalHeight" in source ? source.naturalHeight : source.height)
816
+ let upload: ImageBitmap | HTMLImageElement | HTMLCanvasElement | OffscreenCanvas = source
817
+ // Panoramas routinely exceed maxTextureDimension2D (e.g. 10000×5000 vs the
818
+ // default 8192) — quietly downscale to fit rather than surfacing an error.
819
+ const limit = this.device.limits.maxTextureDimension2D
820
+ if (width > limit || height > limit) {
821
+ const scale = Math.min(limit / width, limit / height)
822
+ const w = Math.max(1, Math.floor(width * scale))
823
+ const h = Math.max(1, Math.floor(height * scale))
824
+ const canvas = new OffscreenCanvas(w, h)
825
+ const cx = canvas.getContext("2d")!
826
+ cx.imageSmoothingQuality = "high"
827
+ cx.drawImage(source, 0, 0, w, h)
828
+ upload = canvas
829
+ width = w
830
+ height = h
831
+ }
832
+ const tex = this.device.createTexture({
833
+ label: "backdrop equirect",
834
+ size: [width, height],
835
+ format: "rgba8unorm",
836
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
837
+ })
838
+ this.device.queue.copyExternalImageToTexture({ source: upload }, { texture: tex }, [width, height])
839
+ this.backdropEquirectTexture = tex
840
+ this.backdropEquirectView = tex.createView()
841
+ }
842
+ this.rebuildCompositeBindGroup()
843
+ if (this.device && this.compositeUniformBuffer) this.writeCompositeViewUniforms()
844
+ }
845
+
751
846
  /** Patch bloom; GPU uniforms update immediately if `init()` has run. */
752
847
  setBloomOptions(patch: Partial<BloomOptions>): void {
753
848
  const b = this.bloomSettings
@@ -1545,7 +1640,9 @@ export class Engine {
1545
1640
  // mirroring EEVEE where bloom color/intensity are combine-stage params, not prefilter).
1546
1641
  this.compositeUniformBuffer = this.device.createBuffer({
1547
1642
  label: "composite view uniforms",
1548
- size: 32,
1643
+ // 6 × vec4f: (exposure, invGamma, _, _) · (bloom tint, intensity) ·
1644
+ // (bg rgb, mode) · camera right/up/forward basis for the 360 skybox ray.
1645
+ size: 96,
1549
1646
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
1550
1647
  })
1551
1648
  this.compositeBindGroupLayout = this.device.createBindGroupLayout({
@@ -1560,8 +1657,17 @@ export class Engine {
1560
1657
  { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: {} },
1561
1658
  // Filmic tone LUT (r16float, filterable) — sampled with the binding-2 sampler.
1562
1659
  { binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: {} },
1660
+ // 360 backdrop equirect (PhotoDome-style skybox) — 1×1 fallback when unset.
1661
+ { binding: 6, visibility: GPUShaderStage.FRAGMENT, texture: {} },
1563
1662
  ],
1564
1663
  })
1664
+ this.fallbackEquirectTexture = this.device.createTexture({
1665
+ label: "equirect fallback",
1666
+ size: [1, 1],
1667
+ format: "rgba8unorm",
1668
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
1669
+ })
1670
+ this.fallbackEquirectView = this.fallbackEquirectTexture.createView()
1565
1671
 
1566
1672
  const compositeShader = this.device.createShaderModule({
1567
1673
  label: "composite shader",
@@ -1949,19 +2055,8 @@ export class Engine {
1949
2055
  )
1950
2056
  }
1951
2057
  // Composite reads bloomUp mip 0 (full pyramid collapsed); fallback to bloomDown mip 0 if no upsample level.
1952
- const compositeBloomView = this.bloomMipCount > 1 ? this.bloomUpMipViews[0] : this.bloomDownMipViews[0]
1953
- this.compositeBindGroup = this.device.createBindGroup({
1954
- label: "composite bind group",
1955
- layout: this.compositeBindGroupLayout,
1956
- entries: [
1957
- { binding: 0, resource: this.hdrResolveTexture.createView() },
1958
- { binding: 1, resource: compositeBloomView },
1959
- { binding: 2, resource: this.bloomSampler },
1960
- { binding: 3, resource: { buffer: this.compositeUniformBuffer } },
1961
- { binding: 4, resource: this.maskResolveView },
1962
- { binding: 5, resource: this.filmicLutView },
1963
- ],
1964
- })
2058
+ this.compositeBloomView = this.bloomMipCount > 1 ? this.bloomUpMipViews[0] : this.bloomDownMipViews[0]
2059
+ this.rebuildCompositeBindGroup()
1965
2060
  }
1966
2061
 
1967
2062
  this.writeCompositeViewUniforms()
@@ -2357,6 +2452,8 @@ export class Engine {
2357
2452
  gridLineOpacity?: number
2358
2453
  gridLineColor?: Vec3
2359
2454
  noiseStrength?: number
2455
+ /** Whole-ground opacity, 0–1 (multiplies the radial edge fade). Default 1. */
2456
+ opacity?: number
2360
2457
  }): void {
2361
2458
  const opts = {
2362
2459
  width: 160,
@@ -2370,6 +2467,7 @@ export class Engine {
2370
2467
  gridLineOpacity: 0.4,
2371
2468
  gridLineColor: new Vec3(0.85, 0.85, 0.85),
2372
2469
  noiseStrength: 0.05,
2470
+ opacity: 1.0,
2373
2471
  ...options,
2374
2472
  }
2375
2473
  this.createGroundGeometry(opts.width, opts.height)
@@ -2990,6 +3088,7 @@ export class Engine {
2990
3088
  gridLineOpacity: number
2991
3089
  gridLineColor: Vec3
2992
3090
  noiseStrength: number
3091
+ opacity: number
2993
3092
  }) {
2994
3093
  const {
2995
3094
  diffuseColor,
@@ -3001,6 +3100,7 @@ export class Engine {
3001
3100
  gridLineOpacity,
3002
3101
  gridLineColor,
3003
3102
  noiseStrength,
3103
+ opacity,
3004
3104
  } = opts
3005
3105
  // Shadow map is already created in setupPipelines()
3006
3106
  const gb = new Float32Array(16)
@@ -3015,7 +3115,7 @@ export class Engine {
3015
3115
  gb[8] = gridLineWidth
3016
3116
  gb[9] = gridLineOpacity
3017
3117
  gb[10] = noiseStrength
3018
- gb[11] = 0
3118
+ gb[11] = opacity
3019
3119
  gb[12] = gridLineColor.x
3020
3120
  gb[13] = gridLineColor.y
3021
3121
  gb[14] = gridLineColor.z
@@ -4577,6 +4677,31 @@ export class Engine {
4577
4677
  this.cameraMatrixData[33] = cameraPos.y
4578
4678
  this.cameraMatrixData[34] = cameraPos.z
4579
4679
  this.device.queue.writeBuffer(this.cameraUniformBuffer, 0, this.cameraMatrixData)
4680
+
4681
+ // 360 backdrop: the composite reconstructs each pixel's view ray from the
4682
+ // camera basis — refresh it every frame the skybox is active. The view matrix
4683
+ // is LEFT-HANDED (+Z forward, see Mat4.lookAtInto), so the world-space
4684
+ // right/up/FORWARD vectors are rows 0/1/2 of its rotation block directly
4685
+ // (column-major storage: row i = values[i], values[i+4], values[i+8]).
4686
+ if (this.backdropEquirectView && this.compositeUniformBuffer) {
4687
+ const v = viewMatrix.values
4688
+ const u = this.compositeUniformData
4689
+ const tanHalf = Math.tan((this.camera.fov ?? Math.PI / 4) / 2)
4690
+ const aspect = this.canvas.width / Math.max(1, this.canvas.height)
4691
+ u[12] = v[0]
4692
+ u[13] = v[4]
4693
+ u[14] = v[8]
4694
+ u[15] = tanHalf * aspect
4695
+ u[16] = v[1]
4696
+ u[17] = v[5]
4697
+ u[18] = v[9]
4698
+ u[19] = tanHalf
4699
+ u[20] = v[2]
4700
+ u[21] = v[6]
4701
+ u[22] = v[10]
4702
+ u[23] = 0
4703
+ this.device.queue.writeBuffer(this.compositeUniformBuffer, 0, u)
4704
+ }
4580
4705
  }
4581
4706
 
4582
4707
  private updateSkinMatrices() {
@@ -12,7 +12,7 @@ override APPLY_GAMMA: bool = true;
12
12
  @group(0) @binding(0) var hdrTex: texture_2d<f32>;
13
13
  @group(0) @binding(1) var bloomTex: texture_2d<f32>; // bloomUpTexture mip 0 (full pyramid top)
14
14
  @group(0) @binding(2) var bloomSamp: sampler;
15
- @group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 2>;
15
+ @group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 6>;
16
16
  // Aux mask/alpha texture. .r = bloom mask (unused here; bloom blit uses it).
17
17
  // .g = accumulated canvas alpha (what hdr.a carried before the HDR format
18
18
  // became rg11b10ufloat). We unpremultiply HDR by this alpha for tonemap, then
@@ -28,7 +28,13 @@ override APPLY_GAMMA: bool = true;
28
28
  // continuity kills the banding — sampled with hardware linear filtering.
29
29
  @group(0) @binding(5) var filmicLut: texture_2d<f32>;
30
30
  // viewU[0] = (exposure, invGamma, _, _); viewU[1] = (tint.rgb, intensity)
31
+ // viewU[2] = (background.rgb, mode) — display-space sRGB, composited UNDER the
32
+ // scene post-tonemap. mode: 0 transparent (DOM shows), 1 solid color,
33
+ // 2 = 360 equirect skybox sampled by view ray.
34
+ // viewU[3] = (camera right, tanHalfFov·aspect); viewU[4] = (camera up, tanHalfFov);
35
+ // viewU[5] = (camera forward, _) — refreshed per frame while the skybox is active.
31
36
  // invGamma = 1/gamma precomputed on CPU — avoids a per-pixel divide.
37
+ @group(0) @binding(6) var bgEquirect: texture_2d<f32>;
32
38
 
33
39
  // Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).
34
40
  const FILMIC_LUT_W: f32 = 256.0;
@@ -70,6 +76,21 @@ fn filmic(x: f32) -> f32 {
70
76
  if (APPLY_GAMMA) {
71
77
  disp = pow(disp, vec3f(viewU[0].y));
72
78
  }
73
- return vec4f(disp * alpha, alpha);
79
+ // Composite over the background in display space (premultiplied out).
80
+ let bg = viewU[2];
81
+ var bgRgb = bg.rgb;
82
+ var bgA = select(0.0, 1.0, bg.w > 0.5);
83
+ if (bg.w > 1.5) {
84
+ // 360 equirect: rebuild this pixel's world-space view ray from the camera
85
+ // basis, then latitude/longitude-map into the panorama. The dome sits at
86
+ // infinity (no parallax) — PhotoDome-style, display-only.
87
+ let ndc = vec2f(fragCoord.x / fullSz.x * 2.0 - 1.0, 1.0 - fragCoord.y / fullSz.y * 2.0);
88
+ let dir = normalize(viewU[5].xyz + ndc.x * viewU[3].w * viewU[3].xyz + ndc.y * viewU[4].w * viewU[4].xyz);
89
+ // LH world (+Z forward): longitude = atan2(x, z), Babylon-PhotoDome convention.
90
+ let su = 0.5 + atan2(dir.x, dir.z) * 0.15915494309; // 1/(2π)
91
+ let sv = 0.5 - asin(clamp(dir.y, -1.0, 1.0)) * 0.31830988618; // 1/π
92
+ bgRgb = textureSampleLevel(bgEquirect, bloomSamp, vec2f(su, sv), 0.0).rgb;
93
+ }
94
+ return vec4f(disp * alpha + bgRgb * bgA * (1.0 - alpha), alpha + bgA * (1.0 - alpha));
74
95
  }
75
96
  `
@@ -8,7 +8,7 @@ struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };
8
8
  struct GroundShadowMat {
9
9
  diffuseColor: vec3f, fadeStart: f32,
10
10
  fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,
11
- gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, _pad: f32,
11
+ gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, opacity: f32,
12
12
  gridLineColor: vec3f, _pad2: f32,
13
13
  };
14
14
  struct LightVP { viewProj: mat4x4f, };
@@ -86,13 +86,20 @@ struct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };
86
86
  var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);
87
87
  baseColor *= noiseTint;
88
88
  let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);
89
+ // Whole-ground opacity fades the SURFACE (color, grid) but the shadow stays —
90
+ // as opacity drops, the received shadow becomes a translucent dark layer
91
+ // (Blender's Shadow Catcher), so models still feel grounded on a photo or
92
+ // 360 backdrop. At opacity 1 this reduces exactly to the plain surface.
93
+ let surfA = edgeFade * material.opacity;
94
+ let catchA = dark * 0.65 * edgeFade * (1.0 - material.opacity);
95
+ let outA = surfA + catchA;
89
96
  var out: FSOut;
90
- out.color = vec4f(finalColor * edgeFade, edgeFade);
97
+ out.color = vec4f(finalColor * surfA, outA);
91
98
  // mask.r = 0: ground never contributes to bloom. mask.g = 1.0 with src.a =
92
- // edgeFade turns the aux blend into alpha-over, so the drawable alpha fades
93
- // from edgeFade at the center to 0 at the radial edge — letting the page
99
+ // outA turns the aux blend into alpha-over, so the drawable alpha goes
100
+ // from outA at the center to 0 at the radial edge — letting the page
94
101
  // background show through under the premultiplied canvas alphaMode.
95
- out.mask = vec4f(0.0, 1.0, 0.0, edgeFade);
102
+ out.mask = vec4f(0.0, 1.0, 0.0, outA);
96
103
  return out;
97
104
  }
98
105
  `