diff --git a/docs/docs/spark-renderer.md b/docs/docs/spark-renderer.md index 84c6ca9..00eee66 100644 --- a/docs/docs/spark-renderer.md +++ b/docs/docs/spark-renderer.md @@ -57,6 +57,7 @@ const spark = new SparkRenderer({ | **preUpdate** | Controls whether to update the splats before or after rendering. For WebXR this *must* be false in order to complete rendering as soon as possible. (default: `false`) | **originDistance** | Distance threshold for `SparkRenderer` movement triggering a splat update at the new origin. (default: `1.0`) This can be useful when your `SparkRenderer` is a child of your camera and you want to retain high precision coordinates near the camera. | **maxStdDev** | Maximum standard deviations from the center to render Gaussians. Values `Math.sqrt(5)`..`Math.sqrt(9)` produce good results and can be tweaked for performance. (default: `Math.sqrt(8)`) +| **minPixelRadius** | Minimum pixel radius for splat rendering. (default: `0.0`) | **maxPixelRadius** | Maximum pixel radius for splat rendering. (default: `512.0`) | **minAlpha** | Minimum alpha value for splat rendering. (default: `0.5 * (1.0 / 255.0)`) | **enable2DGS** | Enable 2D Gaussian splatting rendering ability. When this mode is enabled, any `scale` x/y/z component that is exactly `0` (minimum quantized value) results in the other two non-zero axes being interpreted as an oriented 2D Gaussian Splat instead of the usual approximate projected 3DGS Z-slice. When reading PLY files, scale values less than e^-30 will be interpreted as `0`. (default: `false`) diff --git a/examples/editor/index.html b/examples/editor/index.html index 0505ce4..919e176 100644 --- a/examples/editor/index.html +++ b/examples/editor/index.html @@ -540,6 +540,7 @@ debugFolder.add(spark, "focalAdjustment", 0.1, 2.0, 0.1).name("Tweak focalAdjustment"); spark.defaultView.sort32 = true; debugFolder.add(spark.defaultView, "sort32").name("Float32 sort").listen(); + debugFolder.add(spark, "minPixelRadius", 0, 16, 0.1).name("Min pixel radius").listen(); debugFolder.add(spark, "maxPixelRadius", 1, 1024, 1).name("Max pixel radius").listen(); debugFolder.add(spark, "minAlpha", 0, 1, 0.001).name("Min alpha").listen(); diff --git a/src/SparkRenderer.ts b/src/SparkRenderer.ts index 226aef8..4e090d0 100644 --- a/src/SparkRenderer.ts +++ b/src/SparkRenderer.ts @@ -84,6 +84,11 @@ export type SparkRendererOptions = { * @default Math.sqrt(8) */ maxStdDev?: number; + /** + * Minimum pixel radius for splat rendering. + * @default 0.0 + */ + minPixelRadius?: number; /** * Maximum pixel radius for splat rendering. * @default 512.0 @@ -170,6 +175,7 @@ export class SparkRenderer extends THREE.Mesh { needsUpdate: boolean; originDistance: number; maxStdDev: number; + minPixelRadius: number; maxPixelRadius: number; minAlpha: number; enable2DGS: boolean; @@ -294,6 +300,7 @@ export class SparkRenderer extends THREE.Mesh { this.needsUpdate = false; this.originDistance = options.originDistance ?? 1; this.maxStdDev = options.maxStdDev ?? Math.sqrt(8.0); + this.minPixelRadius = options.minPixelRadius ?? 0.0; this.maxPixelRadius = options.maxPixelRadius ?? 512.0; this.minAlpha = options.minAlpha ?? 0.5 * (1.0 / 255.0); this.enable2DGS = options.enable2DGS ?? false; @@ -344,6 +351,8 @@ export class SparkRenderer extends THREE.Mesh { renderToViewPos: { value: new THREE.Vector3() }, // Maximum distance (in stddevs) from Gsplat center to render maxStdDev: { value: 1.0 }, + // Minimum pixel radius for splat rendering + minPixelRadius: { value: 0.0 }, // Maximum pixel radius for splat rendering maxPixelRadius: { value: 512.0 }, // Minimum alpha value for splat rendering @@ -528,6 +537,7 @@ export class SparkRenderer extends THREE.Mesh { this.uniforms.far.value = typedCamera.far; this.uniforms.encodeLinear.value = viewpoint.encodeLinear; this.uniforms.maxStdDev.value = this.maxStdDev; + this.uniforms.minPixelRadius.value = this.minPixelRadius; this.uniforms.maxPixelRadius.value = this.maxPixelRadius; this.uniforms.minAlpha.value = this.minAlpha; this.uniforms.stochastic.value = viewpoint.stochastic; diff --git a/src/shaders/splatVertex.glsl b/src/shaders/splatVertex.glsl index c246064..ad90d69 100644 --- a/src/shaders/splatVertex.glsl +++ b/src/shaders/splatVertex.glsl @@ -17,6 +17,7 @@ uniform uint numSplats; uniform vec4 renderToViewQuat; uniform vec3 renderToViewPos; uniform float maxStdDev; +uniform float minPixelRadius; uniform float maxPixelRadius; uniform float time; uniform float deltaTime; @@ -199,11 +200,14 @@ void main() { vec2 eigenVec1 = normalize(vec2((abs(b) < 0.001) ? 1.0 : b, eigen1 - a)); vec2 eigenVec2 = vec2(eigenVec1.y, -eigenVec1.x); - float scale1 = position.x * min(maxPixelRadius, maxStdDev * sqrt(eigen1)); - float scale2 = position.y * min(maxPixelRadius, maxStdDev * sqrt(eigen2)); + float scale1 = min(maxPixelRadius, maxStdDev * sqrt(eigen1)); + float scale2 = min(maxPixelRadius, maxStdDev * sqrt(eigen2)); + if (scale1 < minPixelRadius && scale2 < minPixelRadius) { + return; + } // Compute the NDC coordinates for the ellipsoid's diagonal axes. - vec2 pixelOffset = eigenVec1 * scale1 + eigenVec2 * scale2; + vec2 pixelOffset = position.x * eigenVec1 * scale1 + position.y * eigenVec2 * scale2; vec2 ndcOffset = (2.0 / scaledRenderSize) * pixelOffset; vec3 ndc = vec3(ndcCenter.xy + ndcOffset, ndcCenter.z);