Skip to content

Commit 16dcf1a

Browse files
authored
Add fast render path for SOGS spherical harmonics (#7796)
1 parent 0c113b2 commit 16dcf1a

File tree

15 files changed

+609
-208
lines changed

15 files changed

+609
-208
lines changed

src/framework/components/gsplat/component.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class GSplatComponent extends Component {
6161
*/
6262
_materialTmp = null;
6363

64+
/** @private */
65+
_highQualitySH = false;
66+
6467
/**
6568
* @type {BoundingBox|null}
6669
* @private
@@ -206,6 +209,37 @@ class GSplatComponent extends Component {
206209
return this._instance?.material ?? this._materialTmp ?? null;
207210
}
208211

212+
/**
213+
* Sets whether to use the high quality or the approximate (but fast) spherical-harmonic calculation when rendering SOGS data.
214+
*
215+
* The low quality approximation evaluates the scene's spherical harmonic contributions
216+
* along the camera's Z-axis instead of using each gaussian's view vector. This results
217+
* in gaussians being accurate at the center of the screen and becoming less accurate
218+
* as they appear further from the center. This is a good trade-off for performance
219+
* when rendering large SOGS datasets, especially on mobile devices.
220+
*
221+
* Defaults to false.
222+
*
223+
* @type {boolean}
224+
*/
225+
set highQualitySH(value) {
226+
if (value !== this._highQualitySH) {
227+
this._highQualitySH = value;
228+
if (this._instance) {
229+
this._instance.setHighQualitySH(value);
230+
}
231+
}
232+
}
233+
234+
/**
235+
* Gets whether the high quality (true) or the fast approximate (false) spherical-harmonic calculation is used when rendering SOGS data.
236+
*
237+
* @type {boolean}
238+
*/
239+
get highQualitySH() {
240+
return this._highQualitySH;
241+
}
242+
209243
/**
210244
* Sets whether gsplat will cast shadows for lights that have shadow casting enabled. Defaults
211245
* to false.
@@ -469,7 +503,10 @@ class GSplatComponent extends Component {
469503
// create new instance
470504
const asset = this._assetReference.asset;
471505
if (asset) {
472-
this.instance = new GSplatInstance(asset.resource, this._materialTmp);
506+
this.instance = new GSplatInstance(asset.resource, {
507+
material: this._materialTmp,
508+
highQualitySH: this._highQualitySH
509+
});
473510
this._materialTmp = null;
474511
this.customAabb = this.instance.resource.aabb.clone();
475512
}

src/framework/components/gsplat/system.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ const _schema = [
1616
// order matters here
1717
const _properties = [
1818
'castShadows',
19+
'material',
20+
'highQualitySH',
1921
'asset',
20-
'layers',
21-
'material'
22+
'layers'
2223
];
2324

2425
/**

src/scene/gsplat/gsplat-instance.js

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { Mat4 } from '../../core/math/mat4.js';
22
import { Vec3 } from '../../core/math/vec3.js';
33
import { CULLFACE_NONE, SEMANTIC_ATTR13, SEMANTIC_POSITION, PIXELFORMAT_R32U } from '../../platform/graphics/constants.js';
44
import { MeshInstance } from '../mesh-instance.js';
5+
import { GSplatResolveSH } from './gsplat-resolve-sh.js';
56
import { GSplatSorter } from './gsplat-sorter.js';
7+
import { GSplatSogsData } from './gsplat-sogs-data.js';
68
import { ShaderMaterial } from '../materials/shader-material.js';
79
import { BLEND_NONE, BLEND_PREMULTIPLIED } from '../constants.js';
810

@@ -34,13 +36,16 @@ class GSplatInstance {
3436

3537
options = {};
3638

37-
/** @type {GSplatSorter | null} */
39+
/** @type {GSplatSorter|null} */
3840
sorter = null;
3941

4042
lastCameraPosition = new Vec3();
4143

4244
lastCameraDirection = new Vec3();
4345

46+
/** @type {GSplatResolveSH|null} */
47+
resolveSH = null;
48+
4449
/**
4550
* List of cameras this instance is visible for. Updated every frame by the renderer.
4651
*
@@ -51,9 +56,11 @@ class GSplatInstance {
5156

5257
/**
5358
* @param {GSplatResourceBase} resource - The splat instance.
54-
* @param {ShaderMaterial|null} material - The material instance.
59+
* @param {object} [options] - Options for the instance.
60+
* @param {ShaderMaterial|null} [options.material] - The material instance.
61+
* @param {boolean} [options.highQualitySH] - Whether to use the high quality or the approximate spherical harmonic calculation. Only applies to SOGS data.
5562
*/
56-
constructor(resource, material) {
63+
constructor(resource, options = {}) {
5764
this.resource = resource;
5865

5966
// create the order texture
@@ -63,9 +70,9 @@ class GSplatInstance {
6370
resource.evalTextureSize(resource.numSplats)
6471
);
6572

66-
if (material) {
73+
if (options.material) {
6774
// material is provided
68-
this._material = material;
75+
this._material = options.material;
6976

7077
// patch splat order
7178
this._material.setParameter('splatOrder', this.orderTexture);
@@ -111,9 +118,13 @@ class GSplatInstance {
111118
// update splat count on the material
112119
this.material.setParameter('numSplats', count);
113120
});
121+
122+
// configure sogs sh resolve
123+
this.setHighQualitySH(options.highQualitySH ?? false);
114124
}
115125

116126
destroy() {
127+
this.resolveSH?.destroy();
117128
this.material?.destroy();
118129
this.meshInstance?.destroy();
119130
this.sorter?.destroy();
@@ -212,10 +223,30 @@ class GSplatInstance {
212223
const camera = this.cameras[0];
213224
this.sort(camera._node);
214225

226+
// resolve spherical harmonics
227+
this.resolveSH?.render(camera._node, this.meshInstance.node.getWorldTransform());
228+
215229
// we get new list of cameras each frame
216230
this.cameras.length = 0;
217231
}
218232
}
233+
234+
setHighQualitySH(value) {
235+
const { resource } = this;
236+
const { gsplatData } = resource;
237+
238+
if (gsplatData instanceof GSplatSogsData &&
239+
gsplatData.shBands > 0 &&
240+
value === !!this.resolveSH) {
241+
242+
if (this.resolveSH) {
243+
this.resolveSH.destroy();
244+
this.resolveSH = null;
245+
} else {
246+
this.resolveSH = new GSplatResolveSH(resource.device, this);
247+
}
248+
}
249+
}
219250
}
220251

221252
export { GSplatInstance };

0 commit comments

Comments
 (0)