Skip to content
58 changes: 19 additions & 39 deletions src/framework/components/gsplat/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import { Component } from '../component.js';
* @import { Entity } from '../../entity.js'
* @import { EventHandle } from '../../../core/event-handle.js'
* @import { GSplatComponentSystem } from './system.js'
* @import { Material } from '../../../scene/materials/material.js'
* @import { SplatMaterialOptions } from '../../../scene/gsplat/gsplat-material.js'
* @import { ShaderMaterial } from '../../../scene/materials/shader-material.js'
*/

/**
Expand Down Expand Up @@ -56,22 +55,22 @@ class GSplatComponent extends Component {
_instance = null;

/**
* @type {BoundingBox|null}
* @type {ShaderMaterial|null}
* @private
*/
_customAabb = null;
_materialStore = null;

/**
* @type {AssetReference}
* @type {BoundingBox|null}
* @private
*/
_assetReference;
_customAabb = null;

/**
* @type {SplatMaterialOptions|null}
* @type {AssetReference}
* @private
*/
_materialOptions = null;
_assetReference;

/**
* @type {EventHandle|null}
Expand Down Expand Up @@ -158,22 +157,16 @@ class GSplatComponent extends Component {

this._instance = value;

if (this._instance?.meshInstance) {
if (this._instance) {

// if mesh instance was created without a node, assign it here
const mi = this._instance.meshInstance;
if (!mi.node) {
mi.node = this.entity;
}

mi.castShadow = this._castShadows;
mi.setCustomAabb(this._customAabb);

// if we have custom shader options, apply them
if (this._materialOptions) {
this._instance.createMaterial(this._materialOptions);
}

if (this.enabled && this.entity.enabled) {
this.addToLayers();
}
Expand All @@ -190,26 +183,24 @@ class GSplatComponent extends Component {
return this._instance;
}

set materialOptions(value) {
this._materialOptions = Object.assign({}, value);

// apply them on the instance if it exists
/**
* @param {ShaderMaterial} value - The material instance.
*/
set material(value) {
if (this._instance) {
this._instance.createMaterial(this._materialOptions);
this._instance.material = value;
} else {
this._materialStore = value;
}
}

get materialOptions() {
return this._materialOptions;
}

/**
* Gets the material used to render the gsplat.
*
* @type {Material|undefined}
* @type {ShaderMaterial|null}
*/
get material() {
return this._instance?.material;
return this._instance?.material ?? this._materialStore ?? null;
}

/**
Expand Down Expand Up @@ -326,18 +317,6 @@ class GSplatComponent extends Component {
return this._assetReference.id;
}

/**
* Assign asset id to the component, without updating the component with the new asset.
* This can be used to assign the asset id to already fully created component.
*
* @param {Asset|number} asset - The gsplat asset or asset id to assign.
* @ignore
*/
assignAsset(asset) {
const id = asset instanceof Asset ? asset.id : asset;
this._assetReference.id = id;
}

/** @private */
destroyInstance() {
if (this._instance) {
Expand Down Expand Up @@ -487,7 +466,8 @@ class GSplatComponent extends Component {
// create new instance
const asset = this._assetReference.asset;
if (asset) {
this.instance = new GSplatInstance(asset.resource, this._materialOptions || {});
this.instance = new GSplatInstance(asset.resource, this._materialStore);
this._materialStore = null;
this.customAabb = this.instance.resource.aabb.clone();
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/platform/graphics/webgpu/webgpu-graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
this.samples = this.backBufferAntialias ? 4 : 1;

// WGSL features
const wgslFeatures = navigator.gpu.wgslLanguageFeatures;
const wgslFeatures = window.navigator.gpu.wgslLanguageFeatures;
this.supportsStorageTextureRead = wgslFeatures?.has('readonly_and_readwrite_storage_textures');

this.initCapsDefines();
Expand Down Expand Up @@ -291,7 +291,7 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
let canvasToneMapping = 'standard';

// pixel format of the framebuffer that is the most efficient one on the system
let preferredCanvasFormat = navigator.gpu.getPreferredCanvasFormat();
let preferredCanvasFormat = window.navigator.gpu.getPreferredCanvasFormat();

// display format the user asked for
const displayFormat = this.initOptions.displayFormat;
Expand Down Expand Up @@ -346,7 +346,7 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
// (this allows us to view the preferred format as srgb)
viewFormats: displayFormat === DISPLAYFORMAT_LDR_SRGB ? [this.backBufferViewFormat] : []
};
this.gpuContext.configure(this.canvasConfig);
this.gpuContext?.configure(this.canvasConfig);

this.createBackbuffer();

Expand Down
8 changes: 4 additions & 4 deletions src/platform/graphics/webgpu/webgpu-texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,10 +401,10 @@ class WebgpuTexture {

// image types supported by copyExternalImageToTexture
isExternalImage(image) {
return (image instanceof ImageBitmap) ||
(image instanceof HTMLVideoElement) ||
(image instanceof HTMLCanvasElement) ||
(image instanceof OffscreenCanvas);
return (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap) ||
(typeof HTMLVideoElement !== 'undefined' && image instanceof HTMLVideoElement) ||
(typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement) ||
(typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas);
}

uploadExternalImage(device, image, mipLevel, index) {
Expand Down
26 changes: 9 additions & 17 deletions src/scene/gsplat/gsplat-compressed-resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
import {
PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA32U
} from '../../platform/graphics/constants.js';
import { createGSplatMaterial } from './gsplat-material.js';

Check failure on line 5 in src/scene/gsplat/gsplat-compressed-resource.js

View workflow job for this annotation

GitHub Actions / Lint

'createGSplatMaterial' is defined but never used
import { GSplatResourceBase } from './gsplat-resource-base.js';

/**
* @import { GSplatCompressedData } from './gsplat-compressed-data.js'
* @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
* @import { Texture } from '../../platform/graphics/texture.js';
* @import { Material } from '../materials/material.js'
* @import { SplatMaterialOptions } from './gsplat-material.js'
*/

// copy data with padding
Expand Down Expand Up @@ -93,24 +91,18 @@
this.shTexture2?.destroy();
}

/**
* @param {SplatMaterialOptions} options - The splat material options.
* @returns {Material} material - The material to set up for the splat rendering.
*/
createMaterial(options) {
const result = createGSplatMaterial(this.device, options);
result.setDefine('GSPLAT_COMPRESSED_DATA', true);
result.setParameter('packedTexture', this.packedTexture);
result.setParameter('chunkTexture', this.chunkTexture);
configureMaterial(material) {
material.setDefine('GSPLAT_COMPRESSED_DATA', true);
material.setParameter('packedTexture', this.packedTexture);
material.setParameter('chunkTexture', this.chunkTexture);
if (this.shTexture0) {
result.setDefine('SH_BANDS', 3);
result.setParameter('shTexture0', this.shTexture0);
result.setParameter('shTexture1', this.shTexture1);
result.setParameter('shTexture2', this.shTexture2);
material.setDefine('SH_BANDS', 3);
material.setParameter('shTexture0', this.shTexture0);
material.setParameter('shTexture1', this.shTexture1);
material.setParameter('shTexture2', this.shTexture2);
} else {
result.setDefine('SH_BANDS', 0);
material.setDefine('SH_BANDS', 0);
}
return result;
}

/**
Expand Down
114 changes: 79 additions & 35 deletions src/scene/gsplat/gsplat-instance.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Mat4 } from '../../core/math/mat4.js';
import { Vec3 } from '../../core/math/vec3.js';
import { PIXELFORMAT_R32U } from '../../platform/graphics/constants.js';
import { DITHER_NONE } from '../constants.js';
import { SEMANTIC_ATTR13, SEMANTIC_POSITION, PIXELFORMAT_R32U } from '../../platform/graphics/constants.js';

Check warning on line 3 in src/scene/gsplat/gsplat-instance.js

View workflow job for this annotation

GitHub Actions / Lint

'/home/runner/work/engine/engine/src/platform/graphics/constants.js' imported multiple times
import { MeshInstance } from '../mesh-instance.js';
import { GSplatSorter } from './gsplat-sorter.js';
import { ShaderMaterial } from '../materials/shader-material.js';
import { BLEND_NONE, BLEND_PREMULTIPLIED } from '../constants.js';
import { CULLFACE_NONE } from '../../platform/graphics/constants.js';

Check warning on line 8 in src/scene/gsplat/gsplat-instance.js

View workflow job for this annotation

GitHub Actions / Lint

'/home/runner/work/engine/engine/src/platform/graphics/constants.js' imported multiple times

Check failure on line 8 in src/scene/gsplat/gsplat-instance.js

View workflow job for this annotation

GitHub Actions / Lint

'../../platform/graphics/constants.js' import is duplicated

/**
* @import { Camera } from '../camera.js'
* @import { GSplatResourceBase } from './gsplat-resource-base.js'
* @import { GraphNode } from '../graph-node.js'
* @import { Material } from '../materials/material.js'
* @import { SplatMaterialOptions } from './gsplat-material.js'
* @import { Texture } from '../../platform/graphics/texture.js'
*/

Expand All @@ -24,15 +24,15 @@
/** @type {GSplatResourceBase} */
resource;

/** @type {MeshInstance} */
meshInstance;

/** @type {Material} */
material;

/** @type {Texture} */
orderTexture;

/** @type {ShaderMaterial} */
_material;

/** @type {MeshInstance} */
meshInstance;

options = {};

/** @type {GSplatSorter | null} */
Expand All @@ -52,25 +52,43 @@

/**
* @param {GSplatResourceBase} resource - The splat instance.
* @param {SplatMaterialOptions} options - The options.
* @param {ShaderMaterial|null} material - The material instance.
*/
constructor(resource, options) {
constructor(resource, material) {
this.resource = resource;

// clone options object
options = Object.assign(this.options, options);

// create the order texture
this.orderTexture = resource.createTexture(
'splatOrder',
PIXELFORMAT_R32U,
resource.evalTextureSize(resource.numSplats)
);

// material
this.createMaterial(options);
if (material) {
// material is provided
this._material = material;
} else {
// construct the material
this._material = new ShaderMaterial({
uniqueName: 'SplatMaterial',
vertexGLSL: '#include "gsplatVS"',
fragmentGLSL: '#include "gsplatPS"',
vertexWGSL: '#include "gsplatVS"',
fragmentWGSL: '#include "gsplatPS"',
attributes: {
vertex_position: SEMANTIC_POSITION,
vertex_id_attrib: SEMANTIC_ATTR13
}
});

// default configure
this.configureMaterial(this._material);

// update
this._material.update();
}

this.meshInstance = new MeshInstance(resource.mesh, this.material);
this.meshInstance = new MeshInstance(resource.mesh, this._material);
this.meshInstance.setInstancing(resource.instanceIndices, true);
this.meshInstance.gsplatInstance = this;

Expand All @@ -82,17 +100,15 @@
const chunks = resource.chunks?.slice();

// create sorter
if (!options.dither || options.dither === DITHER_NONE) {
this.sorter = new GSplatSorter();
this.sorter.init(this.orderTexture, centers, chunks);
this.sorter.on('updated', (count) => {
// limit splat render count to exclude those behind the camera
this.meshInstance.instancingCount = Math.ceil(count / resource.instanceSize);

// update splat count on the material
this.material.setParameter('numSplats', count);
});
}
this.sorter = new GSplatSorter();
this.sorter.init(this.orderTexture, centers, chunks);
this.sorter.on('updated', (count) => {
// limit splat render count to exclude those behind the camera
this.meshInstance.instancingCount = Math.ceil(count / resource.instanceSize);

// update splat count on the material
this.material.setParameter('numSplats', count);
});
}

destroy() {
Expand All @@ -105,15 +121,43 @@
return new GSplatInstance(this.resource, this.options);
}

createMaterial(options) {
this.material = this.resource.createMaterial(options);
this.material.setParameter('splatOrder', this.orderTexture);
this.material.setParameter('alphaClip', 0.3);
if (this.meshInstance) {
this.meshInstance.material = this.material;
/**
* @param {ShaderMaterial} value - The material instance.
*/
set material(value) {
if (this.material !== value) {
// set the new material
this.material = value;

if (this.meshInstance) {
this.meshInstance.material = value;
}
}
}

get material() {
return this._material;
}

/**
* Configure the material with gsplat instance and resource properties.
*

Check failure on line 144 in src/scene/gsplat/gsplat-instance.js

View workflow job for this annotation

GitHub Actions / Lint

Trailing spaces not allowed
* @param {ShaderMaterial} material

Check failure on line 145 in src/scene/gsplat/gsplat-instance.js

View workflow job for this annotation

GitHub Actions / Lint

Trailing spaces not allowed

Check failure on line 145 in src/scene/gsplat/gsplat-instance.js

View workflow job for this annotation

GitHub Actions / Lint

Missing JSDoc @param "material" description
* @param {boolean} dither

Check failure on line 146 in src/scene/gsplat/gsplat-instance.js

View workflow job for this annotation

GitHub Actions / Lint

Trailing spaces not allowed

Check failure on line 146 in src/scene/gsplat/gsplat-instance.js

View workflow job for this annotation

GitHub Actions / Lint

Missing JSDoc @param "dither" description
*/
configureMaterial(material, dither = false) {
// allow resource to configure the material
this.resource.configureMaterial(material);

// set instance properties
material.setParameter('splatOrder', this.orderTexture);
material.setParameter('alphaClip', 0.3);
material.setDefine(`DITHER_${dither ? 'BLUENOISE' : 'NONE'}`, '');
material.cull = CULLFACE_NONE;
material.blendType = dither ? BLEND_NONE : BLEND_PREMULTIPLIED;
material.depthWrite = dither;
}

updateViewport(cameraNode) {
const camera = cameraNode?.camera;
const renderTarget = camera?.renderTarget;
Expand Down
6 changes: 6 additions & 0 deletions src/scene/gsplat/gsplat-resource-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ class GSplatResourceBase {
return this.gsplatData.numSplats;
}

configureMaterial(material) {
}

evalTextureSize(count) {
}

/**
* Creates a new texture with the specified parameters.
*
Expand Down
Loading
Loading