UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

146 lines (144 loc) 5.65 kB
/** * A utility class for computing camera-relative bin weights used in GSplat sorting. * Pre-allocates a single interleaved Float32Array that is reused across frames. * Used by both GPU (GSplatSortKeyCompute) and CPU worker sorting paths. * * This class is stringified and injected into the worker blob, so it must be * fully self-contained: no imports, and all constants as static properties. * * @ignore */ class GSplatSortBinWeights { /** * Number of bins for camera-relative precision weighting. * * @type {number} */ static get NUM_BINS() { return 32; } /** * Weight tiers for camera-relative precision (distance from camera bin -> weight multiplier). * Closer bins get more precision for better visual quality near the camera. * * @type {Array<{maxDistance: number, weight: number}>} */ static get WEIGHT_TIERS() { return [ { maxDistance: 0, weight: 40.0 }, { maxDistance: 2, weight: 20.0 }, { maxDistance: 5, weight: 8.0 }, { maxDistance: 10, weight: 3.0 }, { maxDistance: Infinity, weight: 1.0 } // Far bins ]; } /** * Computes the camera bin index based on sort mode and distance range. * * @param {boolean} radialSort - Whether using radial sort mode. * @param {number} minDist - Minimum distance. * @param {number} range - Distance range (maxDist - minDist). * @returns {number} The camera bin index (0 to NUM_BINS-1). */ static computeCameraBin(radialSort, minDist, range) { const numBins = GSplatSortBinWeights.NUM_BINS; if (radialSort) { // For radial sort with inverted distances, camera (dist=0) maps to the last bin return numBins - 1; } // For linear sort, calculate where camera falls in the projected distance range const cameraOffsetFromRangeStart = -minDist; const cameraBinFloat = cameraOffsetFromRangeStart / range * numBins; return Math.max(0, Math.min(numBins - 1, Math.floor(cameraBinFloat))); } /** * Computes bin weights for the given camera bin and bucket count. * Results are cached - returns immediately if inputs haven't changed. * * @param {number} cameraBin - The bin index where the camera is located (0 to NUM_BINS-1). * @param {number} bucketCount - Total number of sorting buckets (typically 2^numBits). * @returns {Float32Array} The same binWeights array with computed values. */ compute(cameraBin, bucketCount) { // Return cached result if inputs haven't changed if (cameraBin === this.lastCameraBin && bucketCount === this.lastBucketCount) { return this.binWeights; } // Update cache this.lastCameraBin = cameraBin; this.lastBucketCount = bucketCount; const numBins = GSplatSortBinWeights.NUM_BINS; const bitsPerBin = this.bitsPerBin; // Assign weights to bins based on pre-calculated distance lookup for(let i = 0; i < numBins; i++){ const distFromCamera = Math.abs(i - cameraBin); bitsPerBin[i] = this.weightByDistance[distFromCamera]; } // Normalize to fit within budget let totalWeight = 0; for(let i = 0; i < numBins; i++){ totalWeight += bitsPerBin[i]; } // Write to binWeights array (interleaved base, divider pairs) let accumulated = 0; for(let i = 0; i < numBins; i++){ const divider = Math.max(1, Math.floor(bitsPerBin[i] / totalWeight * bucketCount)); this.binWeights[i * 2] = accumulated; // base this.binWeights[i * 2 + 1] = divider; // divider accumulated += divider; } // Adjust last bin to fit exactly if (accumulated > bucketCount) { const excess = accumulated - bucketCount; const lastDividerIdx = (numBins - 1) * 2 + 1; this.binWeights[lastDividerIdx] = Math.max(1, this.binWeights[lastDividerIdx] - excess); } return this.binWeights; } /** * Creates a new GSplatSortBinWeights instance. */ constructor(){ /** * Pre-allocated interleaved array [base0, divider0, base1, divider1, ...]. * * @type {Float32Array} */ this.binWeights = new Float32Array(GSplatSortBinWeights.NUM_BINS * 2); /** * Cached cameraBin from last compute call. * * @type {number} */ this.lastCameraBin = -1; /** * Cached bucketCount from last compute call. * * @type {number} */ this.lastBucketCount = -1; const numBins = GSplatSortBinWeights.NUM_BINS; const weightTiers = GSplatSortBinWeights.WEIGHT_TIERS; // Pre-allocate scratch array this.bitsPerBin = new Float32Array(numBins); // Pre-compute weight lookup table by distance from camera (constant) this.weightByDistance = new Float32Array(numBins); for(let dist = 0; dist < numBins; dist++){ let weight = 1.0; for(let j = 0; j < weightTiers.length; j++){ if (dist <= weightTiers[j].maxDistance) { weight = weightTiers[j].weight; break; } } this.weightByDistance[dist] = weight; } } } export { GSplatSortBinWeights };