Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@
"webgpu_multisampled_renderbuffers",
"webgpu_occlusion",
"webgpu_ocean",
"webgpu_oit",
"webgpu_parallax_uv",
"webgpu_particles",
"webgpu_particles_soft",
Expand Down
146 changes: 142 additions & 4 deletions examples/jsm/objects/GaussianSplatMesh.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import {
Box3,
BufferAttribute,
Color,
InstancedBufferGeometry,
Matrix3,
Matrix4,
Mesh,
NodeMaterial,
Sphere,
StorageBufferAttribute,
Vector2,
Vector3
Expand Down Expand Up @@ -48,6 +52,7 @@ const BIN_COUNT = 4096;
const WORKGROUP_SIZE = 256;
const SORT_DIRECTION_THRESHOLD = 0.9995;
const KERNEL_2D_SIZE = 0.3;
const SPLAT_KERNEL_CUTOFF = 2;
const MAX_SCREEN_SPACE_SPLAT_SIZE = 1024;
const CLIP_XY = 1.4;

Expand All @@ -58,6 +63,8 @@ const _sortDirection = /*@__PURE__*/ new Vector3();
const _sortDepthRange = /*@__PURE__*/ new Vector2();
const _worldMatrixInverse = /*@__PURE__*/ new Matrix4();
const _modelViewMatrix = /*@__PURE__*/ new Matrix4();
const _splatBox = /*@__PURE__*/ new Box3();
const _splat = {};

/**
* A minimal renderer for 3D Gaussian splat geometry.
Expand Down Expand Up @@ -128,15 +135,29 @@ class GaussianSplatMesh extends Mesh {
*/
this.splatGeometry = splatGeometry;

/**
* The bounding box of the splats. Can be computed via {@link GaussianSplatMesh#computeBoundingBox}.
*
* @type {?Box3}
* @default null
*/
this.boundingBox = null;

/**
* The bounding sphere of the splats. Can be computed via {@link GaussianSplatMesh#computeBoundingSphere}.
*
* @type {?Sphere}
* @default null
*/
this.boundingSphere = null;

/**
* Whether to sort automatically in `onBeforeRender`.
*
* @type {boolean}
*/
this.autoSort = autoSort;

this.frustumCulled = false;

this._buffers = buffers;
this._sort = sort;
this._sortMatrix = uniform( new Matrix4() );
Expand Down Expand Up @@ -239,6 +260,121 @@ class GaussianSplatMesh extends Mesh {

}

/**
* Returns the splat at the given index.
*
* Members that the target does not already have are created, so an empty object can be passed
* and then reused across calls to avoid allocating.
*
* @param {number} index - The splat index.
* @param {Object} [target] - The object the splat is written to.
* @return {Object} The target, with `position`, `covariance`, `color`, `opacity` and `radius` set.
*/
getSplat( index, target = {} ) {

const geometry = this.splatGeometry;
const positionAttribute = geometry.getAttribute( 'position' );
const covarianceAttribute = geometry.getAttribute( 'covariance' );
const colorAttribute = geometry.getAttribute( 'color' );

if ( target.position === undefined ) {

target.position = new Vector3();

}

if ( target.covariance === undefined ) {

target.covariance = new Matrix3();

}

if ( target.color === undefined ) {

target.color = new Color();

}

target.position.fromBufferAttribute( positionAttribute, index );

const c00 = covarianceAttribute.getComponent( index, 0 );
const c01 = covarianceAttribute.getComponent( index, 1 );
const c02 = covarianceAttribute.getComponent( index, 2 );
const c11 = covarianceAttribute.getComponent( index, 3 );
const c12 = covarianceAttribute.getComponent( index, 4 );
const c22 = covarianceAttribute.getComponent( index, 5 );

// the attribute holds the upper triangle of the symmetric covariance
target.covariance.set(
c00, c01, c02,
c01, c11, c12,
c02, c12, c22
);

target.color.fromBufferAttribute( colorAttribute, index );
target.opacity = colorAttribute.getW( index );

// the radius of the drawn largest extent
target.radius = SPLAT_KERNEL_CUTOFF * Math.sqrt( Math.max( c00, c11, c22, 0 ) );

return target;

}

/**
* Computes the bounding box of the splats, updating {@link GaussianSplatMesh#boundingBox}.
*
* Each splat is expanded by its own extent rather than treated as a point, so the bounds cover
* what is drawn.
*/
computeBoundingBox() {

if ( this.boundingBox === null ) this.boundingBox = new Box3();

this.boundingBox.makeEmpty();

const count = this.splatGeometry.getAttribute( 'position' ).count;

for ( let i = 0; i < count; i ++ ) {

this.getSplat( i, _splat );

_splatBox.set( _splat.position, _splat.position ).expandByScalar( _splat.radius );
this.boundingBox.union( _splatBox );

}

}

/**
* Computes the bounding sphere of the splats, updating {@link GaussianSplatMesh#boundingSphere}.
*
* Each splat is expanded by its own extent rather than treated as a point, so the bounds cover
* what is drawn.
*/
computeBoundingSphere() {

if ( this.boundingSphere === null ) this.boundingSphere = new Sphere();

this.computeBoundingBox();
this.boundingBox.getBoundingSphere( this.boundingSphere );

let maxRadius = 0;
const center = this.boundingSphere.center;
const count = this.splatGeometry.getAttribute( 'position' ).count;

for ( let i = 0; i < count; i ++ ) {

this.getSplat( i, _splat );

maxRadius = Math.max( maxRadius, center.distanceTo( _splat.position ) + _splat.radius );

}

this.boundingSphere.radius = maxRadius;

}

/**
* Updates the draw order if the camera or mesh orientation has changed enough
* to need a new sort.
Expand Down Expand Up @@ -295,12 +431,14 @@ class GaussianSplatMesh extends Mesh {

this._sortMatrix.value.multiplyMatrices( camera.matrixWorldInverse, this.matrixWorld );

_worldCenter.copy( this.splatGeometry.boundingSphere.center ).applyMatrix4( this.matrixWorld );
if ( this.boundingSphere === null ) this.computeBoundingSphere();

_worldCenter.copy( this.boundingSphere.center ).applyMatrix4( this.matrixWorld );
_viewCenter.copy( _worldCenter ).applyMatrix4( camera.matrixWorldInverse );

_worldScale.setFromMatrixScale( this.matrixWorld );

const radius = this.splatGeometry.boundingSphere.radius * Math.max( _worldScale.x, _worldScale.y, _worldScale.z );
const radius = this.boundingSphere.radius * Math.max( _worldScale.x, _worldScale.y, _worldScale.z );
const depth = - _viewCenter.z;
const nearDepth = Math.max( camera.near, depth - radius );
const farDepth = Math.max( nearDepth + 0.0001, depth + radius );
Expand Down
Loading