Skip to content
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

Update GS rendering v2.0 #6357

Merged
merged 6 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions src/platform/graphics/shader-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,9 @@ class ShaderUtils {

const precision = forcePrecision ? forcePrecision : device.precision;

let code = '';
if (device.isWebGPU) {

code = `precision ${precision} float;\nprecision ${precision} int;\n`;

} else {
let code = `precision ${precision} float;\nprecision ${precision} int;\n`;

code = `precision ${precision} float;\n`;
if (device.isWebGL2) {
code += `precision ${precision} sampler2DShadow;\n`;
}

Expand Down
2 changes: 1 addition & 1 deletion src/platform/graphics/texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ class Texture {
* - {@link TEXTURELOCK_READ}
* - {@link TEXTURELOCK_WRITE}
* Defaults to {@link TEXTURELOCK_WRITE}.
* @returns {Uint8Array|Uint16Array|Float32Array} A typed array containing the pixel data of
* @returns {Uint8Array|Uint16Array|Uint32Array|Float32Array} A typed array containing the pixel data of
* the locked mip level.
*/
lock(options = {}) {
Expand Down
81 changes: 81 additions & 0 deletions src/scene/gsplat/gsplat-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ class GSplatData {
if (!this.isCompressed && performZScale) {
mat4.setScale(-1, -1, 1);
this.transform(mat4);

// reorder uncompressed splats in morton order for better memory access
// efficiency during rendering
this.reorderData();
slimbuck marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -408,6 +412,83 @@ class GSplatData {
})
}], false);
}

calcMortonOrder() {
const calcMinMax = (arr) => {
let min = arr[0];
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
return { min, max };
};

// https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
const encodeMorton3 = (x, y, z) => {
const Part1By2 = (x) => {
x &= 0x000003ff;
x = (x ^ (x << 16)) & 0xff0000ff;
x = (x ^ (x << 8)) & 0x0300f00f;
x = (x ^ (x << 4)) & 0x030c30c3;
x = (x ^ (x << 2)) & 0x09249249;
return x;
};

return (Part1By2(z) << 2) + (Part1By2(y) << 1) + Part1By2(x);
};

const x = this.getProp('x');
const y = this.getProp('y');
const z = this.getProp('z');

const { min: minX, max: maxX } = calcMinMax(x);
const { min: minY, max: maxY } = calcMinMax(y);
const { min: minZ, max: maxZ } = calcMinMax(z);

const sizeX = 1024 / (maxX - minX);
const sizeY = 1024 / (maxY - minY);
const sizeZ = 1024 / (maxZ - minZ);

const morton = new Uint32Array(this.numSplats);
for (let i = 0; i < this.numSplats; i++) {
const ix = Math.floor((x[i] - minX) * sizeX);
const iy = Math.floor((y[i] - minY) * sizeY);
const iz = Math.floor((z[i] - minZ) * sizeZ);
morton[i] = encodeMorton3(ix, iy, iz);
}

// generate indices
const indices = new Uint32Array(this.numSplats);
for (let i = 0; i < this.numSplats; i++) {
indices[i] = i;
}
// order splats by morton code
indices.sort((a, b) => morton[a] - morton[b]);
slimbuck marked this conversation as resolved.
Show resolved Hide resolved

return indices;
}

reorderData() {
// calculate splat morton order
const order = this.calcMortonOrder();

const reorder = (data) => {
const result = new data.constructor(data.length);

for (let i = 0; i < order.length; i++) {
result[i] = data[order[i]];
}

return result;
};

this.elements.forEach((element) => {
element.properties.forEach((property) => {
property.storage = reorder(property.storage);
});
});
}
}

export { GSplatData };
91 changes: 61 additions & 30 deletions src/scene/gsplat/gsplat-instance.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Mat4 } from '../../core/math/mat4.js';
import { Vec3 } from '../../core/math/vec3.js';
import { SEMANTIC_POSITION, TYPE_UINT32 } from '../../platform/graphics/constants.js';
import { BUFFER_STATIC, PIXELFORMAT_R32U, SEMANTIC_ATTR13, TYPE_UINT32 } from '../../platform/graphics/constants.js';
import { DITHER_NONE } from '../constants.js';
import { MeshInstance } from '../mesh-instance.js';
import { Mesh } from '../mesh.js';
import { createGSplatMaterial } from './gsplat-material.js';
import { GSplatSorter } from './gsplat-sorter.js';
import { VertexFormat } from '../../platform/graphics/vertex-format.js';
import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js';

const mat = new Mat4();
const cameraPosition = new Vec3();
Expand All @@ -26,8 +28,8 @@ class GSplatInstance {
/** @type {import('../materials/material.js').Material} */
material;

/** @type {import('../../platform/graphics/vertex-buffer.js').VertexBuffer} */
vb;
/** @type {import('../../platform/graphics/texture.js').Texture} */
orderTexture;

options = {};

Expand Down Expand Up @@ -58,53 +60,81 @@ class GSplatInstance {

// not supported on WebGL1
const device = splat.device;
if (device.isWebGL1)
return;

// create the order texture
this.orderTexture = this.splat.createTexture(
'splatOrder',
PIXELFORMAT_R32U,
this.splat.evalTextureSize(this.splat.numSplats)
);

// material
this.createMaterial(options);

const numSplats = splat.numSplats;
const indices = new Uint32Array(numSplats * 6);
const ids = new Uint32Array(numSplats * 4);

for (let i = 0; i < numSplats; ++i) {
const base = i * 4;

// 4 vertices
ids[base + 0] = i;
ids[base + 1] = i;
ids[base + 2] = i;
ids[base + 3] = i;

// 2 triangles
const triBase = i * 6;
indices[triBase + 0] = base;
indices[triBase + 1] = base + 1;
indices[triBase + 2] = base + 2;
indices[triBase + 3] = base;
indices[triBase + 4] = base + 2;
indices[triBase + 5] = base + 3;
// number of quads to combine into a single instance. this is to increase occupancy
// in the vertex shader.
const splatInstanceSize = 128;
const numSplats = Math.ceil(splat.numSplats / splatInstanceSize) * splatInstanceSize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use math.roundUp

const numSplatInstances = numSplats / splatInstanceSize;

// specify the base splat index per instance
const indexData = new Uint32Array(numSplatInstances);
for (let i = 0; i < numSplatInstances; ++i) {
indexData[i] = i * splatInstanceSize;
}

const vertexFormat = new VertexFormat(device, [
{ semantic: SEMANTIC_ATTR13, components: 1, type: TYPE_UINT32, asInt: true }
]);

const indicesVB = new VertexBuffer(device, vertexFormat, numSplatInstances, {
usage: BUFFER_STATIC,
data: indexData.buffer
});

// build the instance mesh
const meshPositions = new Float32Array(12 * splatInstanceSize);
const meshIndices = new Uint32Array(6 * splatInstanceSize);
for (let i = 0; i < splatInstanceSize; ++i) {
meshPositions.set([
-2, -2, i,
2, -2, i,
2, 2, i,
-2, 2, i
], i * 12);

const b = i * 4;
meshIndices.set([
0 + b, 1 + b, 2 + b, 0 + b, 2 + b, 3 + b
], i * 6);
}

// mesh
const mesh = new Mesh(device);
mesh.setVertexStream(SEMANTIC_POSITION, ids, 1, numSplats * 4, TYPE_UINT32, false, !device.isWebGL1);
mesh.setIndices(indices);
mesh.setPositions(meshPositions, 3);
mesh.setIndices(meshIndices);
mesh.update();

this.mesh = mesh;
this.mesh.aabb.copy(splat.aabb);

this.meshInstance = new MeshInstance(this.mesh, this.material);
this.meshInstance.setInstancing(indicesVB, true);
this.meshInstance.gsplatInstance = this;
this.meshInstance.instancingCount = numSplatInstances;

// clone centers to allow multiple instances of sorter
this.centers = new Float32Array(splat.centers);

// create sorter
if (!options.dither || options.dither === DITHER_NONE) {
this.sorter = new GSplatSorter();
this.sorter.init(mesh.vertexBuffer, this.centers, !this.splat.device.isWebGL1);
this.sorter.init(this.orderTexture, this.centers);
this.sorter.on('updated', (count) => {
// limit splat render count to exclude those behind the camera.
// NOTE: the last instance rendered may include non-existant splat
// data. this should be ok though. as the data is filled with 0's.
slimbuck marked this conversation as resolved.
Show resolved Hide resolved
this.meshInstance.instancingCount = Math.ceil(count / splatInstanceSize);
});
}
}

Expand All @@ -120,6 +150,7 @@ class GSplatInstance {

createMaterial(options) {
this.material = createGSplatMaterial(options);
this.material.setParameter('splatOrder', this.orderTexture);
this.splat.setupMaterial(this.material);
if (this.meshInstance) {
this.meshInstance.material = this.material;
Expand Down
Loading