Skip to content

Commit 2808b22

Browse files
authored
Add flipbook gaussian splatting example (#225)
* Add flipbook gaussian splat example * Add missing file
1 parent 06bc337 commit 2808b22

File tree

4 files changed

+227
-9
lines changed

4 files changed

+227
-9
lines changed

examples/gsplat-flipbook.html

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6+
<title>PlayCanvas Web Components - GSplat Flipbook</title>
7+
<script type="importmap">
8+
{
9+
"imports": {
10+
"playcanvas": "../node_modules/playcanvas/build/playcanvas.mjs"
11+
}
12+
}
13+
</script>
14+
<script type="module" src="../dist/pwc.mjs"></script>
15+
<link rel="stylesheet" href="css/example.css">
16+
</head>
17+
<body>
18+
<pc-app antialias="false" depth="false" high-resolution="false" stencil="false">
19+
<!-- Assets -->
20+
<pc-asset src="../node_modules/playcanvas/scripts/esm/camera-controls.mjs"></pc-asset>
21+
<pc-asset src="../node_modules/playcanvas/scripts/esm/gsplat/gsplat-flipbook.mjs"></pc-asset>
22+
<pc-asset src="../node_modules/playcanvas/scripts/esm/shadow-catcher.mjs"></pc-asset>
23+
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-controllers.mjs"></pc-asset>
24+
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-navigation.mjs"></pc-asset>
25+
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-session.mjs"></pc-asset>
26+
<pc-asset src="assets/skies/octagon-lamps-photo-studio-2k.hdr" id="studio"></pc-asset>
27+
<!-- Scene -->
28+
<pc-scene>
29+
<!-- Sky -->
30+
<pc-sky asset="studio" type="dome" center="0 0.3 0" scale="5 5 5" lighting></pc-sky>
31+
<!-- Camera (with XR support) -->
32+
<pc-entity name="camera root">
33+
<pc-entity name="camera" position="0 1.25 2">
34+
<pc-camera far-clip="1500" fov="50" tone-mapping="aces"></pc-camera>
35+
<pc-scripts>
36+
<pc-script name="cameraControls" attributes='{
37+
"enableFly": false,
38+
"enablePan": false,
39+
"focusPoint": "vec3:0,1.25,0",
40+
"zoomRange": "vec2:0.2,2.5"
41+
}'></pc-script>
42+
</pc-scripts>
43+
</pc-entity>
44+
<pc-scripts>
45+
<pc-script name="xrControllers"></pc-script>
46+
<pc-script name="xrNavigation"></pc-script>
47+
<pc-script name="xrSession"></pc-script>
48+
</pc-scripts>
49+
</pc-entity>
50+
<!-- Player (Flipbook) -->
51+
<pc-entity name="player" rotation="0 -90 180">
52+
<pc-splat unified cast-shadows></pc-splat>
53+
<pc-scripts>
54+
<pc-script name="gsplatFlipbook" attributes='{
55+
"fps": 30,
56+
"folder": "https://code.playcanvas.com/examples_data/example_basketball_02",
57+
"filenamePattern": "{frame:03}.compressed.ply",
58+
"startFrame": 1,
59+
"endFrame": 149,
60+
"playMode": "loop",
61+
"playing": false,
62+
"preloadCount": 149
63+
}'></pc-script>
64+
</pc-scripts>
65+
</pc-entity>
66+
<!-- Shadow Catcher -->
67+
<pc-entity name="shadow-catcher" position="0 0 0">
68+
<pc-render type="plane" cast-shadows="false"></pc-render>
69+
<pc-scripts>
70+
<pc-script name="shadowCatcher" attributes='{
71+
"scale": "vec3:10,10,10"
72+
}'></pc-script>
73+
</pc-scripts>
74+
</pc-entity>
75+
<!-- Light -->
76+
<pc-entity name="light" rotation="55 320 0">
77+
<pc-light type="directional" color="1 1 1" intensity="1"
78+
cast-shadows shadow-bias="0.2" normal-offset-bias="0.05"
79+
shadow-distance="10" shadow-intensity="0.3"
80+
shadow-resolution="2048" shadow-type="pcss-32f"
81+
penumbra-size="10" penumbra-falloff="2"
82+
shadow-samples="16" shadow-blocker-samples="16">
83+
</pc-light>
84+
</pc-entity>
85+
</pc-scene>
86+
</pc-app>
87+
<script type="module" src="js/example.mjs"></script>
88+
<script type="module">
89+
// Wait for frames to preload before starting playback
90+
document.addEventListener('DOMContentLoaded', async () => {
91+
const appElement = await document.querySelector('pc-app').ready();
92+
const app = appElement.app;
93+
94+
// Wait for the flipbook script to be ready
95+
const waitForFlipbook = setInterval(() => {
96+
const player = app.root.findByName('player');
97+
const flipbook = player?.script?.gsplatFlipbook;
98+
99+
if (flipbook) {
100+
clearInterval(waitForFlipbook);
101+
console.log('Flipbook script found, waiting for frames to load...');
102+
103+
// Now wait for all preloaded frames to be loaded
104+
const checkReady = setInterval(() => {
105+
const preloaded = flipbook.preloadedFrames || [];
106+
const loadedCount = preloaded.filter(f => f.asset.loaded).length;
107+
console.log(`Preloaded: ${loadedCount}/${preloaded.length}`);
108+
109+
// Start when we have enough frames loaded
110+
if (preloaded.length >= 100 && loadedCount >= preloaded.length) {
111+
clearInterval(checkReady);
112+
flipbook.playing = true;
113+
console.log('Frames loaded, starting playback');
114+
}
115+
}, 500);
116+
}
117+
}, 100);
118+
});
119+
</script>
120+
</body>
121+
</html>

examples/js/example-list.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const examples = [
99
{ name: 'First Person Teleport', path: 'first-person-teleport.html' },
1010
{ name: 'FPS Controller', path: 'fps-controller.html' },
1111
{ name: 'Gaussian Splatting', path: 'splat.html' },
12+
{ name: 'GSplat Flipbook', path: 'gsplat-flipbook.html' },
1213
{ name: 'GLB Loader', path: 'glb.html' },
1314
{ name: 'Physics', path: 'physics.html' },
1415
{ name: 'Physics Cluster', path: 'physics-cluster.html' },

package-lock.json

Lines changed: 1 addition & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/light-component.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ class LightComponentElement extends ComponentElement {
5454

5555
private _vsmBlurSize = 11;
5656

57+
private _penumbraSize = 1;
58+
59+
private _penumbraFalloff = 2;
60+
61+
private _shadowSamples = 16;
62+
63+
private _shadowBlockerSamples = 16;
64+
5765
/** @ignore */
5866
constructor() {
5967
super('light');
@@ -67,11 +75,15 @@ class LightComponentElement extends ComponentElement {
6775
intensity: this._intensity,
6876
normalOffsetBias: this._normalOffsetBias,
6977
outerConeAngle: this._outerConeAngle,
78+
penumbraFalloff: this._penumbraFalloff,
79+
penumbraSize: this._penumbraSize,
7080
range: this._range,
7181
shadowBias: this._shadowBias,
82+
shadowBlockerSamples: this._shadowBlockerSamples,
7283
shadowDistance: this._shadowDistance,
7384
shadowIntensity: this._shadowIntensity,
7485
shadowResolution: this._shadowResolution,
86+
shadowSamples: this._shadowSamples,
7587
shadowType: shadowTypes.get(this._shadowType),
7688
type: this._type,
7789
vsmBias: this._vsmBias,
@@ -387,6 +399,82 @@ class LightComponentElement extends ComponentElement {
387399
return this._vsmBlurSize;
388400
}
389401

402+
/**
403+
* Sets the penumbra size of the light. Used for PCSS shadows.
404+
* @param value - The penumbra size.
405+
*/
406+
set penumbraSize(value: number) {
407+
this._penumbraSize = value;
408+
if (this.component) {
409+
this.component.penumbraSize = value;
410+
}
411+
}
412+
413+
/**
414+
* Gets the penumbra size of the light.
415+
* @returns The penumbra size.
416+
*/
417+
get penumbraSize() {
418+
return this._penumbraSize;
419+
}
420+
421+
/**
422+
* Sets the penumbra falloff of the light. Used for PCSS shadows.
423+
* @param value - The penumbra falloff.
424+
*/
425+
set penumbraFalloff(value: number) {
426+
this._penumbraFalloff = value;
427+
if (this.component) {
428+
this.component.penumbraFalloff = value;
429+
}
430+
}
431+
432+
/**
433+
* Gets the penumbra falloff of the light.
434+
* @returns The penumbra falloff.
435+
*/
436+
get penumbraFalloff() {
437+
return this._penumbraFalloff;
438+
}
439+
440+
/**
441+
* Sets the number of shadow samples. Used for PCSS shadows.
442+
* @param value - The number of shadow samples.
443+
*/
444+
set shadowSamples(value: number) {
445+
this._shadowSamples = value;
446+
if (this.component) {
447+
this.component.shadowSamples = value;
448+
}
449+
}
450+
451+
/**
452+
* Gets the number of shadow samples.
453+
* @returns The number of shadow samples.
454+
*/
455+
get shadowSamples() {
456+
return this._shadowSamples;
457+
}
458+
459+
/**
460+
* Sets the number of shadow blocker samples. Used for PCSS shadows.
461+
* @param value - The number of shadow blocker samples.
462+
*/
463+
set shadowBlockerSamples(value: number) {
464+
this._shadowBlockerSamples = value;
465+
if (this.component) {
466+
this.component.shadowBlockerSamples = value;
467+
}
468+
}
469+
470+
/**
471+
* Gets the number of shadow blocker samples.
472+
* @returns The number of shadow blocker samples.
473+
*/
474+
get shadowBlockerSamples() {
475+
return this._shadowBlockerSamples;
476+
}
477+
390478
static get observedAttributes() {
391479
return [
392480
...super.observedAttributes,
@@ -396,11 +484,15 @@ class LightComponentElement extends ComponentElement {
396484
'inner-cone-angle',
397485
'normal-offset-bias',
398486
'outer-cone-angle',
487+
'penumbra-falloff',
488+
'penumbra-size',
399489
'range',
400490
'shadow-bias',
491+
'shadow-blocker-samples',
401492
'shadow-distance',
402493
'shadow-intensity',
403494
'shadow-resolution',
495+
'shadow-samples',
404496
'shadow-type',
405497
'type',
406498
'vsm-bias',
@@ -430,6 +522,12 @@ class LightComponentElement extends ComponentElement {
430522
case 'outer-cone-angle':
431523
this.outerConeAngle = Number(newValue);
432524
break;
525+
case 'penumbra-falloff':
526+
this.penumbraFalloff = Number(newValue);
527+
break;
528+
case 'penumbra-size':
529+
this.penumbraSize = Number(newValue);
530+
break;
433531
case 'range':
434532
this.range = Number(newValue);
435533
break;
@@ -439,12 +537,18 @@ class LightComponentElement extends ComponentElement {
439537
case 'shadow-distance':
440538
this.shadowDistance = Number(newValue);
441539
break;
540+
case 'shadow-blocker-samples':
541+
this.shadowBlockerSamples = Number(newValue);
542+
break;
442543
case 'shadow-resolution':
443544
this.shadowResolution = Number(newValue);
444545
break;
445546
case 'shadow-intensity':
446547
this.shadowIntensity = Number(newValue);
447548
break;
549+
case 'shadow-samples':
550+
this.shadowSamples = Number(newValue);
551+
break;
448552
case 'shadow-type':
449553
this.shadowType = newValue as 'pcf1-16f' | 'pcf1-32f' | 'pcf3-16f' | 'pcf3-32f' | 'pcf5-16f' | 'pcf5-32f' | 'vsm-16f' | 'vsm-32f' | 'pcss-32f';
450554
break;

0 commit comments

Comments
 (0)