playcanvas
Version:
PlayCanvas WebGL game engine
185 lines (182 loc) • 7.45 kB
JavaScript
import { FloatPacking } from '../../core/math/float-packing.js';
import { Quat } from '../../core/math/quat.js';
import { Vec2 } from '../../core/math/vec2.js';
import { Vec3 } from '../../core/math/vec3.js';
import { PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32U, PIXELFORMAT_R32U } from '../../platform/graphics/constants.js';
import { GSplatResourceBase } from './gsplat-resource-base.js';
const getSHData = (gsplatData, numCoeffs)=>{
const result = [];
for(let i = 0; i < numCoeffs; ++i){
result.push(gsplatData.getProp(`f_rest_${i}`));
}
return result;
};
class GSplatResource extends GSplatResourceBase {
constructor(device, gsplatData){
super(device, gsplatData);
const numSplats = gsplatData.numSplats;
const size = this.evalTextureSize(numSplats);
this.colorTexture = this.createTexture('splatColor', PIXELFORMAT_RGBA16F, size);
this.transformATexture = this.createTexture('transformA', PIXELFORMAT_RGBA32U, size);
this.transformBTexture = this.createTexture('transformB', PIXELFORMAT_RGBA16F, size);
this.updateColorData(gsplatData);
this.updateTransformData(gsplatData);
this.shBands = gsplatData.shBands;
if (this.shBands > 0) {
this.sh1to3Texture = this.createTexture('splatSH_1to3', PIXELFORMAT_RGBA32U, size);
if (this.shBands > 1) {
this.sh4to7Texture = this.createTexture('splatSH_4to7', PIXELFORMAT_RGBA32U, size);
if (this.shBands > 2) {
this.sh8to11Texture = this.createTexture('splatSH_8to11', PIXELFORMAT_RGBA32U, size);
this.sh12to15Texture = this.createTexture('splatSH_12to15', PIXELFORMAT_RGBA32U, size);
} else {
this.sh8to11Texture = this.createTexture('splatSH_8to11', PIXELFORMAT_R32U, size);
}
}
this.updateSHData(gsplatData);
}
}
destroy() {
this.colorTexture?.destroy();
this.transformATexture?.destroy();
this.transformBTexture?.destroy();
this.sh1to3Texture?.destroy();
this.sh4to7Texture?.destroy();
this.sh8to11Texture?.destroy();
this.sh12to15Texture?.destroy();
super.destroy();
}
configureMaterial(material) {
material.setParameter('splatColor', this.colorTexture);
material.setParameter('transformA', this.transformATexture);
material.setParameter('transformB', this.transformBTexture);
material.setDefine('SH_BANDS', this.shBands);
if (this.sh1to3Texture) material.setParameter('splatSH_1to3', this.sh1to3Texture);
if (this.sh4to7Texture) material.setParameter('splatSH_4to7', this.sh4to7Texture);
if (this.sh8to11Texture) material.setParameter('splatSH_8to11', this.sh8to11Texture);
if (this.sh12to15Texture) material.setParameter('splatSH_12to15', this.sh12to15Texture);
}
evalTextureSize(count) {
const width = Math.ceil(Math.sqrt(count));
const height = Math.ceil(count / width);
return new Vec2(width, height);
}
updateColorData(gsplatData) {
const texture = this.colorTexture;
if (!texture) {
return;
}
const float2Half = FloatPacking.float2Half;
const data = texture.lock();
const cr = gsplatData.getProp('f_dc_0');
const cg = gsplatData.getProp('f_dc_1');
const cb = gsplatData.getProp('f_dc_2');
const ca = gsplatData.getProp('opacity');
const SH_C0 = 0.28209479177387814;
for(let i = 0; i < this.numSplats; ++i){
const r = cr[i] * SH_C0 + 0.5;
const g = cg[i] * SH_C0 + 0.5;
const b = cb[i] * SH_C0 + 0.5;
const a = 1 / (1 + Math.exp(-ca[i]));
data[i * 4 + 0] = float2Half(r);
data[i * 4 + 1] = float2Half(g);
data[i * 4 + 2] = float2Half(b);
data[i * 4 + 3] = float2Half(a);
}
texture.unlock();
}
updateTransformData(gsplatData) {
const float2Half = FloatPacking.float2Half;
if (!this.transformATexture) {
return;
}
const dataA = this.transformATexture.lock();
const dataAFloat32 = new Float32Array(dataA.buffer);
const dataB = this.transformBTexture.lock();
const p = new Vec3();
const r = new Quat();
const s = new Vec3();
const iter = gsplatData.createIter(p, r, s);
for(let i = 0; i < this.numSplats; i++){
iter.read(i);
r.normalize();
if (r.w < 0) {
r.mulScalar(-1);
}
dataAFloat32[i * 4 + 0] = p.x;
dataAFloat32[i * 4 + 1] = p.y;
dataAFloat32[i * 4 + 2] = p.z;
dataA[i * 4 + 3] = float2Half(r.x) | float2Half(r.y) << 16;
dataB[i * 4 + 0] = float2Half(s.x);
dataB[i * 4 + 1] = float2Half(s.y);
dataB[i * 4 + 2] = float2Half(s.z);
dataB[i * 4 + 3] = float2Half(r.z);
}
this.transformATexture.unlock();
this.transformBTexture.unlock();
}
updateSHData(gsplatData) {
const sh1to3Data = this.sh1to3Texture.lock();
const sh4to7Data = this.sh4to7Texture?.lock();
const sh8to11Data = this.sh8to11Texture?.lock();
const sh12to15Data = this.sh12to15Texture?.lock();
const numCoeffs = {
1: 3,
2: 8,
3: 15
}[this.shBands];
const src = getSHData(gsplatData, numCoeffs * 3);
const t11 = (1 << 11) - 1;
const t10 = (1 << 10) - 1;
const float32 = new Float32Array(1);
const uint32 = new Uint32Array(float32.buffer);
const c = new Array(numCoeffs * 3).fill(0);
for(let i = 0; i < gsplatData.numSplats; ++i){
for(let j = 0; j < numCoeffs; ++j){
c[j * 3] = src[j][i];
c[j * 3 + 1] = src[j + numCoeffs][i];
c[j * 3 + 2] = src[j + numCoeffs * 2][i];
}
let max = c[0];
for(let j = 1; j < numCoeffs * 3; ++j){
max = Math.max(max, Math.abs(c[j]));
}
if (max === 0) {
continue;
}
for(let j = 0; j < numCoeffs; ++j){
c[j * 3 + 0] = Math.max(0, Math.min(t11, Math.floor((c[j * 3 + 0] / max * 0.5 + 0.5) * t11 + 0.5)));
c[j * 3 + 1] = Math.max(0, Math.min(t10, Math.floor((c[j * 3 + 1] / max * 0.5 + 0.5) * t10 + 0.5)));
c[j * 3 + 2] = Math.max(0, Math.min(t11, Math.floor((c[j * 3 + 2] / max * 0.5 + 0.5) * t11 + 0.5)));
}
float32[0] = max;
sh1to3Data[i * 4 + 0] = uint32[0];
sh1to3Data[i * 4 + 1] = c[0] << 21 | c[1] << 11 | c[2];
sh1to3Data[i * 4 + 2] = c[3] << 21 | c[4] << 11 | c[5];
sh1to3Data[i * 4 + 3] = c[6] << 21 | c[7] << 11 | c[8];
if (this.shBands > 1) {
sh4to7Data[i * 4 + 0] = c[9] << 21 | c[10] << 11 | c[11];
sh4to7Data[i * 4 + 1] = c[12] << 21 | c[13] << 11 | c[14];
sh4to7Data[i * 4 + 2] = c[15] << 21 | c[16] << 11 | c[17];
sh4to7Data[i * 4 + 3] = c[18] << 21 | c[19] << 11 | c[20];
if (this.shBands > 2) {
sh8to11Data[i * 4 + 0] = c[21] << 21 | c[22] << 11 | c[23];
sh8to11Data[i * 4 + 1] = c[24] << 21 | c[25] << 11 | c[26];
sh8to11Data[i * 4 + 2] = c[27] << 21 | c[28] << 11 | c[29];
sh8to11Data[i * 4 + 3] = c[30] << 21 | c[31] << 11 | c[32];
sh12to15Data[i * 4 + 0] = c[33] << 21 | c[34] << 11 | c[35];
sh12to15Data[i * 4 + 1] = c[36] << 21 | c[37] << 11 | c[38];
sh12to15Data[i * 4 + 2] = c[39] << 21 | c[40] << 11 | c[41];
sh12to15Data[i * 4 + 3] = c[42] << 21 | c[43] << 11 | c[44];
} else {
sh8to11Data[i] = c[21] << 21 | c[22] << 11 | c[23];
}
}
}
this.sh1to3Texture.unlock();
this.sh4to7Texture?.unlock();
this.sh8to11Texture?.unlock();
this.sh12to15Texture?.unlock();
}
}
export { GSplatResource };