tosijs-3d 0.8.12 → 0.8.14

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/CHANGELOG.md CHANGED
@@ -6,6 +6,60 @@ All notable changes to **tosijs-3d**. This project is pre-1.0 (`0.x`), so minor
6
6
  versions may carry breaking peer-dependency changes — each is called out in a
7
7
  **⚠️ Breaking** block in its version section below, with what a consumer must do.
8
8
 
9
+ ## 0.8.14
10
+
11
+ **Ambient occlusion that works in a headset, and it is the default.**
12
+
13
+ ### Changed
14
+
15
+ - **`ssaoMethod` defaults to `projected`.** Scenes with `ssao="on"` or `"auto"`
16
+ now get the projected method flat, which keeps antialiasing and concentrates
17
+ its darkening in contacts and corners. **`ssao="on"` now also runs in a
18
+ headset**, where it used to switch itself off. `auto` still never runs in a
19
+ headset. Set `ssaoMethod="screen"` for the previous behaviour exactly.
20
+
21
+ ### Added
22
+
23
+ - **Projected ambient occlusion** (`ssaoMethod`, `ssaoRate` on `<tosi-b3d>`):
24
+ occlusion computed once from a camera between the eyes and looked up by every
25
+ material by world position. It is correct in stereo, uses no post-process,
26
+ and is redrawn `ssaoRate` times a second (default 30) instead of every frame.
27
+ Checked on a Quest in the SSAO demo: stereo, and no measurable change in
28
+ frame rate or CPU time. Not yet measured on a heavy scene.
29
+ - Projected occlusion follows **shader vertex animation**: the crowd is
30
+ occluded in its animated pose, because every mesh draws its own depth with
31
+ its own material.
32
+ - The Perf Stats readout is **live** and shows frame interval, CPU time in
33
+ `scene.render()`, and draw calls. It was three labels built once, so the fps
34
+ was whatever it had been when the popup opened.
35
+ - The SSAO demo has a bust (on the CDN at `tosijs-3d/ariosto-bust.glb`) and a
36
+ `method` selector; the scene loader demo has shadow, glow and SSAO toggles.
37
+
38
+ ### Fixed
39
+
40
+ - **Opening a second menu in a panel left the first one open**, one list over
41
+ the other. A panel now has one anchored popup at a time. (Seen in a headset,
42
+ reproduced flat.)
43
+
44
+ ## 0.8.13
45
+
46
+ **A schema for `<tosi-b3d>` itself, and an emulated headset in the repo.**
47
+
48
+ ### Added
49
+
50
+ - **`sceneSchemas.b3d` / `b3dSchema()`**: the host's scene-level attributes,
51
+ with ranges, units and useful bands: `clearColor`, `glowLayerIntensity`,
52
+ `ssao`, `ssaoStrength`, `ssaoRadius`, `timeScale`, `windSpeed`,
53
+ `windBearingDeg`, `windGust`. The attributes that configure a viewer rather
54
+ than a scene (camera limits, quality, pixel ratio, gamepad and panel chrome,
55
+ XR and pause behaviour) are declined by name in `SCENE_OMITTED.b3d`. The
56
+ drift test covers it like every other schema. `ssao` marks `always` in
57
+ `x-deprecated-values`. Asked by tosijs-3d-ensemble (#101).
58
+ - **`bun run xr <path>`** (`bin/xr-emulate.ts`, repo tooling, not shipped): an
59
+ emulated headset. Headless Chrome with a fake WebXR device enters VR on a
60
+ page, screenshots both eyes and drives controllers from script. It is how
61
+ 0.8.12's headset fixes were found.
62
+
9
63
  ## 0.8.12
10
64
 
11
65
  **0.8.11's ambient occlusion broke VR, and this takes it out of the headset.**
@@ -1 +1 @@
1
- {"version":3,"file":"b3d-loader.d.ts","sourceRoot":"","sources":["../src/b3d-loader.ts"],"names":[],"mappings":"AA4FA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAEL,QAAQ,EAGT,MAAM,gBAAgB,CAAA;AAEvB,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE9D,qBAAa,SAAU,SAAQ,QAAQ;IACrC,MAAM,CAAC,gBAAgB,SAAoB;IAE3C,MAAM,CAAC,cAAc;;;;;;;;;;;;;;;MAiBpB;IAED,KAAK,EAAE,GAAG,GAAG,IAAI,CAAO;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,EAAE,CAAA;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,KAAK,EAAE,CAAA;IACxB,OAAO,CAAC,SAAS,CAAC,CAAqB;IACvC,mFAAmF;IACnF,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IAEzE,yDAAyD;IACzD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,8CAA8C;IAC9C,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,4DAA4D;IAC5D,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE;IAIrC,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI;IAIlC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK;IAuC3C,OAAO,CAAC,kBAAkB;IA0C1B,YAAY;CAiBb;AAED,eAAO,MAAM,SAAS,4CAA6B,CAAA"}
1
+ {"version":3,"file":"b3d-loader.d.ts","sourceRoot":"","sources":["../src/b3d-loader.ts"],"names":[],"mappings":"AA6GA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAEL,QAAQ,EAGT,MAAM,gBAAgB,CAAA;AAEvB,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE9D,qBAAa,SAAU,SAAQ,QAAQ;IACrC,MAAM,CAAC,gBAAgB,SAAoB;IAE3C,MAAM,CAAC,cAAc;;;;;;;;;;;;;;;MAiBpB;IAED,KAAK,EAAE,GAAG,GAAG,IAAI,CAAO;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,EAAE,CAAA;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,KAAK,EAAE,CAAA;IACxB,OAAO,CAAC,SAAS,CAAC,CAAqB;IACvC,mFAAmF;IACnF,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IAEzE,yDAAyD;IACzD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,8CAA8C;IAC9C,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,4DAA4D;IAC5D,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE;IAIrC,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI;IAIlC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK;IAuC3C,OAAO,CAAC,kBAAkB;IA0C1B,YAAY;CAiBb;AAED,eAAO,MAAM,SAAS,4CAA6B,CAAA"}
@@ -27,12 +27,24 @@ model, or remove the element to free it.)
27
27
  ## Demo
28
28
 
29
29
  ```js
30
- import { b3d, b3dSun, b3dSkybox, b3dLoader, b3dReflections, slider3d } from 'tosijs-3d'
30
+ import { b3d, b3dSun, b3dSkybox, b3dLoader, b3dReflections, slider3d, toggle3d } from 'tosijs-3d'
31
31
  import { orbitCam } from 'tosijs-3d/demo-utils'
32
32
  import { tosi, elements } from 'tosijs'
33
33
  const { div, span } = elements
34
34
 
35
- const { demo } = tosi({ demo: { time: 10 } })
35
+ const { demo } = tosi({ demo: { time: 10, shadows: true, glow: false, ssao: false } })
36
+
37
+ // Three effects that change how every material in the file reads.
38
+ let host = null
39
+ function applyEffects() {
40
+ if (host == null || host.scene == null) return
41
+ host.scene.shadowsEnabled = demo.shadows.value
42
+ host.glowLayerIntensity = demo.glow.value ? 1 : 0
43
+ host.ssao = demo.ssao.value ? 'on' : 'off'
44
+ }
45
+ demo.shadows.observe(applyEffects)
46
+ demo.glow.observe(applyEffects)
47
+ demo.ssao.observe(applyEffects)
36
48
 
37
49
  const formatTime = (v) => {
38
50
  const h = Math.floor(v)
@@ -45,8 +57,13 @@ preview.append(
45
57
  {
46
58
  scenePanel: () => [
47
59
  slider3d({ label: 'time of day', value: demo.time, min: 0, max: 24, step: 0.1 }),
60
+ toggle3d({ label: 'shadows', value: demo.shadows }),
61
+ toggle3d({ label: 'glow', value: demo.glow }),
62
+ toggle3d({ label: 'ssao', value: demo.ssao }),
48
63
  ],
49
64
  sceneCreated(el, BABYLON) {
65
+ host = el
66
+ applyEffects()
50
67
  const camera = orbitCam(el, {
51
68
  alpha: -Math.PI / 2, beta: Math.PI / 4, radius: 20, target: [0, 1, 0],
52
69
  })
@@ -1 +1 @@
1
- {"version":3,"file":"b3d-loader.js","sourceRoot":"","sources":["../src/b3d-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyFE;AACF,wBAAwB;AAExB,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAE1C,OAAO,EACL,wBAAwB,EACxB,QAAQ,EACR,KAAK,EACL,SAAS,GACV,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAG/D,MAAM,OAAO,SAAU,SAAQ,QAAQ;IACrC,MAAM,CAAC,gBAAgB,GAAG,iBAAiB,CAAA;IAE3C,MAAM,CAAC,cAAc,GAAG;QACtB,GAAG,EAAE,EAAE;QACP,mBAAmB,EAAE,IAAI;QACzB,6EAA6E;QAC7E,iEAAiE;QACjE,WAAW,EAAE,KAAK;QAClB,QAAQ,EAAE,EAAE,EAAE,aAAa;QAC3B,KAAK,EAAE,CAAC;QACR,SAAS,EAAE,CAAC;QACZ,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,KAAK,EAAE,iDAAiD;QACjE,YAAY,EAAE,CAAC;QACf,UAAU,EAAE,KAAK,EAAE,kDAAkD;QACrE,WAAW,EAAE,EAAE;QACf,eAAe,EAAE,CAAC;QAClB,WAAW,EAAE,CAAC;QACd,UAAU,EAAE,GAAG;KAChB,CAAA;IAED,KAAK,GAAe,IAAI,CAAA;IACxB,MAAM,CAAyB;IAC/B,MAAM,CAAkB;IAChB,SAAS,CAAsB;IACvC,mFAAmF;IACnF,aAAa,CAA4D;IAEzE,yDAAyD;IACzD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,SAAS,EAAE,QAAQ,IAAI,EAAE,CAAA;IACvC,CAAC;IAED,8CAA8C;IAC9C,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,KAAK,CAAA;IACtC,CAAC;IAED,4DAA4D;IAC5D,MAAM,CAAC,MAAc;QACnB,OAAO,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;IAC7C,CAAC;IAED,2EAA2E;IAC3E,QAAQ,CAAC,KAAkB;QACzB,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;IACjC,CAAC;IAED,UAAU,CAAC,KAAU,EAAE,KAAoB;QACzC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,MAAM,GAAG,GAAI,IAAY,CAAC,GAAa,CAAA;QACvC,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,IAAI,CACjE,CAAC,MAAM,EAAE,EAAE;YACT,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,CAAA;YACjD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;YACpB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;YAEpB,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;gBAC1B,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,IAAI,CAAC,OAAO,EAAE,CAAA;gBAChB,CAAC;YACH,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;gBAClC,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,IAAI,CAAC,OAAO,EAAE,CAAA;gBAChB,CAAC;YACH,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC1B,KAAK,CAAC,OAAO,EAAE,CAAA;gBACjB,CAAC;qBAAM,IACL,KAAK,YAAY,OAAO,CAAC,UAAU;oBACnC,KAAK,YAAY,OAAO,CAAC,SAAS,EAClC,CAAC;oBACD,KAAK,CAAC,SAAS,IAAK,IAAY,CAAC,mBAAmB,CAAA;gBACtD,CAAC;YACH,CAAC;YACD,wBAAwB,CAAC,MAAM,CAAC,CAAA;YAChC,IAAI,CAAC,KAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;YACxC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAA;QACjC,CAAC,CACF,CAAA;IACH,CAAC;IAED,mFAAmF;IACnF,sFAAsF;IAC9E,kBAAkB,CAAC,MAA8B;QACvD,MAAM,KAAK,GAAG,IAAW,CAAA;QACzB,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YACvE,OAAM;QACR,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAA;QAC9D,IAAI,CAAC,SAAS,GAAG,IAAI,mBAAmB,CACtC,IAAI,CAAC,KAAK,EACV;YACE,IAAI,IAAI;gBACN,OAAO,IAAI,CAAA;YACb,CAAC;YACD,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;SAC5C,EACD;YACE,MAAM,EACJ,KAAK,CAAC,GAAG;gBACP,EAAE,KAAK,CAAC,GAAG,CAAC;iBACX,GAAG,EAAE;gBACN,EAAE,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,OAAO;YACxC,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,EACD;YACE,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC;YAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,UAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC;YACpC,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,6EAA6E;YAC7E,uEAAuE;YACvE,WAAW,EAAE,MAAM;SACpB,CACF,CAAA;QACD,IAAI,CAAC,SAAS,CAAC,aAAa,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAA;QACnE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAA,CAAC,0CAA0C;IACtE,CAAC;IAED,YAAY;QACV,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,CAAA;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;YACxB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC/B,IAAI,CAAC,OAAO,EAAE,CAAA;YAChB,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACzB,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;YACxB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChC,KAAK,CAAC,OAAO,EAAE,CAAA;YACjB,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;;AAGH,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC,cAAc,EAAE,CAAA"}
1
+ {"version":3,"file":"b3d-loader.js","sourceRoot":"","sources":["../src/b3d-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0GE;AACF,wBAAwB;AAExB,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAE1C,OAAO,EACL,wBAAwB,EACxB,QAAQ,EACR,KAAK,EACL,SAAS,GACV,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAG/D,MAAM,OAAO,SAAU,SAAQ,QAAQ;IACrC,MAAM,CAAC,gBAAgB,GAAG,iBAAiB,CAAA;IAE3C,MAAM,CAAC,cAAc,GAAG;QACtB,GAAG,EAAE,EAAE;QACP,mBAAmB,EAAE,IAAI;QACzB,6EAA6E;QAC7E,iEAAiE;QACjE,WAAW,EAAE,KAAK;QAClB,QAAQ,EAAE,EAAE,EAAE,aAAa;QAC3B,KAAK,EAAE,CAAC;QACR,SAAS,EAAE,CAAC;QACZ,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,KAAK,EAAE,iDAAiD;QACjE,YAAY,EAAE,CAAC;QACf,UAAU,EAAE,KAAK,EAAE,kDAAkD;QACrE,WAAW,EAAE,EAAE;QACf,eAAe,EAAE,CAAC;QAClB,WAAW,EAAE,CAAC;QACd,UAAU,EAAE,GAAG;KAChB,CAAA;IAED,KAAK,GAAe,IAAI,CAAA;IACxB,MAAM,CAAyB;IAC/B,MAAM,CAAkB;IAChB,SAAS,CAAsB;IACvC,mFAAmF;IACnF,aAAa,CAA4D;IAEzE,yDAAyD;IACzD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,SAAS,EAAE,QAAQ,IAAI,EAAE,CAAA;IACvC,CAAC;IAED,8CAA8C;IAC9C,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,KAAK,CAAA;IACtC,CAAC;IAED,4DAA4D;IAC5D,MAAM,CAAC,MAAc;QACnB,OAAO,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;IAC7C,CAAC;IAED,2EAA2E;IAC3E,QAAQ,CAAC,KAAkB;QACzB,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;IACjC,CAAC;IAED,UAAU,CAAC,KAAU,EAAE,KAAoB;QACzC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,MAAM,GAAG,GAAI,IAAY,CAAC,GAAa,CAAA;QACvC,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,IAAI,CACjE,CAAC,MAAM,EAAE,EAAE;YACT,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,CAAA;YACjD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;YACpB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;YAEpB,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;gBAC1B,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,IAAI,CAAC,OAAO,EAAE,CAAA;gBAChB,CAAC;YACH,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;gBAClC,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,IAAI,CAAC,OAAO,EAAE,CAAA;gBAChB,CAAC;YACH,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC1B,KAAK,CAAC,OAAO,EAAE,CAAA;gBACjB,CAAC;qBAAM,IACL,KAAK,YAAY,OAAO,CAAC,UAAU;oBACnC,KAAK,YAAY,OAAO,CAAC,SAAS,EAClC,CAAC;oBACD,KAAK,CAAC,SAAS,IAAK,IAAY,CAAC,mBAAmB,CAAA;gBACtD,CAAC;YACH,CAAC;YACD,wBAAwB,CAAC,MAAM,CAAC,CAAA;YAChC,IAAI,CAAC,KAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;YACxC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAA;QACjC,CAAC,CACF,CAAA;IACH,CAAC;IAED,mFAAmF;IACnF,sFAAsF;IAC9E,kBAAkB,CAAC,MAA8B;QACvD,MAAM,KAAK,GAAG,IAAW,CAAA;QACzB,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YACvE,OAAM;QACR,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAA;QAC9D,IAAI,CAAC,SAAS,GAAG,IAAI,mBAAmB,CACtC,IAAI,CAAC,KAAK,EACV;YACE,IAAI,IAAI;gBACN,OAAO,IAAI,CAAA;YACb,CAAC;YACD,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;SAC5C,EACD;YACE,MAAM,EACJ,KAAK,CAAC,GAAG;gBACP,EAAE,KAAK,CAAC,GAAG,CAAC;iBACX,GAAG,EAAE;gBACN,EAAE,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,OAAO;YACxC,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,EACD;YACE,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC;YAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,UAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC;YACpC,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,6EAA6E;YAC7E,uEAAuE;YACvE,WAAW,EAAE,MAAM;SACpB,CACF,CAAA;QACD,IAAI,CAAC,SAAS,CAAC,aAAa,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAA;QACnE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAA,CAAC,0CAA0C;IACtE,CAAC;IAED,YAAY;QACV,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,CAAA;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;YACxB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC/B,IAAI,CAAC,OAAO,EAAE,CAAA;YAChB,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACzB,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;YACxB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChC,KAAK,CAAC,OAAO,EAAE,CAAA;YACjB,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;;AAGH,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC,cAAc,EAAE,CAAA"}
@@ -0,0 +1,59 @@
1
+ import * as BABYLON from '@babylonjs/core';
2
+ export interface ProjectedAoParams {
3
+ active: boolean;
4
+ /** How dark a fully occluded crease gets. */
5
+ strength: number;
6
+ /** Metres a surface looks for occluders. */
7
+ radius: number;
8
+ samples: number;
9
+ /** Resolution as a fraction of the frame. */
10
+ ratio: number;
11
+ /** Redraws per second. 0 = every frame. */
12
+ rate: number;
13
+ }
14
+ /**
15
+ * Owns the projected-occlusion textures for one scene and the hook on every
16
+ * material that reads them. `<tosi-b3d>` drives it.
17
+ */
18
+ export declare class ProjectedAoController {
19
+ private _scene;
20
+ private _camera;
21
+ private _depth;
22
+ private _ao;
23
+ private _blur;
24
+ private _plugins;
25
+ private _frameObs;
26
+ private _materialObs;
27
+ private _built;
28
+ private _params;
29
+ private _lastDraw;
30
+ private _warmUp;
31
+ private _drawing;
32
+ private _tan;
33
+ /** The view-projection the textures were last drawn with. */
34
+ readonly viewProjection: BABYLON.Matrix;
35
+ /** Debug: how many times the occlusion has been redrawn. */
36
+ draws: number;
37
+ constructor(_scene: BABYLON.Scene);
38
+ get running(): boolean;
39
+ get texture(): BABYLON.Nullable<BABYLON.BaseTexture>;
40
+ get depthTexture(): BABYLON.Nullable<BABYLON.BaseTexture>;
41
+ get attachedCount(): number;
42
+ private _blank;
43
+ /** A 1x1 stand-in for a sampler that must not point at the depth texture. */
44
+ get blank(): BABYLON.BaseTexture;
45
+ /** True while the scene is drawing this controller's depth picture. */
46
+ get drawingDepth(): boolean;
47
+ update(p: ProjectedAoParams): void;
48
+ dispose(): void;
49
+ private _disposeTextures;
50
+ private _attach;
51
+ /**
52
+ * Point the middle camera where the viewer is looking, with a frustum that
53
+ * covers everything either eye can see. Returns false if there is no viewer.
54
+ */
55
+ private _aim;
56
+ private _build;
57
+ private _frame;
58
+ }
59
+ //# sourceMappingURL=b3d-ssao-projected.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"b3d-ssao-projected.d.ts","sourceRoot":"","sources":["../src/b3d-ssao-projected.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAE1C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAA;IACf,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAA;IAChB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAA;CACb;AA2FD;;;GAGG;AACH,qBAAa,qBAAqB;IAqBpB,OAAO,CAAC,MAAM;IApB1B,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,MAAM,CAA2C;IACzD,OAAO,CAAC,GAAG,CAAyC;IACpD,OAAO,CAAC,KAAK,CAAyC;IACtD,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,SAAS,CAA0D;IAC3E,OAAO,CAAC,YAAY,CACd;IACN,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,OAAO,CAAI;IACnB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,IAAI,CAAiB;IAE7B,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,iBAA4B;IACnD,4DAA4D;IAC5D,KAAK,SAAI;gBAEW,MAAM,EAAE,OAAO,CAAC,KAAK;IAEzC,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAEnD;IAED,IAAI,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAExD;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,OAAO,CAAC,MAAM,CAAkC;IAEhD,6EAA6E;IAC7E,IAAI,KAAK,IAAI,OAAO,CAAC,WAAW,CAW/B;IAED,uEAAuE;IACvE,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,MAAM,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAiBlC,OAAO,IAAI,IAAI;IAoBf,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,OAAO;IAkBf;;;OAGG;IACH,OAAO,CAAC,IAAI;IA2CZ,OAAO,CAAC,MAAM;IAiFd,OAAO,CAAC,MAAM;CAsEf"}
@@ -0,0 +1,473 @@
1
+ /*
2
+ PROJECTED AMBIENT OCCLUSION — the default method, and the only kind that can run
3
+ in a headset. `<tosi-b3d ssao="on">`.
4
+
5
+ Babylon's SSAO is a post-process: it composites one full-frame image, and on a
6
+ WebXR camera that is one image across both eyes (see b3d-ssao.ts, "Screen").
7
+ This does the same sum somewhere else. Occlusion is computed ONCE, from a
8
+ camera between the eyes, into a texture. Every lit material then looks its own
9
+ pixel up in that texture BY WORLD POSITION, the way cloud shadows and caustics
10
+ do. So:
11
+
12
+ - it is correct in stereo: each eye shades its own pixels, and the darkening is
13
+ glued to the surface, not to the screen;
14
+ - nothing is post-processed, so the canvas keeps its antialiasing (and a
15
+ headset keeps foveation);
16
+ - it need not be recomputed every frame. The lookup uses the matrix the
17
+ texture was drawn with, so a stale texture stays where it was in the world
18
+ while the head moves. `rate` is how many times a second it is redrawn.
19
+
20
+ What it costs: one depth pass over the opaque scene per update, plus two small
21
+ full-screen passes, plus two texture reads per lit pixel.
22
+
23
+ What it cannot do: darken a surface the middle camera could not see. Those
24
+ pixels fail the depth check and are left alone, which shows as a thin
25
+ unoccluded sliver beside a near object, never as a ghost.
26
+ */
27
+ import * as BABYLON from '@babylonjs/core';
28
+ const AO_SHADER = 'tosiProjectedAo';
29
+ const BLUR_SHADER = 'tosiProjectedAoBlur';
30
+ const MAX_SAMPLES = 32;
31
+ BABYLON.Effect.ShadersStore[`${AO_SHADER}PixelShader`] = `
32
+ precision highp float;
33
+ varying vec2 vUV;
34
+ uniform sampler2D depthSampler;
35
+ // xy = tan(half fov) for x and y, zw = 1 / texture size
36
+ uniform vec4 aoFrustum;
37
+ // x = radius (m), y = strength, z = sample count
38
+ uniform vec3 aoParams;
39
+
40
+ vec3 viewPos(vec2 uv) {
41
+ float z = texture2D(depthSampler, uv).r;
42
+ return vec3((uv * 2.0 - 1.0) * aoFrustum.xy * z, z);
43
+ }
44
+
45
+ void main(void) {
46
+ vec3 p = viewPos(vUV);
47
+ if (p.z <= 0.0) { gl_FragColor = vec4(1.0); return; }
48
+
49
+ // The normal from depth alone: of each pair of neighbours take the one
50
+ // nearer in depth, so the normal does not smear across a silhouette.
51
+ vec2 px = aoFrustum.zw;
52
+ vec3 l = viewPos(vUV - vec2(px.x, 0.0)), r = viewPos(vUV + vec2(px.x, 0.0));
53
+ vec3 d = viewPos(vUV - vec2(0.0, px.y)), u = viewPos(vUV + vec2(0.0, px.y));
54
+ vec3 dx = abs(l.z - p.z) < abs(r.z - p.z) ? p - l : r - p;
55
+ vec3 dy = abs(d.z - p.z) < abs(u.z - p.z) ? p - d : u - p;
56
+ vec3 n = normalize(cross(dy, dx));
57
+
58
+ // Each sample is a real point on a real neighbouring surface, and counts by
59
+ // how far it rises above this pixel's tangent plane. (Testing a point in
60
+ // space against the depth buffer instead darkens every silhouette, because
61
+ // a point just behind a curved edge is hidden without being an occluder.)
62
+ float spin = 6.2831853 * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
63
+ float count = aoParams.z;
64
+ float radius = aoParams.x;
65
+ // The radius as a fraction of the picture at this depth, capped so a
66
+ // close-up does not sample half the screen.
67
+ vec2 reach = min(vec2(radius) / (p.z * aoFrustum.xy) * 0.5, vec2(0.25));
68
+ float occlusion = 0.0;
69
+ for (int i = 0; i < ${MAX_SAMPLES}; i++) {
70
+ if (float(i) >= count) break;
71
+ float t = (float(i) + 0.5) / count;
72
+ float a = spin + float(i) * 2.3999632;
73
+ vec2 uv = vUV + vec2(cos(a), sin(a)) * reach * t;
74
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) continue;
75
+ vec3 q = viewPos(uv);
76
+ if (q.z <= 0.0) continue;
77
+ vec3 v = q - p;
78
+ float far = dot(v, v) / (radius * radius);
79
+ float rise = dot(n, v) / (length(v) + 0.0001);
80
+ occlusion += max(0.0, rise - 0.1) * max(0.0, 1.0 - far);
81
+ }
82
+ // Tuned by eye on a headset against the screen method. Averaged over a
83
+ // frame the two darken about equally at 2.5, but this one puts its darkness
84
+ // into contacts and corners where the screen method spreads it, so at equal
85
+ // averages it LOOKS about twice as strong.
86
+ occlusion *= 1.4;
87
+ float ao = clamp(1.0 - aoParams.y * occlusion / count, 0.0, 1.0);
88
+ gl_FragColor = vec4(ao, ao, ao, 1.0);
89
+ }`;
90
+ BABYLON.Effect.ShadersStore[`${BLUR_SHADER}PixelShader`] = `
91
+ precision highp float;
92
+ varying vec2 vUV;
93
+ uniform sampler2D aoSampler;
94
+ uniform sampler2D depthSampler;
95
+ uniform vec2 aoTexel;
96
+
97
+ void main(void) {
98
+ float z = texture2D(depthSampler, vUV).r;
99
+ float sum = 0.0;
100
+ float weight = 0.0;
101
+ for (int x = -2; x <= 2; x++) {
102
+ for (int y = -2; y <= 2; y++) {
103
+ vec2 uv = vUV + vec2(float(x), float(y)) * aoTexel;
104
+ float zs = texture2D(depthSampler, uv).r;
105
+ // Do not blur across a depth edge.
106
+ float w = 1.0 / (1.0 + 400.0 * abs(zs - z) / max(z, 0.01));
107
+ sum += texture2D(aoSampler, uv).r * w;
108
+ weight += w;
109
+ }
110
+ }
111
+ float ao = sum / max(weight, 0.0001);
112
+ gl_FragColor = vec4(ao, ao, ao, 1.0);
113
+ }`;
114
+ /**
115
+ * Owns the projected-occlusion textures for one scene and the hook on every
116
+ * material that reads them. `<tosi-b3d>` drives it.
117
+ */
118
+ export class ProjectedAoController {
119
+ _scene;
120
+ _camera = null;
121
+ _depth = null;
122
+ _ao = null;
123
+ _blur = null;
124
+ _plugins = [];
125
+ _frameObs = null;
126
+ _materialObs = null;
127
+ _built = { width: 0, height: 0 };
128
+ _params = null;
129
+ _lastDraw = -Infinity;
130
+ _warmUp = 0;
131
+ _drawing = false;
132
+ _tan = { x: 1, y: 1 };
133
+ /** The view-projection the textures were last drawn with. */
134
+ viewProjection = BABYLON.Matrix.Identity();
135
+ /** Debug: how many times the occlusion has been redrawn. */
136
+ draws = 0;
137
+ constructor(_scene) {
138
+ this._scene = _scene;
139
+ }
140
+ get running() {
141
+ return this._blur != null;
142
+ }
143
+ get texture() {
144
+ return this._blur;
145
+ }
146
+ get depthTexture() {
147
+ return this._depth;
148
+ }
149
+ get attachedCount() {
150
+ return this._plugins.length;
151
+ }
152
+ _blank = null;
153
+ /** A 1x1 stand-in for a sampler that must not point at the depth texture. */
154
+ get blank() {
155
+ this._blank ??= BABYLON.RawTexture.CreateRGBATexture(new Uint8Array([255, 255, 255, 255]), 1, 1, this._scene, false, false, BABYLON.Texture.NEAREST_SAMPLINGMODE);
156
+ return this._blank;
157
+ }
158
+ /** True while the scene is drawing this controller's depth picture. */
159
+ get drawingDepth() {
160
+ return this._drawing;
161
+ }
162
+ update(p) {
163
+ const scene = this._scene;
164
+ if (!p.active || scene.activeCamera == null || scene.isDisposed) {
165
+ this.dispose();
166
+ return;
167
+ }
168
+ this._params = p;
169
+ if (this._frameObs == null) {
170
+ this._frameObs = scene.onBeforeRenderObservable.add(() => this._frame());
171
+ this._materialObs = scene.onNewMaterialAddedObservable.add((m) =>
172
+ // A material is announced from its constructor, before a subclass has
173
+ // finished building: attach once it exists.
174
+ setTimeout(() => this._attach(m), 0));
175
+ }
176
+ }
177
+ dispose() {
178
+ const scene = this._scene;
179
+ if (this._frameObs != null) {
180
+ scene.onBeforeRenderObservable.remove(this._frameObs);
181
+ scene.onNewMaterialAddedObservable.remove(this._materialObs);
182
+ this._frameObs = null;
183
+ this._materialObs = null;
184
+ }
185
+ for (const plugin of this._plugins) {
186
+ plugin.isEnabled = false;
187
+ plugin.controller = null;
188
+ }
189
+ this._plugins = [];
190
+ this._disposeTextures();
191
+ this._camera?.dispose();
192
+ this._camera = null;
193
+ this._blank?.dispose();
194
+ this._blank = null;
195
+ }
196
+ _disposeTextures() {
197
+ const scene = this._scene;
198
+ this._depth?.dispose();
199
+ this._ao?.dispose();
200
+ this._blur?.dispose();
201
+ this._depth = null;
202
+ this._ao = null;
203
+ this._blur = null;
204
+ this._built = { width: 0, height: 0 };
205
+ }
206
+ _attach(material) {
207
+ if (this._frameObs == null)
208
+ return;
209
+ // Only materials that take plugins, and only ones something might light.
210
+ if (!(material instanceof BABYLON.StandardMaterial) &&
211
+ !(material instanceof BABYLON.PBRBaseMaterial)) {
212
+ return;
213
+ }
214
+ let plugin = material.pluginManager?.getPlugin('ProjectedAo');
215
+ if (plugin == null)
216
+ plugin = new ProjectedAoPlugin(material);
217
+ if (!this._plugins.includes(plugin))
218
+ this._plugins.push(plugin);
219
+ plugin.controller = this;
220
+ plugin.isEnabled = true;
221
+ }
222
+ /**
223
+ * Point the middle camera where the viewer is looking, with a frustum that
224
+ * covers everything either eye can see. Returns false if there is no viewer.
225
+ */
226
+ _aim() {
227
+ const scene = this._scene;
228
+ const viewer = scene.activeCamera;
229
+ if (viewer == null)
230
+ return false;
231
+ if (this._camera == null) {
232
+ const cam = new BABYLON.FreeCamera('projected-ao', BABYLON.Vector3.Zero(), scene, false);
233
+ cam.rotationQuaternion = BABYLON.Quaternion.Identity();
234
+ // It is never the active camera and never takes input.
235
+ scene.removeCamera(cam);
236
+ this._camera = cam;
237
+ }
238
+ const cam = this._camera;
239
+ // The widest half-angle any eye needs, plus the little the eyes sit apart.
240
+ let tanX = 0;
241
+ let tanY = 0;
242
+ const eyes = viewer.rigCameras.length > 0 ? viewer.rigCameras : [viewer];
243
+ for (const eye of eyes) {
244
+ const m = eye.getProjectionMatrix().m;
245
+ if (m[0] === 0 || m[5] === 0)
246
+ continue;
247
+ tanX = Math.max(tanX, (1 + Math.abs(m[8])) / Math.abs(m[0]));
248
+ tanY = Math.max(tanY, (1 + Math.abs(m[9])) / Math.abs(m[5]));
249
+ }
250
+ if (tanX === 0 || tanY === 0)
251
+ return false;
252
+ if (eyes.length > 1) {
253
+ tanX *= 1.08;
254
+ tanY *= 1.04;
255
+ }
256
+ this._tan = { x: tanX, y: tanY };
257
+ viewer
258
+ .getWorldMatrix()
259
+ .decompose(undefined, cam.rotationQuaternion, cam.position);
260
+ cam.minZ = viewer.minZ;
261
+ cam.maxZ = viewer.maxZ;
262
+ cam.fov = 2 * Math.atan(tanY);
263
+ cam.fovMode = BABYLON.Camera.FOVMODE_VERTICAL_FIXED;
264
+ return true;
265
+ }
266
+ _build(width, height) {
267
+ const scene = this._scene;
268
+ const cam = this._camera;
269
+ this._disposeTextures();
270
+ // Full floats where the device can draw to them. Half floats step by
271
+ // several millimetres ten metres out, and a normal worked out from depth
272
+ // turns those steps into stripes across any surface seen at a glancing angle.
273
+ const depthType = scene.getEngine().getCaps().textureFloatRender
274
+ ? BABYLON.Constants.TEXTURETYPE_FLOAT
275
+ : BABYLON.Constants.TEXTURETYPE_HALF_FLOAT;
276
+ /*
277
+ EVERY MESH DRAWS ITS OWN DEPTH, WITH ITS OWN MATERIAL. The plugin below is
278
+ on that material and, while this texture is being drawn, replaces the
279
+ material's output with camera-space depth and returns before any lighting.
280
+
281
+ Not Babylon's DepthRenderer, though it is the obvious tool. It draws with
282
+ its own depth shader, which follows bones but knows nothing about a
283
+ material plugin that moves vertices, so a baked vertex-animated crowd was
284
+ occluded in its rest pose. Going through the material itself is one path
285
+ for skinning, morphs, vertex animation and instances alike.
286
+ */
287
+ const map = new BABYLON.RenderTargetTexture('projected-ao-depth', { width, height }, scene, false, true, depthType, false, BABYLON.Texture.NEAREST_SAMPLINGMODE, true, false, false, BABYLON.Constants.TEXTUREFORMAT_R);
288
+ // 0 where there is nothing (the sky): depth is in metres and never 0.
289
+ map.clearColor = new BABYLON.Color4(0, 0, 0, 1);
290
+ map.activeCamera = cam;
291
+ map.renderParticles = false;
292
+ map.renderSprites = false;
293
+ map.ignoreCameraViewport = true;
294
+ // Only what can write depth this way: a material carrying the hook, and
295
+ // nothing see-through or pinned to the horizon.
296
+ map.renderListPredicate = (mesh) => {
297
+ const material = mesh.material;
298
+ if (material == null || mesh.infiniteDistance)
299
+ return false;
300
+ if (material.needAlphaBlendingForMesh(mesh))
301
+ return false;
302
+ const plugin = material.pluginManager?.getPlugin('ProjectedAo');
303
+ return plugin != null && plugin.isEnabled;
304
+ };
305
+ map.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
306
+ map.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
307
+ // Not in `scene.customRenderTargets`: `_frame` calls `render()` itself,
308
+ // because when it is drawn is the whole point of `rate`.
309
+ this._depth = map;
310
+ const pass = (name, shader) => {
311
+ const t = new BABYLON.ProceduralTexture(name, { width, height }, shader, scene, null, false);
312
+ t.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
313
+ t.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
314
+ t.refreshRate = 0;
315
+ return t;
316
+ };
317
+ this._ao = pass('projected-ao', AO_SHADER);
318
+ this._blur = pass('projected-ao-blur', BLUR_SHADER);
319
+ this._ao.setTexture('depthSampler', map);
320
+ this._blur.setTexture('aoSampler', this._ao);
321
+ this._blur.setTexture('depthSampler', map);
322
+ this._blur.setVector2('aoTexel', new BABYLON.Vector2(1 / width, 1 / height));
323
+ this._built = { width, height };
324
+ }
325
+ _frame() {
326
+ const p = this._params;
327
+ const scene = this._scene;
328
+ if (p == null || scene.activeCamera == null)
329
+ return;
330
+ const now = performance.now();
331
+ // Shaders compile asynchronously and a mesh whose depth shader is not
332
+ // ready is simply left out of that drawing. So the first drawings after a
333
+ // build are not rate-limited, or a slow rate would keep a picture with
334
+ // holes in it.
335
+ const limited = p.rate > 0 && this._warmUp <= 0;
336
+ if (limited && now - this._lastDraw < 1000 / p.rate - 1)
337
+ return;
338
+ if (this._warmUp > 0)
339
+ this._warmUp--;
340
+ if (!this._aim())
341
+ return;
342
+ this._lastDraw = now;
343
+ const engine = scene.getEngine();
344
+ // The middle camera's picture is wider than one eye's, so size it by the
345
+ // frame's HEIGHT and its own aspect.
346
+ const height = Math.max(64, Math.round(engine.getRenderHeight() * p.ratio * 0.5) * 2);
347
+ const width = Math.max(64, Math.round((height * this._tan.x) / this._tan.y / 2) * 2);
348
+ if (this._blur == null ||
349
+ this._built.width !== width ||
350
+ this._built.height !== height) {
351
+ this._build(width, height);
352
+ this._warmUp = 30;
353
+ for (const material of scene.materials)
354
+ this._attach(material);
355
+ }
356
+ const cam = this._camera;
357
+ // Frozen, because a camera works out its aspect from whatever is bound
358
+ // when it is asked, and that is not reliably this texture.
359
+ const projection = BABYLON.Matrix.PerspectiveFovLH(cam.fov, width / height, cam.minZ, cam.maxZ, engine.isNDCHalfZRange);
360
+ cam.freezeProjectionMatrix(projection);
361
+ // What the lookup must use is the matrix THIS drawing was made with.
362
+ cam.getViewMatrix(true).multiplyToRef(projection, this.viewProjection);
363
+ this._ao.setColor4('aoFrustum', new BABYLON.Color4(this._tan.x, this._tan.y, 1 / width, 1 / height));
364
+ this._ao.setVector3('aoParams', new BABYLON.Vector3(p.radius, p.strength, Math.min(MAX_SAMPLES, Math.max(4, p.samples))));
365
+ this._drawing = true;
366
+ try {
367
+ this._depth.render(false, false);
368
+ }
369
+ finally {
370
+ this._drawing = false;
371
+ }
372
+ this._ao.resetRefreshCounter();
373
+ this._blur.resetRefreshCounter();
374
+ this.draws++;
375
+ }
376
+ }
377
+ class ProjectedAoPlugin extends BABYLON.MaterialPluginBase {
378
+ controller = null;
379
+ _isEnabled = false;
380
+ constructor(material) {
381
+ super(material, 'ProjectedAo', 220, { PROJECTED_AO: false });
382
+ }
383
+ get isEnabled() {
384
+ return this._isEnabled;
385
+ }
386
+ set isEnabled(enabled) {
387
+ if (this._isEnabled === enabled)
388
+ return;
389
+ this._isEnabled = enabled;
390
+ this.markAllDefinesAsDirty();
391
+ this._enable(enabled);
392
+ }
393
+ prepareDefines(defines) {
394
+ defines.PROJECTED_AO = this._isEnabled && this.controller?.texture != null;
395
+ }
396
+ getClassName() {
397
+ return 'ProjectedAoPlugin';
398
+ }
399
+ getSamplers(samplers) {
400
+ samplers.push('projectedAoSampler', 'projectedAoDepthSampler');
401
+ }
402
+ getUniforms() {
403
+ return {
404
+ ubo: [
405
+ { name: 'projectedAoMatrix', size: 16, type: 'mat4' },
406
+ { name: 'projectedAoMode', size: 4, type: 'vec4' },
407
+ ],
408
+ fragment: `#ifdef PROJECTED_AO
409
+ uniform mat4 projectedAoMatrix;
410
+ uniform vec4 projectedAoMode;
411
+ #endif`,
412
+ };
413
+ }
414
+ bindForSubMesh(uniformBuffer) {
415
+ const c = this.controller;
416
+ const ao = c?.texture;
417
+ const depth = c?.depthTexture;
418
+ if (!this._isEnabled || c == null || ao == null || depth == null)
419
+ return;
420
+ uniformBuffer.updateMatrix('projectedAoMatrix', c.viewProjection);
421
+ // x = 1 while this material is being used to draw the depth picture.
422
+ uniformBuffer.updateFloat4('projectedAoMode', c.drawingDepth ? 1 : 0, 0, 0, 0);
423
+ uniformBuffer.setTexture('projectedAoSampler', ao);
424
+ /*
425
+ NEVER the depth texture while it is the thing being drawn. Sampling the
426
+ texture you are rendering into is a feedback loop: WebGL raises
427
+ INVALID_OPERATION and drops the draw, with nothing in the console, so the
428
+ depth picture comes out empty and the only symptom is no occlusion. The
429
+ shader does not read it in that pass, but a sampler left pointing at it is
430
+ enough.
431
+ */
432
+ uniformBuffer.setTexture('projectedAoDepthSampler', c.drawingDepth ? c.blank : depth);
433
+ }
434
+ getCustomCode(shaderType) {
435
+ if (shaderType !== 'fragment')
436
+ return null;
437
+ return {
438
+ CUSTOM_FRAGMENT_DEFINITIONS: `#ifdef PROJECTED_AO
439
+ uniform sampler2D projectedAoSampler;
440
+ uniform sampler2D projectedAoDepthSampler;
441
+ #endif`,
442
+ // Drawing the depth picture with this material: write camera-space
443
+ // depth (for a perspective camera, 1 / gl_FragCoord.w) and leave before
444
+ // the material does any of its own work.
445
+ CUSTOM_FRAGMENT_MAIN_BEGIN: `#ifdef PROJECTED_AO
446
+ if (projectedAoMode.x > 0.5) {
447
+ gl_FragColor = vec4(1.0 / gl_FragCoord.w, 0.0, 0.0, 1.0);
448
+ return;
449
+ }
450
+ #endif`,
451
+ CUSTOM_FRAGMENT_MAIN_END: `#ifdef PROJECTED_AO
452
+ {
453
+ vec4 aoClip = projectedAoMatrix * vec4(vPositionW, 1.0);
454
+ if (aoClip.w > 0.0) {
455
+ vec2 aoUv = aoClip.xy / aoClip.w * 0.5 + 0.5;
456
+ // Fade out toward the edge of what the middle camera saw, so a
457
+ // stale picture ends softly as the head turns.
458
+ vec2 aoEdge = smoothstep(vec2(0.0), vec2(0.04), aoUv) * smoothstep(vec2(0.0), vec2(0.04), 1.0 - aoUv);
459
+ float aoSeen = texture2D(projectedAoDepthSampler, aoUv).r;
460
+ // Only where the middle camera saw THIS surface: clip w is its depth.
461
+ float aoMatch = 1.0 - smoothstep(0.02, 0.05, abs(aoSeen - aoClip.w) / aoClip.w);
462
+ float aoValue = texture2D(projectedAoSampler, aoUv).r;
463
+ gl_FragColor.rgb *= mix(1.0, aoValue, aoEdge.x * aoEdge.y * aoMatch);
464
+ }
465
+ }
466
+ #endif`,
467
+ };
468
+ }
469
+ }
470
+ // In the registry, or Material.clone() throws on a material that carries it
471
+ // (the same trap cloud-shadows documents).
472
+ BABYLON.RegisterMaterialPlugin('ProjectedAoPlugin', (material) => new ProjectedAoPlugin(material));
473
+ //# sourceMappingURL=b3d-ssao-projected.js.map