Skip to content

Commit fcf05aa

Browse files
committed
Histogram sort for uncompressed gsplat (#7596)
1 parent 86a49ca commit fcf05aa

File tree

1 file changed

+88
-77
lines changed

1 file changed

+88
-77
lines changed

src/scene/gsplat/gsplat-sorter.js

Lines changed: 88 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,16 @@ function SortWorker() {
114114
distances[i] = 0;
115115
countBuffer[0]++;
116116
}
117-
} else if (chunks) {
118-
// handle sort with compressed chunks
119-
const numChunks = chunks.length / 6;
117+
} else {
118+
// use chunks to calculate rough histogram of splats per distance
119+
const numChunks = chunks.length / 4;
120120

121-
// calculate a histogram of chunk distances to camera
122121
binCount.fill(0);
123122
for (let i = 0; i < numChunks; ++i) {
124-
const x = chunks[i * 6 + 0];
125-
const y = chunks[i * 6 + 1];
126-
const z = chunks[i * 6 + 2];
127-
const r = chunks[i * 6 + 3];
123+
const x = chunks[i * 4 + 0];
124+
const y = chunks[i * 4 + 1];
125+
const z = chunks[i * 4 + 2];
126+
const r = chunks[i * 4 + 3];
128127
const d = x * dx + y * dy + z * dz - minDist;
129128

130129
const binMin = Math.max(0, Math.floor((d - r) * numBins / range));
@@ -159,23 +158,6 @@ function SortWorker() {
159158

160159
distances[i] = sortKey;
161160

162-
// count occurrences of each distance
163-
countBuffer[sortKey]++;
164-
}
165-
} else {
166-
// generate per vertex distance to camera for uncompressed data
167-
const divider = (2 ** compareBits) / range;
168-
let ii = 0;
169-
for (let i = 0; i < numVertices; ++i) {
170-
const x = centers[ii++];
171-
const y = centers[ii++];
172-
const z = centers[ii++];
173-
174-
const d = (x * dx + y * dy + z * dz - minDist) * divider;
175-
const sortKey = d >>> 0;
176-
177-
distances[i] = sortKey;
178-
179161
// count occurrences of each distance
180162
countBuffer[sortKey]++;
181163
}
@@ -230,61 +212,90 @@ function SortWorker() {
230212
centers = new Float32Array(message.data.centers);
231213
forceUpdate = true;
232214

233-
// calculate bounds
234-
let initialized = false;
235-
const numVertices = centers.length / 3;
236-
for (let i = 0; i < numVertices; ++i) {
237-
let x = centers[i * 3 + 0];
238-
let y = centers[i * 3 + 1];
239-
let z = centers[i * 3 + 2];
240-
241-
if (isNaN(x)) {
242-
x = centers[i * 3 + 0] = 0;
243-
}
244-
if (isNaN(y)) {
245-
y = centers[i * 3 + 1] = 0;
215+
if (message.data.chunks) {
216+
const chunksSrc = new Float32Array(message.data.chunks);
217+
// reuse chunks memory, but we only need 4 floats per chunk
218+
chunks = new Float32Array(message.data.chunks, 0, chunksSrc.length * 4 / 6);
219+
220+
boundMin.x = chunksSrc[0];
221+
boundMin.y = chunksSrc[1];
222+
boundMin.z = chunksSrc[2];
223+
boundMax.x = chunksSrc[3];
224+
boundMax.y = chunksSrc[4];
225+
boundMax.z = chunksSrc[5];
226+
227+
// convert chunk min/max to center/radius
228+
for (let i = 0; i < chunksSrc.length / 6; ++i) {
229+
const mx = chunksSrc[i * 6 + 0];
230+
const my = chunksSrc[i * 6 + 1];
231+
const mz = chunksSrc[i * 6 + 2];
232+
const Mx = chunksSrc[i * 6 + 3];
233+
const My = chunksSrc[i * 6 + 4];
234+
const Mz = chunksSrc[i * 6 + 5];
235+
236+
chunks[i * 4 + 0] = (mx + Mx) * 0.5;
237+
chunks[i * 4 + 1] = (my + My) * 0.5;
238+
chunks[i * 4 + 2] = (mz + Mz) * 0.5;
239+
chunks[i * 4 + 3] = Math.sqrt((Mx - mx) ** 2 + (My - my) ** 2 + (Mz - mz) ** 2) * 0.5;
240+
241+
if (mx < boundMin.x) boundMin.x = mx;
242+
if (my < boundMin.y) boundMin.y = my;
243+
if (mz < boundMin.z) boundMin.z = mz;
244+
if (Mx > boundMax.x) boundMax.x = Mx;
245+
if (My > boundMax.y) boundMax.y = My;
246+
if (Mz > boundMax.z) boundMax.z = Mz;
246247
}
247-
if (isNaN(z)) {
248-
z = centers[i * 3 + 2] = 0;
249-
}
250-
251-
if (!initialized) {
252-
initialized = true;
253-
boundMin.x = boundMax.x = x;
254-
boundMin.y = boundMax.y = y;
255-
boundMin.z = boundMax.z = z;
256-
} else {
257-
boundMin.x = Math.min(boundMin.x, x);
258-
boundMax.x = Math.max(boundMax.x, x);
259-
boundMin.y = Math.min(boundMin.y, y);
260-
boundMax.y = Math.max(boundMax.y, y);
261-
boundMin.z = Math.min(boundMin.z, z);
262-
boundMax.z = Math.max(boundMax.z, z);
248+
} else {
249+
// chunk bounds weren't provided, so calculate them from the centers
250+
const numVertices = centers.length / 3;
251+
const numChunks = Math.ceil(numVertices / 256);
252+
253+
// allocate storage for one bounding sphere per 256-vertex chunk
254+
chunks = new Float32Array(numChunks * 4);
255+
256+
boundMin.x = boundMin.y = boundMin.z = Infinity;
257+
boundMax.x = boundMax.y = boundMax.z = -Infinity;
258+
259+
// calculate bounds
260+
let mx, my, mz, Mx, My, Mz;
261+
for (let c = 0; c < numChunks; ++c) {
262+
mx = my = mz = Infinity;
263+
Mx = My = Mz = -Infinity;
264+
265+
const start = c * 256;
266+
const end = Math.min(numVertices, (c + 1) * 256);
267+
for (let i = start; i < end; ++i) {
268+
const x = centers[i * 3 + 0];
269+
const y = centers[i * 3 + 1];
270+
const z = centers[i * 3 + 2];
271+
272+
const validX = Number.isFinite(x);
273+
const validY = Number.isFinite(y);
274+
const validZ = Number.isFinite(z);
275+
276+
if (!validX) centers[i * 3 + 0] = 0;
277+
if (!validY) centers[i * 3 + 1] = 0;
278+
if (!validZ) centers[i * 3 + 2] = 0;
279+
if (!validX || !validY || !validZ) {
280+
continue;
281+
}
282+
283+
if (x < mx) mx = x; else if (x > Mx) Mx = x;
284+
if (y < my) my = y; else if (y > My) My = y;
285+
if (z < mz) mz = z; else if (z > Mz) Mz = z;
286+
287+
if (x < boundMin.x) boundMin.x = x; else if (x > boundMax.x) boundMax.x = x;
288+
if (y < boundMin.y) boundMin.y = y; else if (y > boundMax.y) boundMax.y = y;
289+
if (z < boundMin.z) boundMin.z = z; else if (z > boundMax.z) boundMax.z = z;
290+
}
291+
292+
// calculate chunk center and radius from bound min/max
293+
chunks[c * 4 + 0] = (mx + Mx) * 0.5;
294+
chunks[c * 4 + 1] = (my + My) * 0.5;
295+
chunks[c * 4 + 2] = (mz + Mz) * 0.5;
296+
chunks[c * 4 + 3] = Math.sqrt((Mx - mx) ** 2 + (My - my) ** 2 + (Mz - mz) ** 2) * 0.5;
263297
}
264298
}
265-
266-
if (!initialized) {
267-
boundMin.x = boundMax.x = boundMin.y = boundMax.y = boundMin.z = boundMax.z = 0;
268-
}
269-
}
270-
if (message.data.chunks) {
271-
chunks = new Float32Array(message.data.chunks);
272-
forceUpdate = true;
273-
274-
// convert chunk min/max to center/radius
275-
for (let i = 0; i < chunks.length / 6; ++i) {
276-
const mx = chunks[i * 6 + 0];
277-
const my = chunks[i * 6 + 1];
278-
const mz = chunks[i * 6 + 2];
279-
const Mx = chunks[i * 6 + 3];
280-
const My = chunks[i * 6 + 4];
281-
const Mz = chunks[i * 6 + 5];
282-
283-
chunks[i * 6 + 0] = (mx + Mx) * 0.5;
284-
chunks[i * 6 + 1] = (my + My) * 0.5;
285-
chunks[i * 6 + 2] = (mz + Mz) * 0.5;
286-
chunks[i * 6 + 3] = Math.sqrt((Mx - mx) ** 2 + (My - my) ** 2 + (Mz - mz) ** 2) * 0.5;
287-
}
288299
}
289300
if (message.data.hasOwnProperty('mapping')) {
290301
mapping = message.data.mapping ? new Uint32Array(message.data.mapping) : null;

0 commit comments

Comments
 (0)