Gaussian Splatting
3D Gaussian Splatting is a rasterization technique that allows to render 3D objects with a high level of detail. It’s currently quickly gaining popularity because of the photorealistic rendering of 3D scan data. This example shows how to implement 3D Gaussian Splatting with the help of two libraries:
The technology presented in this example is rapidly developing and therefore the components are
not part of the @threlte/extras
package as of now.
You may however copy and paste them into your project and use them as you wish.
<script>
import { Canvas } from '@threlte/core'
import Scene from './Scene.svelte'
</script>
<div>
<Canvas>
<Scene />
</Canvas>
</div>
<style>
:global(body) {
margin: 0;
}
div {
width: 100%;
height: 100%;
}
</style>
<script lang="ts">
import { LumaSplatsThree, type LumaSplatsSource } from '@lumaai/luma-web'
import { T, asyncWritable, useCache, useTask, useThrelte } from '@threlte/core'
import { useSuspense } from '@threlte/extras'
import { onDestroy } from 'svelte'
import type { CubeTexture, Scene } from 'three'
export let source: LumaSplatsSource
export let mode: 'object' | 'object-env' | 'env' = 'object'
export let loadingAnimationEnabled = false
export let particleRevealEnabled = false
export let enableThreeShaderIntegration = true
const { invalidate, renderer, scene } = useThrelte()
const { remember } = useCache()
const suspend = useSuspense()
const captureCubemap = mode === 'env' || mode === 'object-env'
const splats = suspend(
asyncWritable(
remember(() => {
return new Promise<[LumaSplatsThree, CubeTexture | undefined]>((resolve) => {
const splats = new LumaSplatsThree({
source,
loadingAnimationEnabled,
particleRevealEnabled,
enableThreeShaderIntegration
})
splats.onLoad = async () => {
if (captureCubemap) {
splats.captureCubemap(renderer).then((cubemap) => {
resolve([splats, cubemap])
})
} else {
resolve([splats, undefined])
}
}
})
}, [source])
)
)
let preheat =
particleRevealEnabled && loadingAnimationEnabled ? 400 : loadingAnimationEnabled ? 100 : 10
let frame = 0
const { start, stop } = useTask(
() => {
frame++
if (frame >= preheat) {
stop()
frame = 0
}
},
{ autoStart: false }
)
let previousEnvironment: Scene['environment']
let previousBackground: Scene['background']
let previousBackgroundBluriness: Scene['backgroundBlurriness']
$: if ($splats && $splats[1]) {
previousEnvironment = scene.environment
previousBackground = scene.background
previousBackgroundBluriness = scene.backgroundBlurriness
scene.environment = $splats[1]
scene.background = $splats[1]
scene.backgroundBlurriness = 0.5
invalidate()
}
onDestroy(() => {
if (captureCubemap) {
scene.environment = previousEnvironment
scene.background = previousBackground
scene.backgroundBlurriness = previousBackgroundBluriness
invalidate()
}
})
</script>
{#if (mode === 'object' || mode === 'object-env') && $splats && $splats[0]}
<T
dispose={false}
is={$splats[0]}
on:create={() => {
start()
}}
{...$$restProps}
>
<slot ref={$splats[0]} />
</T>
{/if}
import type { Events, Props, Slots } from '@threlte/core'
import { SvelteComponent } from 'svelte'
import { LumaSplatsSemantics, LumaSplatsThree, type LumaSplatsSource } from '@lumaai/luma-web'
export type LumaSplatsThreeProps = Props<LumaSplatsThree> & {
source: LumaSplatsSource
mode?: 'object' | 'env' | 'object-env'
loadingAnimationEnabled?: boolean
particleRevealEnabled?: boolean
enableThreeShaderIntegration?: boolean
}
export type LumaSplatsThreeEvents = Events<LumaSplatsThree>
export type LumaSplatsThreeSlots = Slots<LumaSplatsThree>
export default class LumaSplatsThree extends SvelteComponent<
LumaSplatsThreeProps,
LumaSplatsThreeEvents,
LumaSplatsThreeSlots
> {}
<script lang="ts">
import { useStage, useTask, useThrelte } from '@threlte/core'
import { WaveformMonitor } from 'svelte-tweakpane-ui'
const { shouldRender, renderStage } = useThrelte()
const afterRenderStage = useStage('after-render', {
after: renderStage
})
let log = Array(100).fill(0)
useTask(
() => {
log = update(log)
},
{
autoInvalidate: false,
stage: afterRenderStage
}
)
function update(log: number[]) {
log.shift()
log.push(shouldRender() ? 1 : 0)
return log
}
</script>
<WaveformMonitor
value={log}
min={-1}
max={2}
/>
<script lang="ts">
import { T } from '@threlte/core'
import { GLTF, OrbitControls } from '@threlte/extras'
import { Checkbox, Folder, FpsGraph, List, Pane, Slider } from 'svelte-tweakpane-ui'
import type { MeshStandardMaterial } from 'three'
import { DEG2RAD } from 'three/src/math/MathUtils.js'
import LumaSplats from './LumaSplatsThree/LumaSplatsThree.svelte'
import RenderIndicator from './RenderIndicator.svelte'
import Splat from './Splat/Splat.svelte'
// <LumaSplatsThree>
let showLumaSplats = true
let lumaSplatsMode: 'object' | 'object-env' | 'env' = 'object-env'
// <Splat>
let showSplat = true
let alphaHash = false
let alphaTest = 0.06
let toneMapped = true
// Car
let showPorsche = true
let paneExpanded = false
let gltfMaterials: Record<string, MeshStandardMaterial> | undefined
$: if (gltfMaterials) {
Object.values(gltfMaterials).forEach((material) => {
material.envMapIntensity = 5
})
}
</script>
<LumaSplats
visible={showLumaSplats}
source="https://lumalabs.ai/capture/4c15c22e-8655-4423-aeac-b08f017dda22"
mode={lumaSplatsMode}
/>
<Splat
visible={showSplat}
position={[1.08, 2.21, -1.99]}
rotation={[-32.3 * DEG2RAD, -18.5 * DEG2RAD, -6.4 * DEG2RAD]}
src="https://huggingface.co/cakewalk/splat-data/resolve/main/nike.splat"
{alphaHash}
alphaTest={alphaTest > 0 ? alphaTest : undefined}
{toneMapped}
/>
<GLTF
visible={showPorsche}
position={[-1.48, -0.51, 2.15]}
rotation.y={57 * DEG2RAD}
scale={0.7}
bind:materials={gltfMaterials}
url="/models/splat-example/porsche_959.glb"
/>
<T.PerspectiveCamera
makeDefault
position={[0.22, 2.44, 9.06]}
on:create={({ ref }) => {
ref.lookAt(0, 0, 0)
}}
fov={25}
>
<OrbitControls />
</T.PerspectiveCamera>
<!-- TWEAKPANE -->
<Pane
position="fixed"
title="Gaussian Splatting"
bind:expanded={paneExpanded}
>
<Folder
userExpandable={false}
expanded
title="Luma"
>
<Checkbox
bind:value={showLumaSplats}
label="Show LumaSplats"
/>
{#if showLumaSplats}
<List
bind:value={lumaSplatsMode}
options={{
object: 'object',
'object-env': 'object-env',
env: 'env'
}}
/>
{/if}
</Folder>
<Folder
userExpandable={false}
expanded
title="Splat"
>
<Checkbox
bind:value={showSplat}
label="Show Splats"
/>
{#if showSplat}
<Checkbox
bind:value={alphaHash}
label="alphaHash"
/>
<Slider
bind:value={alphaTest}
label="alphaTest"
min={0}
max={1}
step={0.01}
/>
<Checkbox
bind:value={toneMapped}
label="toneMapped"
/>
{/if}
</Folder>
<Folder title="Porsche">
<Checkbox
bind:value={showPorsche}
label="Show Porsche"
/>
</Folder>
<Folder title="Rendering Activity">
<RenderIndicator />
<FpsGraph />
</Folder>
</Pane>
<script lang="ts">
import { Splat, SplatLoader } from '@pmndrs/vanilla'
import { T, useLoader, useTask, useThrelte } from '@threlte/core'
export let src: string
export let alphaHash = false
export let alphaTest: number | undefined = undefined
export let toneMapped: boolean | undefined = undefined
const { renderer, camera } = useThrelte()
const loader = useLoader(SplatLoader, {
args: [renderer]
})
let framesRendered = 0
const { start, stop } = useTask(
() => {
framesRendered++
// render for 10 frames
if (framesRendered >= 10) {
stop()
framesRendered = 0
}
},
{ autoStart: false }
)
</script>
{#await loader.load(src) then splat}
<T
{...$$restProps}
dispose={false}
is={Splat}
args={[
splat,
$camera,
{
alphaHash,
alphaTest,
toneMapped
}
]}
on:create={start}
let:ref
>
<slot {ref} />
</T>
{/await}
import type { Splat } from '@pmndrs/vanilla'
import type { Events, Props, Slots } from '@threlte/core'
import { SvelteComponent } from 'svelte'
export type SplatProps = Props<Splat> & {
alphaHash?: boolean
alphaTest?: number | undefined
toneMapped?: boolean
}
export type SplatEvents = Events<Splat>
export type SplatSlots = Slots<Splat>
export default class Splat extends SvelteComponent<SplatProps, SplatEvents, SplatSlots> {}
Models: