Yosun's picture
Deploy TripoSplat WebGPU demo
31c7d49 verified
Raw
History Blame Contribute Delete
3.65 kB
import { createGaussianScene, type GaussianScene, type GaussianSceneMetadata } from '@ai3d/gaussian-scene'
export const GAUSSIANS_PER_POINT = 32
export const GAUSSIAN_FEATURE_WIDTH = 480
const OPACITY_BIAS = Math.log(0.1 / 0.9)
const SCALE_BIAS = Math.log(Math.expm1(0.004))
const OFFSET_SCALE_BIAS = Math.log(Math.expm1(0.05))
const BASE_OFFSETS = buildBaseOffsets()
function softplus(value: number): number {
if (value > 20) return value
if (value < -20) return Math.exp(value)
return Math.log1p(Math.exp(value))
}
function sigmoid(value: number): number {
return value >= 0 ? 1 / (1 + Math.exp(-value)) : Math.exp(value) / (1 + Math.exp(value))
}
function radicalInverse(base: number, value: number): number {
let output = 0
let inversePower = 1 / base
while (value > 0) {
output += (value % base) * inversePower
value = Math.floor(value / base)
inversePower /= base
}
return output
}
function buildBaseOffsets(): Float32Array {
const offsets = new Float32Array(GAUSSIANS_PER_POINT * 3)
for (let index = 0; index < GAUSSIANS_PER_POINT; index += 1) {
const values = [index / GAUSSIANS_PER_POINT, radicalInverse(2, index), radicalInverse(3, index)]
for (let axis = 0; axis < 3; axis += 1) {
offsets[index * 3 + axis] = Math.atanh((values[axis] * 2 - 1) / 1.5)
}
}
return offsets
}
export interface DecodeGaussiansOptions {
metadata: GaussianSceneMetadata
}
/** Decode the official fixed-length ElasticGaussian 480-feature representation. */
export function decodeGaussians(
points: Float32Array,
features: Float32Array,
options: DecodeGaussiansOptions,
): GaussianScene {
if (points.length % 3 !== 0) throw new RangeError('points must contain xyz triplets.')
const pointCount = points.length / 3
if (features.length !== pointCount * GAUSSIAN_FEATURE_WIDTH) {
throw new RangeError(`features must contain ${pointCount * GAUSSIAN_FEATURE_WIDTH} values.`)
}
const count = pointCount * GAUSSIANS_PER_POINT
const positions = new Float32Array(count * 3)
const scales = new Float32Array(count * 3)
const rotations = new Float32Array(count * 4)
const sphericalHarmonics = new Float32Array(count * 3)
const opacities = new Float32Array(count)
for (let point = 0; point < pointCount; point += 1) {
const featureBase = point * GAUSSIAN_FEATURE_WIDTH
for (let gaussian = 0; gaussian < GAUSSIANS_PER_POINT; gaussian += 1) {
const output = point * GAUSSIANS_PER_POINT + gaussian
const output3 = output * 3
const output4 = output * 4
const learnedOffsetScale = softplus(features[featureBase + 448 + gaussian] + OFFSET_SCALE_BIAS)
for (let axis = 0; axis < 3; axis += 1) {
const component = gaussian * 3 + axis
positions[output3 + axis] = points[point * 3 + axis]
+ Math.tanh(features[featureBase + component] + BASE_OFFSETS[component]) * 0.75 * learnedOffsetScale
- 0.5
sphericalHarmonics[output3 + axis] = features[featureBase + 96 + component]
const scale = softplus(features[featureBase + 192 + component] + SCALE_BIAS)
scales[output3 + axis] = Math.sqrt(scale * scale + 0.0009 ** 2)
}
for (let component = 0; component < 4; component += 1) {
rotations[output4 + component] = features[featureBase + 288 + gaussian * 4 + component] * 0.1
+ (component === 0 ? 1 : 0)
}
opacities[output] = sigmoid(features[featureBase + 416 + gaussian] + OPACITY_BIAS)
}
}
return createGaussianScene({
count,
positions,
scales,
rotations,
opacities,
sphericalHarmonics,
metadata: options.metadata,
})
}