UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

281 lines (278 loc) 13.5 kB
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 { Mat3 } from '../../core/math/mat3.js'; import { FILTER_NEAREST, ADDRESS_CLAMP_TO_EDGE, PIXELFORMAT_RGBA8, PIXELFORMAT_RGBA32U, PIXELFORMAT_RGBA16F, PIXELFORMAT_R32U } from '../../platform/graphics/constants.js'; import { Texture } from '../../platform/graphics/texture.js'; import { BoundingBox } from '../../core/shape/bounding-box.js'; import { createGSplatMaterial } from './gsplat-material.js'; /** * @import { GSplatData } from './gsplat-data.js' * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' * @import { Material } from '../materials/material.js' */ var getSHData = (gsplatData, numCoeffs)=>{ var result = []; for(var i = 0; i < numCoeffs; ++i){ result.push(gsplatData.getProp("f_rest_" + i)); } return result; }; /** @ignore */ class GSplat { destroy() { var _this_colorTexture, _this_transformATexture, _this_transformBTexture, _this_sh1to3Texture, _this_sh4to7Texture, _this_sh8to11Texture, _this_sh12to15Texture; (_this_colorTexture = this.colorTexture) == null ? undefined : _this_colorTexture.destroy(); (_this_transformATexture = this.transformATexture) == null ? undefined : _this_transformATexture.destroy(); (_this_transformBTexture = this.transformBTexture) == null ? undefined : _this_transformBTexture.destroy(); (_this_sh1to3Texture = this.sh1to3Texture) == null ? undefined : _this_sh1to3Texture.destroy(); (_this_sh4to7Texture = this.sh4to7Texture) == null ? undefined : _this_sh4to7Texture.destroy(); (_this_sh8to11Texture = this.sh8to11Texture) == null ? undefined : _this_sh8to11Texture.destroy(); (_this_sh12to15Texture = this.sh12to15Texture) == null ? undefined : _this_sh12to15Texture.destroy(); } /** * @returns {Material} material - The material to set up for the splat rendering. */ createMaterial(options) { var result = createGSplatMaterial(options); result.setParameter('splatColor', this.colorTexture); result.setParameter('transformA', this.transformATexture); result.setParameter('transformB', this.transformBTexture); result.setParameter('numSplats', this.numSplatsVisible); result.setDefine('SH_BANDS', this.shBands); if (this.sh1to3Texture) result.setParameter('splatSH_1to3', this.sh1to3Texture); if (this.sh4to7Texture) result.setParameter('splatSH_4to7', this.sh4to7Texture); if (this.sh8to11Texture) result.setParameter('splatSH_8to11', this.sh8to11Texture); if (this.sh12to15Texture) result.setParameter('splatSH_12to15', this.sh12to15Texture); return result; } /** * Evaluates the texture size needed to store a given number of elements. * The function calculates a width and height that is close to a square * that can contain 'count' elements. * * @param {number} count - The number of elements to store in the texture. * @returns {Vec2} An instance of Vec2 representing the width and height of the texture. */ evalTextureSize(count) { var width = Math.ceil(Math.sqrt(count)); var height = Math.ceil(count / width); return new Vec2(width, height); } /** * Creates a new texture with the specified parameters. * * @param {string} name - The name of the texture to be created. * @param {number} format - The pixel format of the texture. * @param {Vec2} size - The size of the texture in a Vec2 object, containing width (x) and height (y). * @returns {Texture} The created texture instance. */ createTexture(name, format, size) { return new Texture(this.device, { name: name, width: size.x, height: size.y, format: format, cubemap: false, mipmaps: false, minFilter: FILTER_NEAREST, magFilter: FILTER_NEAREST, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE }); } /** * Updates pixel data of this.colorTexture based on the supplied color components and opacity. * Assumes that the texture is using an RGBA format where RGB are color components influenced * by SH spherical harmonics and A is opacity after a sigmoid transformation. * * @param {GSplatData} gsplatData - The source data */ updateColorData(gsplatData) { var texture = this.colorTexture; if (!texture) { return; } var data = texture.lock(); var cr = gsplatData.getProp('f_dc_0'); var cg = gsplatData.getProp('f_dc_1'); var cb = gsplatData.getProp('f_dc_2'); var ca = gsplatData.getProp('opacity'); var SH_C0 = 0.28209479177387814; for(var i = 0; i < this.numSplats; ++i){ var r = (cr[i] * SH_C0 + 0.5) * 255; var g = (cg[i] * SH_C0 + 0.5) * 255; var b = (cb[i] * SH_C0 + 0.5) * 255; var a = 255 / (1 + Math.exp(-ca[i])); data[i * 4 + 0] = r < 0 ? 0 : r > 255 ? 255 : r; data[i * 4 + 1] = g < 0 ? 0 : g > 255 ? 255 : g; data[i * 4 + 2] = b < 0 ? 0 : b > 255 ? 255 : b; data[i * 4 + 3] = a < 0 ? 0 : a > 255 ? 255 : a; } texture.unlock(); } /** * @param {GSplatData} gsplatData - The source data */ updateTransformData(gsplatData) { var float2Half = FloatPacking.float2Half; if (!this.transformATexture) { return; } var dataA = this.transformATexture.lock(); var dataAFloat32 = new Float32Array(dataA.buffer); var dataB = this.transformBTexture.lock(); var p = new Vec3(); var r = new Quat(); var s = new Vec3(); var iter = gsplatData.createIter(p, r, s); var mat = new Mat3(); var cA = new Vec3(); var cB = new Vec3(); for(var i = 0; i < this.numSplats; i++){ iter.read(i); r.normalize(); mat.setFromQuat(r); this.computeCov3d(mat, s, cA, cB); dataAFloat32[i * 4 + 0] = p.x; dataAFloat32[i * 4 + 1] = p.y; dataAFloat32[i * 4 + 2] = p.z; dataA[i * 4 + 3] = float2Half(cB.x) | float2Half(cB.y) << 16; dataB[i * 4 + 0] = float2Half(cA.x); dataB[i * 4 + 1] = float2Half(cA.y); dataB[i * 4 + 2] = float2Half(cA.z); dataB[i * 4 + 3] = float2Half(cB.z); } this.transformATexture.unlock(); this.transformBTexture.unlock(); } /** * Evaluate the covariance values based on the rotation and scale. * * @param {Mat3} rot - The rotation matrix. * @param {Vec3} scale - The scale. * @param {Vec3} covA - The first covariance vector. * @param {Vec3} covB - The second covariance vector. */ computeCov3d(rot, scale, covA, covB) { var sx = scale.x; var sy = scale.y; var sz = scale.z; var data = rot.data; var r00 = data[0] * sx; var r01 = data[1] * sx; var r02 = data[2] * sx; var r10 = data[3] * sy; var r11 = data[4] * sy; var r12 = data[5] * sy; var r20 = data[6] * sz; var r21 = data[7] * sz; var r22 = data[8] * sz; covA.x = r00 * r00 + r10 * r10 + r20 * r20; covA.y = r00 * r01 + r10 * r11 + r20 * r21; covA.z = r00 * r02 + r10 * r12 + r20 * r22; covB.x = r01 * r01 + r11 * r11 + r21 * r21; covB.y = r01 * r02 + r11 * r12 + r21 * r22; covB.z = r02 * r02 + r12 * r12 + r22 * r22; } /** * @param {GSplatData} gsplatData - The source data */ updateSHData(gsplatData) { var _this_sh4to7Texture, _this_sh8to11Texture, _this_sh12to15Texture, _this_sh4to7Texture1, _this_sh8to11Texture1, _this_sh12to15Texture1; var sh1to3Data = this.sh1to3Texture.lock(); var sh4to7Data = (_this_sh4to7Texture = this.sh4to7Texture) == null ? undefined : _this_sh4to7Texture.lock(); var sh8to11Data = (_this_sh8to11Texture = this.sh8to11Texture) == null ? undefined : _this_sh8to11Texture.lock(); var sh12to15Data = (_this_sh12to15Texture = this.sh12to15Texture) == null ? undefined : _this_sh12to15Texture.lock(); var numCoeffs = { 1: 3, 2: 8, 3: 15 }[this.shBands]; var src = getSHData(gsplatData, numCoeffs * 3); var t11 = (1 << 11) - 1; var t10 = (1 << 10) - 1; var float32 = new Float32Array(1); var uint32 = new Uint32Array(float32.buffer); // coefficients var c = new Array(numCoeffs * 3).fill(0); for(var i = 0; i < gsplatData.numSplats; ++i){ // extract coefficients for(var 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]; } // calc maximum value var max = c[0]; for(var j1 = 1; j1 < numCoeffs * 3; ++j1){ max = Math.max(max, Math.abs(c[j1])); } if (max === 0) { continue; } // normalize for(var j2 = 0; j2 < numCoeffs; ++j2){ c[j2 * 3 + 0] = Math.max(0, Math.min(t11, Math.floor((c[j2 * 3 + 0] / max * 0.5 + 0.5) * t11 + 0.5))); c[j2 * 3 + 1] = Math.max(0, Math.min(t10, Math.floor((c[j2 * 3 + 1] / max * 0.5 + 0.5) * t10 + 0.5))); c[j2 * 3 + 2] = Math.max(0, Math.min(t11, Math.floor((c[j2 * 3 + 2] / max * 0.5 + 0.5) * t11 + 0.5))); } // pack 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_sh4to7Texture1 = this.sh4to7Texture) == null ? undefined : _this_sh4to7Texture1.unlock(); (_this_sh8to11Texture1 = this.sh8to11Texture) == null ? undefined : _this_sh8to11Texture1.unlock(); (_this_sh12to15Texture1 = this.sh12to15Texture) == null ? undefined : _this_sh12to15Texture1.unlock(); } /** * @param {GraphicsDevice} device - The graphics device. * @param {GSplatData} gsplatData - The splat data. */ constructor(device, gsplatData){ var numSplats = gsplatData.numSplats; this.device = device; this.numSplats = numSplats; this.numSplatsVisible = numSplats; this.centers = new Float32Array(gsplatData.numSplats * 3); gsplatData.getCenters(this.centers); this.aabb = new BoundingBox(); gsplatData.calcAabb(this.aabb); var size = this.evalTextureSize(numSplats); this.colorTexture = this.createTexture('splatColor', PIXELFORMAT_RGBA8, size); this.transformATexture = this.createTexture('transformA', PIXELFORMAT_RGBA32U, size); this.transformBTexture = this.createTexture('transformB', PIXELFORMAT_RGBA16F, size); // write texture data this.updateColorData(gsplatData); this.updateTransformData(gsplatData); // initialize SH data 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); } } } export { GSplat };