Skip to content

Commit 4e34b40

Browse files
mvaligurskyMartin Valigursky
andauthored
Expose GSplat rendering properties on GSplatParams (#8518)
* feat: expose GSplat rendering properties on GSplatParams Add alphaClip, minPixelSize, antiAlias, and twoDimensional properties to GSplatParams for unified mode. Replace hardcoded 2-pixel splat size cutoff with configurable minPixelSize uniform. Fixed #8437 Made-with: Cursor * fix: register alphaClip observer before setting initial value Ensures the initial data.set triggers the listener so app.scene.gsplat.alphaClip is applied on startup. Made-with: Cursor --------- Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
1 parent 8be9715 commit 4e34b40

File tree

10 files changed

+141
-12
lines changed

10 files changed

+141
-12
lines changed

examples/src/examples/gaussian-splatting/flipbook.example.mjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,7 @@ assetListLoader.load(() => {
128128
player.setLocalScale(80, 80, 80);
129129
app.root.addChild(player);
130130

131-
// set alpha clip value, used by shadows
132-
app.scene.gsplat.material.setParameter('alphaClip', 0.1);
131+
app.scene.gsplat.alphaClip = 0.1;
133132

134133
// Create shadow catcher
135134
const shadowCatcher = new pc.Entity('ShadowCatcher');

examples/src/examples/gaussian-splatting/lod-streaming.controls.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
3434
jsx(
3535
Panel,
3636
{ headerText: 'Settings' },
37+
jsx(
38+
LabelGroup,
39+
{ text: 'Min Pixel Size' },
40+
jsx(SliderInput, {
41+
binding: new BindingTwoWay(),
42+
link: { observer, path: 'minPixelSize' },
43+
min: 0,
44+
max: 5,
45+
precision: 1
46+
})
47+
),
3748
jsx(
3849
LabelGroup,
3950
{ text: 'Radial' },

examples/src/examples/gaussian-splatting/lod-streaming.example.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ assetListLoader.load(() => {
146146
app.scene.gsplat.culling = !!data.get('culling');
147147
});
148148
}
149+
data.on('minPixelSize:set', () => {
150+
app.scene.gsplat.minPixelSize = data.get('minPixelSize');
151+
});
149152
data.on('debugLod:set', () => {
150153
app.scene.gsplat.colorizeLod = !!data.get('debugLod');
151154
});
@@ -155,6 +158,7 @@ assetListLoader.load(() => {
155158

156159
// initialize UI settings (must be after observer registration)
157160
data.set('cameraFov', 75);
161+
data.set('minPixelSize', 2);
158162
data.set('radialSorting', true);
159163
data.set('gpuSorting', false);
160164
data.set('culling', device.isWebGPU);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @param {import('../../app/components/Example.mjs').ControlOptions} options - The options.
3+
* @returns {JSX.Element} The returned JSX Element.
4+
*/
5+
export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
6+
const { BindingTwoWay, LabelGroup, SliderInput } = ReactPCUI;
7+
return fragment(
8+
jsx(
9+
LabelGroup,
10+
{ text: 'Alpha Clip' },
11+
jsx(SliderInput, {
12+
binding: new BindingTwoWay(),
13+
link: { observer, path: 'alphaClip' },
14+
min: 0,
15+
max: 1,
16+
precision: 2
17+
})
18+
)
19+
);
20+
};

examples/src/examples/gaussian-splatting/shadows.example.mjs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @config HIDDEN
22
// @config DESCRIPTION Demonstrates shadow catching with Gaussian Splats.
3+
import { data } from 'examples/observer';
34
import { deviceType, rootPath, fileImport } from 'examples/utils';
45
import * as pc from 'playcanvas';
56

@@ -81,10 +82,10 @@ assetListLoader.load(() => {
8182
app.scene.sky.node.setLocalPosition(pc.Vec3.ZERO);
8283
app.scene.sky.center = new pc.Vec3(0, 0.05, 0);
8384

84-
// Customize GSplat material using unified mode material customization
85-
// This sets the alpha clip value for all GSplat instances in unified mode
86-
app.scene.gsplat.material.setParameter('alphaClip', 0.4);
87-
app.scene.gsplat.material.update();
85+
data.on('alphaClip:set', () => {
86+
app.scene.gsplat.alphaClip = data.get('alphaClip');
87+
});
88+
data.set('alphaClip', 0.4);
8889

8990
// Create first splat entity
9091
const biker = new pc.Entity('biker');

src/scene/gsplat-unified/gsplat-params.js

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ class GSplatParams {
6060
constructor(device) {
6161
this._device = device;
6262
this._format = this._createFormat(GSPLATDATA_COMPACT);
63+
64+
this._material.setParameter('alphaClip', 0.3);
65+
this._material.setParameter('minPixelSize', 2.0);
6366
}
6467

6568
/**
@@ -531,6 +534,95 @@ class GSplatParams {
531534
*/
532535
colorUpdateAngleLodScale = 2;
533536

537+
/**
538+
* Sets the alpha threshold below which splats are discarded during shadow, pick, and prepass
539+
* rendering. Higher values create more aggressive clipping, while lower values preserve more
540+
* translucent splats. Defaults to 0.3.
541+
*
542+
* @type {number}
543+
*/
544+
set alphaClip(value) {
545+
this._material.setParameter('alphaClip', value);
546+
this._material.update();
547+
}
548+
549+
/**
550+
* Gets the alpha clip threshold.
551+
*
552+
* @type {number}
553+
*/
554+
get alphaClip() {
555+
return this._material.getParameter('alphaClip')?.data ?? 0.3;
556+
}
557+
558+
/**
559+
* Sets the minimum screen-space pixel size below which splats are discarded. Defaults to 2.
560+
*
561+
* @type {number}
562+
*/
563+
set minPixelSize(value) {
564+
this._material.setParameter('minPixelSize', value);
565+
this._material.update();
566+
}
567+
568+
/**
569+
* Gets the minimum pixel size threshold.
570+
*
571+
* @type {number}
572+
*/
573+
get minPixelSize() {
574+
return this._material.getParameter('minPixelSize')?.data ?? 2.0;
575+
}
576+
577+
/**
578+
* Enables anti-aliasing compensation for Gaussian splats. Defaults to false.
579+
*
580+
* This option is intended for splat data that was generated with anti-aliasing
581+
* enabled during training/export. It improves visual stability and reduces
582+
* flickering for very small or distant splats.
583+
*
584+
* If the source splats were generated without anti-aliasing, enabling this
585+
* option may slightly soften the image or alter opacity.
586+
* @type {boolean}
587+
*/
588+
set antiAlias(value) {
589+
this._material.setDefine('GSPLAT_AA', value);
590+
this._material.update();
591+
}
592+
593+
/**
594+
* Gets whether anti-aliasing compensation is enabled.
595+
*
596+
* @type {boolean}
597+
*/
598+
get antiAlias() {
599+
return !!this._material.getDefine('GSPLAT_AA');
600+
}
601+
602+
/**
603+
* Enables 2D Gaussian Splatting mode. Defaults to false.
604+
*
605+
* Renders splats as oriented 2D surface elements instead of volumetric 3D Gaussians.
606+
* This provides a more surface-accurate appearance but requires splat data that
607+
* was generated for 2D Gaussian Splatting.
608+
*
609+
* Enabling this with standard 3D splat data may produce incorrect results.
610+
* @type {boolean}
611+
*/
612+
set twoDimensional(value) {
613+
this._material.setDefine('GSPLAT_2DGS', value);
614+
this._material.update();
615+
}
616+
617+
/**
618+
* Gets whether 2D Gaussian Splatting mode is enabled.
619+
*
620+
* @type {boolean}
621+
*/
622+
get twoDimensional() {
623+
return !!this._material.getDefine('GSPLAT_2DGS');
624+
}
625+
534626
/**
535627
* Number of update ticks before unloading unused streamed resources. When a streamed resource's
536628
* reference count reaches zero, it enters a cooldown period before being unloaded. This allows
@@ -589,7 +681,7 @@ class GSplatParams {
589681
* @type {ShaderMaterial}
590682
* @example
591683
* // Set a custom parameter on all GSplat materials
592-
* app.scene.gsplat.material.setParameter('alphaClip', 0.4);
684+
* app.scene.gsplat.material.setParameter('myCustomParam', 1.0);
593685
* app.scene.gsplat.material.update();
594686
*/
595687
get material() {

src/scene/gsplat-unified/gsplat-renderer.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ class GSplatRenderer {
181181

182182
this.setOrderData();
183183

184-
this._material.setParameter('alphaClip', 0.3);
185184
this._material.setDefine(`DITHER_${dither ? 'BLUENOISE' : 'NONE'}`, '');
186185
this._material.cull = CULLFACE_NONE;
187186
this._material.blendType = dither ? BLEND_NONE : BLEND_PREMULTIPLIED;

src/scene/gsplat/gsplat-instance.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class GSplatInstance {
180180
material.setParameter('numSplats', 0);
181181
this.setMaterialOrderData(material);
182182
material.setParameter('alphaClip', 0.3);
183+
material.setParameter('minPixelSize', 2.0);
183184
material.setDefine(`DITHER_${options.dither ? 'BLUENOISE' : 'NONE'}`, '');
184185
material.cull = CULLFACE_NONE;
185186
material.blendType = options.dither ? BLEND_NONE : BLEND_PREMULTIPLIED;

src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCorner.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export default /* glsl */`
22
uniform vec4 viewport_size; // viewport width, height, 1/width, 1/height
3+
uniform float minPixelSize;
34
45
// compute 3d covariance from rotation (w,x,y,z format) and scale
56
void computeCovariance(vec4 rotation, vec3 scale, out vec3 covA, out vec3 covB) {
@@ -62,8 +63,8 @@ bool initCornerCov(SplatSource source, SplatCenter center, out SplatCorner corne
6263
float l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);
6364
float l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);
6465
65-
// early-out gaussians smaller than 2 pixels
66-
if (l1 < 2.0 && l2 < 2.0) {
66+
// early-out gaussians smaller than minPixelSize
67+
if (max(l1, l2) < minPixelSize) {
6768
return false;
6869
}
6970

src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCorner.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export default /* wgsl */`
22
uniform viewport_size: vec4f; // viewport width, height, 1/width, 1/height
3+
uniform minPixelSize: f32;
34
45
// Rotation and scale source data are f16; covariance must be f32 to avoid scale^2 overflow
56
fn computeCovariance(rotation: half4, scale: half3, covA_ptr: ptr<function, vec3f>, covB_ptr: ptr<function, vec3f>) {
@@ -63,8 +64,8 @@ fn initCornerCov(source: ptr<function, SplatSource>, center: ptr<function, Splat
6364
let l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);
6465
let l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);
6566
66-
// early-out gaussians smaller than 2 pixels
67-
if (l1 < 2.0 && l2 < 2.0) {
67+
// early-out gaussians smaller than minPixelSize
68+
if (max(l1, l2) < uniform.minPixelSize) {
6869
return false;
6970
}
7071

0 commit comments

Comments
 (0)