-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Gaussian splat spherical harmonics support #12790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
7c6a2a8
a78ab94
94165d2
faf0111
2448e21
f9f99aa
42e0c8d
81d252b
9026e4a
1d03ac0
40e4fe6
8ecb57c
0b099c3
3e0ac2b
c3cc59c
abc8209
a18a935
a0baf6c
b725db4
2836299
38bba9a
032fb1f
ae0adcc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import GaussianSplatPrimitive from "./GaussianSplatPrimitive.js"; | |
import destroyObject from "../Core/destroyObject.js"; | ||
import ModelUtility from "./Model/ModelUtility.js"; | ||
import VertexAttributeSemantic from "./VertexAttributeSemantic.js"; | ||
import deprecationWarning from "../Core/deprecationWarning.js"; | ||
|
||
/** | ||
* Represents the contents of a glTF or glb using the {@link https://github.com/CesiumGS/glTF/tree/draft-spz-splat-compression/extensions/2.0/Khronos/KHR_spz_gaussian_splats_compression|KHR_spz_gaussian_splats_compression} extension. | ||
|
@@ -78,6 +79,20 @@ function GaussianSplat3DTileContent(loader, tileset, tile, resource) { | |
* @private | ||
*/ | ||
this._transformed = false; | ||
|
||
/** | ||
* The degree of the spherical harmonics used for the Gaussian splats. | ||
* @type {number} | ||
* @private | ||
*/ | ||
this.shDegree = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are correct it can be derived, but it's nice to not have to and keeps things more explicit. They are used in different ways when making checks and indexing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we set one and use a getter function for the other? I'm mostly concerned about these values getting out of sync since they can both be set. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I see now they are set privately and only exposed via the getter. Should all be fine then. 👍 |
||
|
||
/** | ||
* The number of spherical harmonic coefficients used for the Gaussian splats. | ||
* @type {number} | ||
* @private | ||
*/ | ||
this.shCoefficientCount = 0; | ||
keyboardspecialist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
Object.defineProperties(GaussianSplat3DTileContent.prototype, { | ||
|
@@ -311,6 +326,172 @@ Object.defineProperties(GaussianSplat3DTileContent.prototype, { | |
}, | ||
}); | ||
|
||
GaussianSplat3DTileContent.tilesetHasGaussianSplattingExt = function (tileset) { | ||
keyboardspecialist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let hasGaussianSplatExtension = false; | ||
let hasLegacyGaussianSplatExtension = false; | ||
if (tileset.isGltfExtensionRequired instanceof Function) { | ||
hasGaussianSplatExtension = | ||
tileset.isGltfExtensionRequired("KHR_gaussian_splatting") && | ||
tileset.isGltfExtensionRequired( | ||
"KHR_gaussian_splatting_compression_spz_2", | ||
); | ||
|
||
hasLegacyGaussianSplatExtension = tileset.isGltfExtensionRequired( | ||
"KHR_spz_gaussian_splats_compression", | ||
); | ||
} | ||
|
||
if (hasLegacyGaussianSplatExtension) { | ||
deprecationWarning( | ||
"KHR_spz_gaussian_splats_compression", | ||
"Support for the original KHR_spz_gaussian_splats_compression extension has been deprecated in favor " + | ||
"of the up to date KHR_gaussian_splatting and KHR_gaussian_splatting_compression_spz_2 extensions and will be " + | ||
"removed in CesiumJS 1.134.\n\nPlease retile your tileset with the KHR_gaussian_splatting and " + | ||
"KHR_gaussian_splatting_compression_spz_2 extensions.", | ||
); | ||
} | ||
|
||
return hasGaussianSplatExtension || hasLegacyGaussianSplatExtension; | ||
}; | ||
|
||
/** | ||
* Determine Spherical Harmonics degree and coefficient count from attributes | ||
* @param {Array} attributes - The list of attributes. | ||
* @returns {Object} An object containing the degree (l) and coefficient (n). | ||
keyboardspecialist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
function degreeAndCoefFromAttributes(attributes) { | ||
const prefix = "_SH_DEGREE_"; | ||
const shAttributes = attributes.filter((attr) => | ||
attr.name.startsWith(prefix), | ||
); | ||
keyboardspecialist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
switch (shAttributes.length) { | ||
default: | ||
case 0: | ||
return { l: 0, n: 0 }; | ||
case 3: | ||
return { l: 1, n: 9 }; | ||
case 8: | ||
return { l: 2, n: 24 }; | ||
case 15: | ||
return { l: 3, n: 45 }; | ||
} | ||
} | ||
|
||
/** | ||
* Converts a 32-bit floating point number to a 16-bit floating point number. | ||
* @param {Float} float32 input | ||
keyboardspecialist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @returns {number} Half precision float | ||
* @private | ||
*/ | ||
const buffer = new ArrayBuffer(4); | ||
const floatView = new Float32Array(buffer); | ||
const intView = new Uint32Array(buffer); | ||
|
||
function float32ToFloat16(float32) { | ||
floatView[0] = float32; | ||
const bits = intView[0]; | ||
|
||
const sign = (bits >> 31) & 0x1; | ||
const exponent = (bits >> 23) & 0xff; | ||
const mantissa = bits & 0x7fffff; | ||
|
||
let half; | ||
|
||
if (exponent === 0xff) { | ||
half = (sign << 15) | (0x1f << 10) | (mantissa ? 0x200 : 0); | ||
} else if (exponent === 0) { | ||
half = sign << 15; | ||
} else { | ||
const newExponent = exponent - 127 + 15; | ||
if (newExponent >= 31) { | ||
half = (sign << 15) | (0x1f << 10); | ||
} else if (newExponent <= 0) { | ||
half = sign << 15; | ||
} else { | ||
half = (sign << 15) | (newExponent << 10) | (mantissa >>> 13); | ||
} | ||
} | ||
|
||
return half; | ||
} | ||
|
||
/** | ||
* Extracts the spherical harmonic degree and coefficient from the attribute name. | ||
* @param {String} attribute - The attribute name. | ||
keyboardspecialist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @returns {Object} An object containing the degree (l) and coefficient (n). | ||
keyboardspecialist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @private | ||
*/ | ||
function extractSHDegreeAndCoef(attribute) { | ||
const prefix = "_SH_DEGREE_"; | ||
const separator = "_COEF_"; | ||
|
||
const lStart = prefix.length; | ||
const coefIndex = attribute.indexOf(separator, lStart); | ||
|
||
const l = parseInt(attribute.slice(lStart, coefIndex), 10); | ||
const n = parseInt(attribute.slice(coefIndex + separator.length), 10); | ||
|
||
return { l, n }; | ||
keyboardspecialist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
* Packs spherical harmonic data into half-precision floats. | ||
* @param {*} data - The input data to pack. | ||
* @param {*} shDegree - The spherical harmonic degree. | ||
* @returns {Uint32Array} - The packed data. | ||
*/ | ||
function packSphericalHarmonicData(tileContent) { | ||
const degree = tileContent.shDegree; | ||
const coefs = tileContent.shCoefficientCount; | ||
const totalLength = tileContent.pointsLength * (coefs * (2 / 3)); //3 packs into 2 | ||
const packedData = new Uint32Array(totalLength); | ||
|
||
const shAttributes = tileContent.splatPrimitive.attributes.filter((attr) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's defined in the GS content class. Very ambiguous, but it's a reference to the glTF primitive. I renamed it for better clarity. |
||
attr.name.startsWith("_SH_DEGREE_"), | ||
); | ||
keyboardspecialist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let stride = 0; | ||
const base = [0, 9, 24]; | ||
switch (degree) { | ||
case 1: | ||
stride = 9; | ||
break; | ||
case 2: | ||
stride = 24; | ||
break; | ||
case 3: | ||
stride = 45; | ||
break; | ||
} | ||
shAttributes.sort((a, b) => { | ||
if (a.name < b.name) { | ||
return -1; | ||
} | ||
if (a.name > b.name) { | ||
return 1; | ||
} | ||
|
||
return 0; | ||
}); | ||
const packedStride = stride * (2 / 3); | ||
for (let i = 0; i < shAttributes.length; i++) { | ||
const { l, n } = extractSHDegreeAndCoef(shAttributes[i].name); | ||
for (let j = 0; j < tileContent.pointsLength; j++) { | ||
//interleave the data | ||
const packedBase = (base[l - 1] * 2) / 3; | ||
const idx = j * packedStride + packedBase + n * 2; | ||
const src = j * 3; | ||
packedData[idx] = | ||
float32ToFloat16(shAttributes[i].typedArray[src]) | | ||
(float32ToFloat16(shAttributes[i].typedArray[src + 1]) << 16); | ||
packedData[idx + 1] = float32ToFloat16( | ||
shAttributes[i].typedArray[src + 2], | ||
); | ||
} | ||
} | ||
return packedData; | ||
} | ||
|
||
/** | ||
* Creates a new instance of {@link GaussianSplat3DTileContent} from a glTF or glb resource. | ||
* | ||
|
@@ -412,6 +593,14 @@ GaussianSplat3DTileContent.prototype.update = function (primitive, frameState) { | |
).typedArray, | ||
); | ||
|
||
const { l, n } = degreeAndCoefFromAttributes( | ||
this.splatPrimitive.attributes, | ||
); | ||
this.shDegree = l; | ||
this.shCoefficientCount = n; | ||
|
||
this._packedShData = packSphericalHarmonicData(this); | ||
|
||
return; | ||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.