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 +54 -0
- package/dist/b3d-loader.d.ts.map +1 -1
- package/dist/b3d-loader.js +19 -2
- package/dist/b3d-loader.js.map +1 -1
- package/dist/b3d-ssao-projected.d.ts +59 -0
- package/dist/b3d-ssao-projected.d.ts.map +1 -0
- package/dist/b3d-ssao-projected.js +473 -0
- package/dist/b3d-ssao-projected.js.map +1 -0
- package/dist/b3d-ssao.d.ts +2 -1
- package/dist/b3d-ssao.d.ts.map +1 -1
- package/dist/b3d-ssao.js +88 -24
- package/dist/b3d-ssao.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/scene-schemas.d.ts +18 -0
- package/dist/scene-schemas.d.ts.map +1 -1
- package/dist/scene-schemas.js +135 -0
- package/dist/scene-schemas.js.map +1 -1
- package/dist/tosi-b3d.d.ts +4 -0
- package/dist/tosi-b3d.d.ts.map +1 -1
- package/dist/tosi-b3d.js +77 -23
- package/dist/tosi-b3d.js.map +1 -1
- package/dist/widgets3d.d.ts.map +1 -1
- package/dist/widgets3d.js +22 -2
- package/dist/widgets3d.js.map +1 -1
- package/package.json +3 -1
- package/static/attributes.json +17 -1
- package/static/attributes.txt +5 -1
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.**
|
package/dist/b3d-loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"b3d-loader.d.ts","sourceRoot":"","sources":["../src/b3d-loader.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/b3d-loader.js
CHANGED
|
@@ -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
|
})
|
package/dist/b3d-loader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"b3d-loader.js","sourceRoot":"","sources":["../src/b3d-loader.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"b3d-loader.js","sourceRoot":"","sources":["../src/b3d-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|