Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ assetListLoader.load(() => {
player.setLocalScale(80, 80, 80);
app.root.addChild(player);

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

// Create shadow catcher
const shadowCatcher = new pc.Entity('ShadowCatcher');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
jsx(
Panel,
{ headerText: 'Settings' },
jsx(
LabelGroup,
{ text: 'Min Pixel Size' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'minPixelSize' },
min: 0,
max: 5,
precision: 1
})
),
jsx(
LabelGroup,
{ text: 'Radial' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ assetListLoader.load(() => {
app.scene.gsplat.culling = !!data.get('culling');
});
}
data.on('minPixelSize:set', () => {
app.scene.gsplat.minPixelSize = data.get('minPixelSize');
});
data.on('debugLod:set', () => {
app.scene.gsplat.colorizeLod = !!data.get('debugLod');
});
Expand All @@ -155,6 +158,7 @@ assetListLoader.load(() => {

// initialize UI settings (must be after observer registration)
data.set('cameraFov', 75);
data.set('minPixelSize', 2);
data.set('radialSorting', true);
data.set('gpuSorting', false);
data.set('culling', device.isWebGPU);
Expand Down
20 changes: 20 additions & 0 deletions examples/src/examples/gaussian-splatting/shadows.controls.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @param {import('../../app/components/Example.mjs').ControlOptions} options - The options.
* @returns {JSX.Element} The returned JSX Element.
*/
export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
const { BindingTwoWay, LabelGroup, SliderInput } = ReactPCUI;
return fragment(
jsx(
LabelGroup,
{ text: 'Alpha Clip' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'alphaClip' },
min: 0,
max: 1,
precision: 2
})
)
);
};
9 changes: 5 additions & 4 deletions examples/src/examples/gaussian-splatting/shadows.example.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @config HIDDEN
// @config DESCRIPTION Demonstrates shadow catching with Gaussian Splats.
import { data } from 'examples/observer';
import { deviceType, rootPath, fileImport } from 'examples/utils';
import * as pc from 'playcanvas';

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

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

// Create first splat entity
const biker = new pc.Entity('biker');
Expand Down
94 changes: 93 additions & 1 deletion src/scene/gsplat-unified/gsplat-params.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class GSplatParams {
constructor(device) {
this._device = device;
this._format = this._createFormat(GSPLATDATA_COMPACT);

this._material.setParameter('alphaClip', 0.3);
this._material.setParameter('minPixelSize', 2.0);
}

/**
Expand Down Expand Up @@ -531,6 +534,95 @@ class GSplatParams {
*/
colorUpdateAngleLodScale = 2;

/**
* Sets the alpha threshold below which splats are discarded during shadow, pick, and prepass
* rendering. Higher values create more aggressive clipping, while lower values preserve more
* translucent splats. Defaults to 0.3.
*
* @type {number}
*/
set alphaClip(value) {
this._material.setParameter('alphaClip', value);
this._material.update();
}

/**
* Gets the alpha clip threshold.
*
* @type {number}
*/
get alphaClip() {
return this._material.getParameter('alphaClip')?.data ?? 0.3;
}

/**
* Sets the minimum screen-space pixel size below which splats are discarded. Defaults to 2.
*
* @type {number}
*/
set minPixelSize(value) {
this._material.setParameter('minPixelSize', value);
this._material.update();
}

/**
* Gets the minimum pixel size threshold.
*
* @type {number}
*/
get minPixelSize() {
return this._material.getParameter('minPixelSize')?.data ?? 2.0;
}

/**
* Enables anti-aliasing compensation for Gaussian splats. Defaults to false.
*
* This option is intended for splat data that was generated with anti-aliasing
* enabled during training/export. It improves visual stability and reduces
* flickering for very small or distant splats.
*
* If the source splats were generated without anti-aliasing, enabling this
* option may slightly soften the image or alter opacity.
* @type {boolean}
*/
set antiAlias(value) {
this._material.setDefine('GSPLAT_AA', value);
this._material.update();
}

/**
* Gets whether anti-aliasing compensation is enabled.
*
* @type {boolean}
*/
get antiAlias() {
return !!this._material.getDefine('GSPLAT_AA');
}

/**
* Enables 2D Gaussian Splatting mode. Defaults to false.
*
* Renders splats as oriented 2D surface elements instead of volumetric 3D Gaussians.
* This provides a more surface-accurate appearance but requires splat data that
* was generated for 2D Gaussian Splatting.
*
* Enabling this with standard 3D splat data may produce incorrect results.
* @type {boolean}
*/
set twoDimensional(value) {
this._material.setDefine('GSPLAT_2DGS', value);
this._material.update();
}

/**
* Gets whether 2D Gaussian Splatting mode is enabled.
*
* @type {boolean}
*/
get twoDimensional() {
return !!this._material.getDefine('GSPLAT_2DGS');
}

/**
* Number of update ticks before unloading unused streamed resources. When a streamed resource's
* reference count reaches zero, it enters a cooldown period before being unloaded. This allows
Expand Down Expand Up @@ -589,7 +681,7 @@ class GSplatParams {
* @type {ShaderMaterial}
* @example
* // Set a custom parameter on all GSplat materials
* app.scene.gsplat.material.setParameter('alphaClip', 0.4);
* app.scene.gsplat.material.setParameter('myCustomParam', 1.0);
* app.scene.gsplat.material.update();
*/
get material() {
Expand Down
1 change: 0 additions & 1 deletion src/scene/gsplat-unified/gsplat-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ class GSplatRenderer {

this.setOrderData();

this._material.setParameter('alphaClip', 0.3);
this._material.setDefine(`DITHER_${dither ? 'BLUENOISE' : 'NONE'}`, '');
this._material.cull = CULLFACE_NONE;
this._material.blendType = dither ? BLEND_NONE : BLEND_PREMULTIPLIED;
Expand Down
1 change: 1 addition & 0 deletions src/scene/gsplat/gsplat-instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class GSplatInstance {
material.setParameter('numSplats', 0);
this.setMaterialOrderData(material);
material.setParameter('alphaClip', 0.3);
material.setParameter('minPixelSize', 2.0);
material.setDefine(`DITHER_${options.dither ? 'BLUENOISE' : 'NONE'}`, '');
material.cull = CULLFACE_NONE;
material.blendType = options.dither ? BLEND_NONE : BLEND_PREMULTIPLIED;
Expand Down
5 changes: 3 additions & 2 deletions src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCorner.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export default /* glsl */`
uniform vec4 viewport_size; // viewport width, height, 1/width, 1/height
uniform float minPixelSize;

// compute 3d covariance from rotation (w,x,y,z format) and scale
void computeCovariance(vec4 rotation, vec3 scale, out vec3 covA, out vec3 covB) {
Expand Down Expand Up @@ -62,8 +63,8 @@ bool initCornerCov(SplatSource source, SplatCenter center, out SplatCorner corne
float l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);
float l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);

// early-out gaussians smaller than 2 pixels
if (l1 < 2.0 && l2 < 2.0) {
// early-out gaussians smaller than minPixelSize
if (max(l1, l2) < minPixelSize) {
return false;
}

Expand Down
5 changes: 3 additions & 2 deletions src/scene/shader-lib/wgsl/chunks/gsplat/vert/gsplatCorner.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export default /* wgsl */`
uniform viewport_size: vec4f; // viewport width, height, 1/width, 1/height
uniform minPixelSize: f32;

// Rotation and scale source data are f16; covariance must be f32 to avoid scale^2 overflow
fn computeCovariance(rotation: half4, scale: half3, covA_ptr: ptr<function, vec3f>, covB_ptr: ptr<function, vec3f>) {
Expand Down Expand Up @@ -63,8 +64,8 @@ fn initCornerCov(source: ptr<function, SplatSource>, center: ptr<function, Splat
let l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);
let l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);

// early-out gaussians smaller than 2 pixels
if (l1 < 2.0 && l2 < 2.0) {
// early-out gaussians smaller than minPixelSize
if (max(l1, l2) < uniform.minPixelSize) {
return false;
}

Expand Down