@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
744 lines (743 loc) • 29.7 kB
JavaScript
/** This file must only contain pure code and pure imports */
import { MaterialDefines } from "../materialDefines.js";
import { MaterialPluginBase } from "../materialPluginBase.pure.js";
import { RegisterClass } from "../../Misc/typeStore.js";
import { Logger } from "../../Misc/logger.js";
import { GetGaussianSplattingMaxPartCount } from "./gaussianSplattingMaterial.pure.js";
import { RawTexture } from "../Textures/rawTexture.js";
/** @internal */
class GaussianSplattingDebugDefines extends MaterialDefines {
constructor() {
super(...arguments);
/** Defines whether any debug feature is active */
this.GS_DBG_ENABLED = false;
/** Defines whether world-space clipping box is enabled (0: off, 1: on) */
this.GS_DBG_CLIP = 0;
/** Defines whether opacity culling is enabled (0: off, 1: on) */
this.GS_DBG_CULL_OPACITY = 0;
/** Defines whether size culling is enabled (0: off, 1: on) */
this.GS_DBG_CULL_SIZE = 0;
/** Defines whether per-splat opacity scaling is enabled (0: off, 1: on) */
this.GS_DBG_OPACITY_SCALE = 0;
/** Defines whether opacity saturation (flat disk) is enabled (0: off, 1: on) */
this.GS_DBG_OPACITY_SATURATE = 0;
/** Defines whether the DC (base) SH color is included (0: off, 1: on) */
this.GS_DBG_SH_DC = 1;
/** Defines whether SH band 1 contribution is included (0: off, 1: on) */
this.GS_DBG_SH_ORDER1 = 1;
/** Defines whether SH band 2 contribution is included (0: off, 1: on) */
this.GS_DBG_SH_ORDER2 = 1;
/** Defines whether SH band 3 contribution is included (0: off, 1: on) */
this.GS_DBG_SH_ORDER3 = 1;
/** Defines whether SH band 4 contribution is included (0: off, 1: on) */
this.GS_DBG_SH_ORDER4 = 1;
}
}
/**
* Debug plugin for GaussianSplattingMaterial.
* Provides runtime controls for clipping, opacity/size culling, opacity scaling,
* opacity saturation, and per-SH-order toggling. All features are gated behind
* the GS_DBG_ENABLED shader define — when every option is at its default value
* the define is absent and the shader compiles to identical code as without the plugin.
*
* In compound mode (partCount \> 0), per-part overrides can be set via setPartOptions().
* Global settings act as defaults; per-part settings override them for that part index.
*/
export class GaussianSplattingDebugMaterialPlugin extends MaterialPluginBase {
/**
* Creates a new GaussianSplattingDebugMaterialPlugin.
* @param material The GaussianSplattingMaterial to attach the plugin to.
*/
constructor(material) {
super(material, "GaussianSplattingDebug", 200, new GaussianSplattingDebugDefines(), true, true);
this._clippingBox = null;
this._opacityCulling = null;
this._sizeCulling = null;
this._opacityScale = 1.0;
this._opacitySaturate = false;
this._shDc = true;
this._shOrder1 = true;
this._shOrder2 = true;
this._shOrder3 = true;
this._shOrder4 = true;
// Per-part state (only populated when partCount > 0)
this._partCount = 0;
this._partClippingBoxes = [];
this._partOpacityCullings = [];
this._partSizeCullings = [];
this._partOpacityScales = [];
this._partOpacitySaturates = [];
this._partShDcs = [];
this._partShOrder1s = [];
this._partShOrder2s = [];
this._partShOrder3s = [];
this._partShOrder4s = [];
// Per-part debug LUT: MAX_PART_COUNT × 5 RGBA float texture (dbgPartData)
this._dbgPartDataTexture = null;
this._textureDirty = false;
this._maxPartCount = 0;
this._partArrays = [
this._partClippingBoxes,
this._partOpacityCullings,
this._partSizeCullings,
this._partOpacityScales,
this._partOpacitySaturates,
this._partShDcs,
this._partShOrder1s,
this._partShOrder2s,
this._partShOrder3s,
this._partShOrder4s,
];
}
_isAnyFeatureActive() {
return (this._clippingBox !== null ||
this._opacityCulling !== null ||
this._sizeCulling !== null ||
this._opacityScale !== 1.0 ||
this._opacitySaturate ||
!this._shDc ||
!this._shOrder1 ||
!this._shOrder2 ||
!this._shOrder3 ||
!this._shOrder4 ||
this._partClippingBoxes.some((b) => b !== null) ||
this._partOpacityCullings.some((b) => b !== null) ||
this._partSizeCullings.some((b) => b !== null) ||
this._partOpacityScales.some((s) => s !== null) ||
this._partOpacitySaturates.some((s) => s !== null) ||
this._partShDcs.some((s) => s !== null) ||
this._partShOrder1s.some((s) => s !== null) ||
this._partShOrder2s.some((s) => s !== null) ||
this._partShOrder3s.some((s) => s !== null) ||
this._partShOrder4s.some((s) => s !== null));
}
_markDirty() {
this.markAllDefinesAsDirty();
this._textureDirty = true;
}
// ----- Public API -----
/**
* Number of parts in compound mode. Set automatically by GaussianSplattingDebugger.addMesh().
* When 0 (non-compound), setPartOptions() logs an error.
* @returns the part count
*/
get partCount() {
return this._partCount;
}
set partCount(count) {
if (this._partCount !== count) {
this._partCount = count;
this._markDirty();
}
}
/**
* Sets per-part debug overrides for the given part index.
* Only valid on compound meshes (partCount \> 0); logs an error otherwise.
* @param partIndex The zero-based part index.
* @param options Partial set of debug options to override for this part.
*/
setPartOptions(partIndex, options) {
if (this._partCount === 0) {
Logger.Error("GaussianSplattingDebugMaterialPlugin.setPartOptions: called on a non-compound mesh. partCount is 0.");
return;
}
if (options.clippingBox !== undefined) {
this._partClippingBoxes[partIndex] = options.clippingBox;
}
if (options.opacityCulling !== undefined) {
this._partOpacityCullings[partIndex] = options.opacityCulling;
}
if (options.sizeCulling !== undefined) {
this._partSizeCullings[partIndex] = options.sizeCulling;
}
if (options.opacityScale !== undefined) {
this._partOpacityScales[partIndex] = options.opacityScale;
}
if (options.opacitySaturate !== undefined) {
this._partOpacitySaturates[partIndex] = options.opacitySaturate;
}
if (options.shDc !== undefined) {
this._partShDcs[partIndex] = options.shDc;
}
if (options.shOrder1 !== undefined) {
this._partShOrder1s[partIndex] = options.shOrder1;
}
if (options.shOrder2 !== undefined) {
this._partShOrder2s[partIndex] = options.shOrder2;
}
if (options.shOrder3 !== undefined) {
this._partShOrder3s[partIndex] = options.shOrder3;
}
if (options.shOrder4 !== undefined) {
this._partShOrder4s[partIndex] = options.shOrder4;
}
this._markDirty();
}
/**
* Clears all per-part debug overrides for the given part index,
* falling back to global settings.
* @param partIndex The zero-based part index.
*/
clearPartOptions(partIndex) {
this._partClippingBoxes[partIndex] = null;
this._partOpacityCullings[partIndex] = null;
this._partSizeCullings[partIndex] = null;
this._partOpacityScales[partIndex] = null;
this._partOpacitySaturates[partIndex] = null;
this._partShDcs[partIndex] = null;
this._partShOrder1s[partIndex] = null;
this._partShOrder2s[partIndex] = null;
this._partShOrder3s[partIndex] = null;
this._partShOrder4s[partIndex] = null;
this._markDirty();
}
/**
* Removes the per-part override slot at `removedIndex` and shifts all higher-indexed
* slots down by one, keeping the arrays aligned with the compound mesh's new part layout.
* @param removedIndex The original (pre-removal) part index.
* @internal
*/
shiftPartOptions(removedIndex) {
for (const arr of this._partArrays) {
if (removedIndex < arr.length) {
arr.splice(removedIndex, 1);
}
}
this._markDirty();
}
/**
* World-space axis-aligned clipping box. Splats outside are not rendered.
* Set to null to disable clipping.
* Example: `{ min: new Vector3(-2,-2,-2), max: new Vector3(2,2,2) }`
*/
get clippingBox() {
return this._clippingBox;
}
set clippingBox(value) {
this._clippingBox = value;
this._markDirty();
}
/**
* Opacity culling range [0..1]. Splats whose stored opacity falls outside this
* range are not rendered. Set to null to disable.
*/
get opacityCulling() {
return this._opacityCulling;
}
set opacityCulling(value) {
this._opacityCulling = value;
this._markDirty();
}
/**
* Size culling range. Size is pow(|det(Σ)|, 1/6) of the 3D covariance matrix,
* equal to the geometric mean of the principal radii. Use GaussianSplattingMeshBase.splatSizeRange
* to find the asset's range. Set to null to disable.
*/
get sizeCulling() {
return this._sizeCulling;
}
set sizeCulling(value) {
this._sizeCulling = value;
this._markDirty();
}
/**
* Scalar multiplier applied to every splat's opacity after all other modifiers.
* 1.0 (default) = no change.
*/
get opacityScale() {
return this._opacityScale;
}
set opacityScale(value) {
this._opacityScale = value;
this._markDirty();
}
/**
* When true, replaces the Gaussian spatial falloff with a flat uniform opacity,
* making each splat appear as a solid disk with its raw alpha value.
*/
get opacitySaturate() {
return this._opacitySaturate;
}
set opacitySaturate(value) {
this._opacitySaturate = value;
this._markDirty();
}
/** Include the DC (base) color from colorsTexture. Default: true. */
get shDc() {
return this._shDc;
}
set shDc(value) {
this._shDc = value;
this._markDirty();
}
/** Include SH band 1 contribution. Default: true. */
get shOrder1() {
return this._shOrder1;
}
set shOrder1(value) {
this._shOrder1 = value;
this._markDirty();
}
/** Include SH band 2 contribution. Default: true. */
get shOrder2() {
return this._shOrder2;
}
set shOrder2(value) {
this._shOrder2 = value;
this._markDirty();
}
/** Include SH band 3 contribution. Default: true. */
get shOrder3() {
return this._shOrder3;
}
set shOrder3(value) {
this._shOrder3 = value;
this._markDirty();
}
/** Include SH band 4 contribution. Default: true. */
get shOrder4() {
return this._shOrder4;
}
set shOrder4(value) {
this._shOrder4 = value;
this._markDirty();
}
// ----- Plugin overrides -----
/**
* Adds the per-part debug data texture name to the sampler list so the effect can bind it.
* @param samplers the sampler list to populate
*/
getSamplers(samplers) {
samplers.push("dbgPartData");
}
/** @returns the class name of this plugin */
getClassName() {
return "GaussianSplattingDebugMaterialPlugin";
}
/**
* @param shaderLanguage the shader language to check
* @returns true for GLSL and WGSL
*/
isCompatible(shaderLanguage) {
switch (shaderLanguage) {
case 0 /* ShaderLanguage.GLSL */:
case 1 /* ShaderLanguage.WGSL */:
return true;
default:
return false;
}
}
/**
* Always ready — no async resources.
* @param _defines unused
* @param _scene unused
* @param _engine unused
* @param _subMesh unused
* @returns true
*/
isReadyForSubMesh(_defines, _scene, _engine, _subMesh) {
return true;
}
/**
* Sets shader defines from current property state. GS_DBG_ENABLED is set to true
* only when at least one feature is non-default, ensuring zero overhead otherwise.
* Sub-flags also check per-part arrays so compound-only overrides activate the correct
* code paths even when the global setting is at its default.
* @param defines the defines object
*/
prepareDefines(defines) {
defines.GS_DBG_ENABLED = this._isAnyFeatureActive();
defines.GS_DBG_CLIP = this._clippingBox !== null || this._partClippingBoxes.some((b) => b !== null) ? 1 : 0;
defines.GS_DBG_CULL_OPACITY = this._opacityCulling !== null || this._partOpacityCullings.some((b) => b !== null) ? 1 : 0;
defines.GS_DBG_CULL_SIZE = this._sizeCulling !== null || this._partSizeCullings.some((b) => b !== null) ? 1 : 0;
defines.GS_DBG_OPACITY_SCALE = this._opacityScale !== 1.0 || this._partOpacityScales.some((s) => s !== null) ? 1 : 0;
defines.GS_DBG_OPACITY_SATURATE = this._opacitySaturate || this._partOpacitySaturates.some((s) => s !== null) ? 1 : 0;
defines.GS_DBG_SH_DC = !this._shDc || this._partShDcs.some((s) => s !== null) ? 0 : 1;
defines.GS_DBG_SH_ORDER1 = !this._shOrder1 || this._partShOrder1s.some((s) => s !== null) ? 0 : 1;
defines.GS_DBG_SH_ORDER2 = !this._shOrder2 || this._partShOrder2s.some((s) => s !== null) ? 0 : 1;
defines.GS_DBG_SH_ORDER3 = !this._shOrder3 || this._partShOrder3s.some((s) => s !== null) ? 0 : 1;
defines.GS_DBG_SH_ORDER4 = !this._shOrder4 || this._partShOrder4s.some((s) => s !== null) ? 0 : 1;
}
/**
* Returns shader code injections for the debug features.
* @param shaderType "vertex" or "fragment"
* @param shaderLanguage GLSL or WGSL
* @returns map of injection-point name to injected code, or null
*/
getCustomCode(shaderType, shaderLanguage = 0 /* ShaderLanguage.GLSL */) {
if (shaderLanguage === 1 /* ShaderLanguage.WGSL */) {
return this._getCustomCodeWGSL(shaderType);
}
return this._getCustomCodeGLSL(shaderType);
}
_getCustomCodeGLSL(shaderType) {
if (shaderType === "vertex") {
return {
CUSTOM_VERTEX_DEFINITIONS: `
#if defined(GS_DBG_ENABLED) && GS_DBG_CLIP == 1 && !defined(IS_COMPOUND)
uniform vec3 dbgClipMin;
uniform vec3 dbgClipMax;
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_CULL_OPACITY == 1 && !defined(IS_COMPOUND)
uniform float dbgMinOpacity;
uniform float dbgMaxOpacity;
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_CULL_SIZE == 1 && !defined(IS_COMPOUND)
uniform float dbgMinSize;
uniform float dbgMaxSize;
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_OPACITY_SCALE == 1 && !defined(IS_COMPOUND)
uniform float dbgOpacityScale;
#endif
#if defined(GS_DBG_ENABLED) && IS_COMPOUND
uniform sampler2D dbgPartData;
varying float vPartIndex;
#endif
`,
CUSTOM_VERTEX_UPDATE: `
#if defined(GS_DBG_ENABLED) && GS_DBG_CLIP == 1 && !defined(IS_COMPOUND)
if (worldPos.x < dbgClipMin.x || worldPos.x > dbgClipMax.x ||
worldPos.y < dbgClipMin.y || worldPos.y > dbgClipMax.y ||
worldPos.z < dbgClipMin.z || worldPos.z > dbgClipMax.z) {
scale = vec2(0.0);
}
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_CULL_OPACITY == 1 && !defined(IS_COMPOUND)
if (splat.color.w < dbgMinOpacity || splat.color.w > dbgMaxOpacity) {
scale = vec2(0.0);
}
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_CULL_SIZE == 1 && !defined(IS_COMPOUND)
{
float _d0 = splat.covA.x; float _d1 = splat.covA.y; float _d2 = splat.covA.z;
float _d3 = splat.covA.w; float _d4 = splat.covB.x; float _d5 = splat.covB.y;
float _det = _d0*(_d3*_d5 - _d4*_d4) - _d1*(_d1*_d5 - _d4*_d2) + _d2*(_d1*_d4 - _d3*_d2);
float _sz = pow(abs(_det), 1.0/6.0);
if (_sz < dbgMinSize || _sz > dbgMaxSize) {
scale = vec2(0.0);
}
}
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_OPACITY_SCALE == 1 && !defined(IS_COMPOUND)
vColor.w *= dbgOpacityScale;
#endif
#if defined(GS_DBG_ENABLED) && IS_COMPOUND
{
int _pIdx = int(splat.partIndex);
vPartIndex = float(splat.partIndex);
vec4 _row0 = texelFetch(dbgPartData, ivec2(_pIdx, 0), 0);
vec4 _row1 = texelFetch(dbgPartData, ivec2(_pIdx, 1), 0);
vec4 _row2 = texelFetch(dbgPartData, ivec2(_pIdx, 2), 0);
vec3 _clipMin = _row0.xyz;
vec3 _clipMax = vec3(_row0.w, _row1.xy);
float _minOp = _row1.z;
float _maxOp = _row1.w;
float _minSz = _row2.x;
float _maxSz = _row2.y;
float _opSc = _row2.z;
#if GS_DBG_CLIP == 1
if (worldPos.x < _clipMin.x || worldPos.x > _clipMax.x ||
worldPos.y < _clipMin.y || worldPos.y > _clipMax.y ||
worldPos.z < _clipMin.z || worldPos.z > _clipMax.z) {
scale = vec2(0.0);
}
#endif
#if GS_DBG_CULL_OPACITY == 1
if (splat.color.w < _minOp || splat.color.w > _maxOp) {
scale = vec2(0.0);
}
#endif
#if GS_DBG_CULL_SIZE == 1
{
float _d0 = splat.covA.x; float _d1 = splat.covA.y; float _d2 = splat.covA.z;
float _d3 = splat.covA.w; float _d4 = splat.covB.x; float _d5 = splat.covB.y;
float _det = _d0*(_d3*_d5 - _d4*_d4) - _d1*(_d1*_d5 - _d4*_d2) + _d2*(_d1*_d4 - _d3*_d2);
float _sz = pow(abs(_det), 1.0/6.0);
if (_sz < _minSz || _sz > _maxSz) {
scale = vec2(0.0);
}
}
#endif
#if GS_DBG_OPACITY_SCALE == 1
vColor.w *= _opSc;
#endif
}
#endif
`,
};
}
else if (shaderType === "fragment") {
return {
CUSTOM_FRAGMENT_DEFINITIONS: `
#if defined(GS_DBG_ENABLED) && IS_COMPOUND
uniform sampler2D dbgPartData;
varying float vPartIndex;
#endif
`,
CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR: `
#if defined(GS_DBG_ENABLED) && IS_COMPOUND
if (texelFetch(dbgPartData, ivec2(int(vPartIndex + 0.5), 2), 0).w > 0.5) { finalColor.a = vColor.a; }
#elif defined(GS_DBG_ENABLED) && GS_DBG_OPACITY_SATURATE == 1
finalColor.a = vColor.a;
#endif
`,
};
}
return null;
}
_getCustomCodeWGSL(shaderType) {
if (shaderType === "vertex") {
return {
CUSTOM_VERTEX_DEFINITIONS: `
#if defined(GS_DBG_ENABLED) && GS_DBG_CLIP == 1 && !defined(IS_COMPOUND)
uniform dbgClipMin: vec3f;
uniform dbgClipMax: vec3f;
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_CULL_OPACITY == 1 && !defined(IS_COMPOUND)
uniform dbgMinOpacity: f32;
uniform dbgMaxOpacity: f32;
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_CULL_SIZE == 1 && !defined(IS_COMPOUND)
uniform dbgMinSize: f32;
uniform dbgMaxSize: f32;
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_OPACITY_SCALE == 1 && !defined(IS_COMPOUND)
uniform dbgOpacityScale: f32;
#endif
#if defined(GS_DBG_ENABLED) && IS_COMPOUND
var dbgPartData: texture_2d<f32>;
varying vPartIndex: f32;
#endif
`,
CUSTOM_VERTEX_UPDATE: `
#if defined(GS_DBG_ENABLED) && GS_DBG_CLIP == 1 && !defined(IS_COMPOUND)
if (worldPos.x < uniforms.dbgClipMin.x || worldPos.x > uniforms.dbgClipMax.x ||
worldPos.y < uniforms.dbgClipMin.y || worldPos.y > uniforms.dbgClipMax.y ||
worldPos.z < uniforms.dbgClipMin.z || worldPos.z > uniforms.dbgClipMax.z) {
scale = vec2f(0.0);
}
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_CULL_OPACITY == 1 && !defined(IS_COMPOUND)
if (splat.color.w < uniforms.dbgMinOpacity || splat.color.w > uniforms.dbgMaxOpacity) {
scale = vec2f(0.0);
}
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_CULL_SIZE == 1 && !defined(IS_COMPOUND)
{
let _d0 = splat.covA.x; let _d1 = splat.covA.y; let _d2 = splat.covA.z;
let _d3 = splat.covA.w; let _d4 = splat.covB.x; let _d5 = splat.covB.y;
let _det = _d0*(_d3*_d5 - _d4*_d4) - _d1*(_d1*_d5 - _d4*_d2) + _d2*(_d1*_d4 - _d3*_d2);
let _sz = pow(abs(_det), 1.0/6.0);
if (_sz < uniforms.dbgMinSize || _sz > uniforms.dbgMaxSize) {
scale = vec2f(0.0);
}
}
#endif
#if defined(GS_DBG_ENABLED) && GS_DBG_OPACITY_SCALE == 1 && !defined(IS_COMPOUND)
vertexOutputs.vColor.w *= uniforms.dbgOpacityScale;
#endif
#if defined(GS_DBG_ENABLED) && IS_COMPOUND
{
let _pIdx = i32(splat.partIndex);
vertexOutputs.vPartIndex = f32(splat.partIndex);
let _row0 = textureLoad(dbgPartData, vec2i(_pIdx, 0), 0);
let _row1 = textureLoad(dbgPartData, vec2i(_pIdx, 1), 0);
let _row2 = textureLoad(dbgPartData, vec2i(_pIdx, 2), 0);
let _clipMin = _row0.xyz;
let _clipMax = vec3f(_row0.w, _row1.xy);
let _minOp = _row1.z;
let _maxOp = _row1.w;
let _minSz = _row2.x;
let _maxSz = _row2.y;
let _opSc = _row2.z;
#if GS_DBG_CLIP == 1
if (worldPos.x < _clipMin.x || worldPos.x > _clipMax.x ||
worldPos.y < _clipMin.y || worldPos.y > _clipMax.y ||
worldPos.z < _clipMin.z || worldPos.z > _clipMax.z) {
scale = vec2f(0.0);
}
#endif
#if GS_DBG_CULL_OPACITY == 1
if (splat.color.w < _minOp || splat.color.w > _maxOp) {
scale = vec2f(0.0);
}
#endif
#if GS_DBG_CULL_SIZE == 1
{
let _d0 = splat.covA.x; let _d1 = splat.covA.y; let _d2 = splat.covA.z;
let _d3 = splat.covA.w; let _d4 = splat.covB.x; let _d5 = splat.covB.y;
let _det = _d0*(_d3*_d5 - _d4*_d4) - _d1*(_d1*_d5 - _d4*_d2) + _d2*(_d1*_d4 - _d3*_d2);
let _sz = pow(abs(_det), 1.0/6.0);
if (_sz < _minSz || _sz > _maxSz) {
scale = vec2f(0.0);
}
}
#endif
#if GS_DBG_OPACITY_SCALE == 1
vertexOutputs.vColor.w *= _opSc;
#endif
}
#endif
`,
};
}
else if (shaderType === "fragment") {
return {
CUSTOM_FRAGMENT_DEFINITIONS: `
#if defined(GS_DBG_ENABLED) && IS_COMPOUND
var dbgPartData: texture_2d<f32>;
varying vPartIndex: f32;
#endif
`,
CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR: `
#if defined(GS_DBG_ENABLED) && IS_COMPOUND
if (textureLoad(dbgPartData, vec2i(i32(fragmentInputs.vPartIndex + 0.5), 2), 0).w > 0.5) {
let _gsdbgA: f32 = -dot(fragmentInputs.vPosition, fragmentInputs.vPosition);
if (_gsdbgA > -4.0) { finalColor.a = fragmentInputs.vColor.a; }
}
#elif defined(GS_DBG_ENABLED) && GS_DBG_OPACITY_SATURATE == 1
{
let _gsdbgA: f32 = -dot(fragmentInputs.vPosition, fragmentInputs.vPosition);
if (_gsdbgA > -4.0) {
finalColor.a = fragmentInputs.vColor.a;
}
}
#endif
`,
};
}
return null;
}
/**
* Declares the non-compound scalar debug uniform names as external so the Effect can
* resolve their locations. WGSL uniforms are declared inline in getCustomCode() injections.
* @returns uniform descriptor with externalUniforms list
*/
getUniforms() {
return {
externalUniforms: ["dbgClipMin", "dbgClipMax", "dbgMinOpacity", "dbgMaxOpacity", "dbgMinSize", "dbgMaxSize", "dbgOpacityScale"],
};
}
_buildTextureData(partCount) {
const maxPartCount = this._maxPartCount;
const data = new Float32Array(maxPartCount * 5 * 4);
for (let i = 0; i < maxPartCount; i++) {
const r0 = i * 4;
const r1 = (maxPartCount + i) * 4;
const r2 = (maxPartCount * 2 + i) * 4;
const r3 = (maxPartCount * 3 + i) * 4;
const r4 = (maxPartCount * 4 + i) * 4;
// Row 0: clipMin.xyz, clipMax.x — Row 1: clipMax.yz, minOpacity, maxOpacity
const box = i < partCount ? (this._partClippingBoxes[i] ?? this._clippingBox) : this._clippingBox;
if (box) {
data[r0] = box.min.x;
data[r0 + 1] = box.min.y;
data[r0 + 2] = box.min.z;
data[r0 + 3] = box.max.x;
data[r1] = box.max.y;
data[r1 + 1] = box.max.z;
}
else {
data[r0] = -1e9;
data[r0 + 1] = -1e9;
data[r0 + 2] = -1e9;
data[r0 + 3] = 1e9;
data[r1] = 1e9;
data[r1 + 1] = 1e9;
}
const opCull = i < partCount ? (this._partOpacityCullings[i] ?? this._opacityCulling) : this._opacityCulling;
data[r1 + 2] = opCull ? opCull.min : 0.0;
data[r1 + 3] = opCull ? opCull.max : 1.0;
// Row 2: minSize, maxSize, opacityScale, opacitySaturate
const szCull = i < partCount ? (this._partSizeCullings[i] ?? this._sizeCulling) : this._sizeCulling;
data[r2] = szCull ? szCull.min : 0.0;
data[r2 + 1] = szCull ? szCull.max : 1e9;
data[r2 + 2] = i < partCount ? (this._partOpacityScales[i] ?? this._opacityScale) : this._opacityScale;
const sat = i < partCount ? (this._partOpacitySaturates[i] ?? this._opacitySaturate) : this._opacitySaturate;
data[r2 + 3] = sat ? 1.0 : 0.0;
// Row 3: shDc, shOrder1, shOrder2, shOrder3
data[r3] = (i < partCount ? (this._partShDcs[i] ?? this._shDc) : this._shDc) ? 1.0 : 0.0;
data[r3 + 1] = (i < partCount ? (this._partShOrder1s[i] ?? this._shOrder1) : this._shOrder1) ? 1.0 : 0.0;
data[r3 + 2] = (i < partCount ? (this._partShOrder2s[i] ?? this._shOrder2) : this._shOrder2) ? 1.0 : 0.0;
data[r3 + 3] = (i < partCount ? (this._partShOrder3s[i] ?? this._shOrder3) : this._shOrder3) ? 1.0 : 0.0;
// Row 4: shOrder4, padding
data[r4] = (i < partCount ? (this._partShOrder4s[i] ?? this._shOrder4) : this._shOrder4) ? 1.0 : 0.0;
}
return data;
}
_updateOrCreateTexture(scene) {
if (!this._maxPartCount) {
this._maxPartCount = GetGaussianSplattingMaxPartCount(scene.getEngine());
}
const data = this._buildTextureData(this._partCount);
if (!this._dbgPartDataTexture) {
this._dbgPartDataTexture = RawTexture.CreateRGBATexture(data, this._maxPartCount, 5, scene, false, false, 1, 1);
}
else {
this._dbgPartDataTexture.update(data);
}
this._textureDirty = false;
}
/**
* Binds uniform values each frame. Scalar uniforms are uploaded for non-compound mode;
* the per-part data texture is updated and bound for compound mode.
* @param _uniformBuffer unused
* @param _scene the current scene
* @param _engine unused
* @param subMesh the submesh being rendered
*/
bindForSubMesh(_uniformBuffer, _scene, _engine, subMesh) {
const effect = subMesh.effect;
if (!effect) {
return;
}
if (this._partCount > 0 && this._isAnyFeatureActive()) {
// Compound mode: pack per-part data into a texture (only when the debug path is active)
if (this._textureDirty || !this._dbgPartDataTexture) {
this._updateOrCreateTexture(_scene);
}
if (this._dbgPartDataTexture) {
effect.setTexture("dbgPartData", this._dbgPartDataTexture);
}
}
else if (this._partCount === 0) {
// Non-compound: upload scalar uniforms
if (this._clippingBox) {
effect.setVector3("dbgClipMin", this._clippingBox.min);
effect.setVector3("dbgClipMax", this._clippingBox.max);
}
if (this._opacityCulling) {
effect.setFloat("dbgMinOpacity", this._opacityCulling.min);
effect.setFloat("dbgMaxOpacity", this._opacityCulling.max);
}
if (this._sizeCulling) {
effect.setFloat("dbgMinSize", this._sizeCulling.min);
effect.setFloat("dbgMaxSize", this._sizeCulling.max);
}
if (this._opacityScale !== 1.0) {
effect.setFloat("dbgOpacityScale", this._opacityScale);
}
}
}
/**
* Disposes the per-part data texture.
* @param _forceDisposeTextures unused; the LUT texture is always disposed as it is owned by this plugin
*/
dispose(_forceDisposeTextures) {
this._dbgPartDataTexture?.dispose();
this._dbgPartDataTexture = null;
}
}
let _Registered = false;
/**
* Register side effects for GaussianSplattingDebugMaterialPlugin.
* Safe to call multiple times; only the first call has an effect.
*/
export function RegisterGaussianSplattingDebugMaterialPlugin() {
if (_Registered) {
return;
}
_Registered = true;
RegisterClass("BABYLON.GaussianSplattingDebugMaterialPlugin", GaussianSplattingDebugMaterialPlugin);
}
//# sourceMappingURL=gaussianSplattingDebugMaterialPlugin.pure.js.map