Skip to content

Commit de1e52d

Browse files
mvaligurskyMartin Valigursky
andauthored
Updated GsplstShaderEffect script to use simpler material API (#8229)
* Updated GsplstShaderEffect script to use simpler material API * small fixes to material update logic --------- Co-authored-by: Martin Valigursky <[email protected]>
1 parent 8593288 commit de1e52d

File tree

4 files changed

+38
-137
lines changed

4 files changed

+38
-137
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ assetListLoader.load(() => {
196196
camera.addComponent('camera', {
197197
clearColor: new pc.Color(0.2, 0.2, 0.2),
198198
fov: 75,
199-
toneMapping: pc.TONEMAP_ACES
199+
toneMapping: pc.TONEMAP_LINEAR
200200
});
201201

202202
// Set camera position

scripts/esm/gsplat/gsplat-shader-effect.mjs

Lines changed: 28 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ import { Script } from 'playcanvas';
1414
* `entity.gsplat.material` and applies shader customizations immediately or when the asset loads.
1515
*
1616
* **Unified Mode (`unified=true`):**
17-
* Multiple gsplat components share materials per camera/layer combination. Materials are created
18-
* during the first frame render. The script listens to the 'material:created' event for immediate
19-
* notification and retries each frame as fallback to ensure materials are applied.
17+
* Multiple gsplat components share a template material accessible via `app.scene.gsplat.material`.
18+
* The script applies shader customizations to this template material.
2019
*
2120
* **Enable/Disable:**
2221
* When enabled, the shader effect is applied and effectTime starts tracking from 0.
@@ -32,30 +31,22 @@ import { Script } from 'playcanvas';
3231
class GsplatShaderEffect extends Script {
3332
static scriptName = 'gsplatShaderEffect';
3433

35-
/**
36-
* Optional camera entity to target in unified mode. If not set, applies to all cameras.
37-
*
38-
* @attribute
39-
* @type {import('playcanvas').Entity | null}
40-
*/
41-
camera = null;
42-
4334
/**
4435
* Time since effect was enabled
4536
* @type {number}
4637
*/
4738
effectTime = 0;
4839

4940
/**
50-
* Set of materials with applied shader
51-
* @type {Set<import('playcanvas').Material>}
41+
* The material this effect is applied to
42+
* @type {import('playcanvas').Material | null}
5243
*/
53-
materialsApplied = new Set();
44+
material = null;
5445

5546
initialize() {
5647
this.initialized = false;
5748
this.effectTime = 0;
58-
this.materialsApplied.clear();
49+
this.material = null;
5950
this.shadersNeedApplication = false;
6051

6152
// Listen to enable/disable events
@@ -81,10 +72,6 @@ class GsplatShaderEffect extends Script {
8172
this.removeShaders();
8273
});
8374

84-
// Register event listener immediately for unified mode to catch materials created on first frame
85-
// This is safe to call even if the gsplat component doesn't exist yet
86-
this.setupUnifiedEventListener();
87-
8875
if (!this.entity.gsplat) {
8976
// gsplat component not yet available, will retry each frame
9077
return;
@@ -100,7 +87,7 @@ class GsplatShaderEffect extends Script {
10087

10188
applyShaders() {
10289
if (this.entity.gsplat?.unified) {
103-
// Unified mode: Apply to specified camera (or all cameras if not specified)
90+
// Unified mode: Apply to template material
10491
this.applyToUnifiedMaterials();
10592
} else {
10693
// Non-unified mode: Apply to component's material
@@ -109,63 +96,24 @@ class GsplatShaderEffect extends Script {
10996
}
11097

11198
removeShaders() {
112-
if (this.materialsApplied.size === 0) return;
99+
if (!this.material) return;
113100

114101
const device = this.app.graphicsDevice;
115102
const shaderLanguage = device?.isWebGPU ? 'wgsl' : 'glsl';
116103

117-
// Remove custom shader chunk from all materials
118-
this.materialsApplied.forEach((material) => {
119-
material.getShaderChunks(shaderLanguage).delete('gsplatCustomizeVS');
120-
material.update();
121-
});
122-
123-
// Clear the set and stop tracking
124-
this.materialsApplied.clear();
125-
}
126-
127-
setupUnifiedEventListener() {
128-
// Only set up once
129-
if (this._materialCreatedHandler) return;
130-
131-
// @ts-ignore - gsplat system exists at runtime
132-
const gsplatSystem = this.app.systems.gsplat;
133-
134-
// Set up event listener
135-
this._materialCreatedHandler = (material, camera, layer) => {
136-
// Only apply if enabled
137-
if (!this.enabled) return;
138-
139-
// Apply shader immediately when material is created
140-
// The gsplat component may not be fully initialized yet, so we can't check it here
141-
if (!this.materialsApplied.has(material)) {
142-
// Check camera filter if specified
143-
if (this.camera && this.camera.camera && this.camera.camera.camera !== camera) {
144-
return;
145-
}
146-
147-
this.applyShaderToMaterial(material);
148-
this.materialsApplied.add(material);
149-
150-
// Store layer info for potential validation later
151-
if (!this._materialLayers) {
152-
this._materialLayers = new Map();
153-
}
154-
this._materialLayers.set(material, layer.id);
155-
}
156-
};
157-
158-
gsplatSystem.on('material:created', this._materialCreatedHandler);
104+
this.material.getShaderChunks(shaderLanguage).delete('gsplatCustomizeVS');
105+
this.material.update();
106+
this.material = null;
159107
}
160108

161109
applyToComponentMaterial() {
162110
const applyShader = () => {
163-
const material = this.entity.gsplat?.material;
164-
if (!material) {
111+
this.material = this.entity.gsplat?.material ?? null;
112+
if (!this.material) {
165113
console.error(`${this.constructor.name}: gsplat material not available.`);
166114
return;
167115
}
168-
this.applyShaderToMaterial(material);
116+
this.applyShaderToMaterial(this.material);
169117
};
170118

171119
if (this.entity.gsplat?.material) {
@@ -177,58 +125,13 @@ class GsplatShaderEffect extends Script {
177125
}
178126

179127
applyToUnifiedMaterials() {
180-
// Try to apply immediately to any existing materials
181-
this.updateUnifiedMaterials();
182-
183-
// If no materials yet, set retry flag (event listener is already set up)
184-
if (this.materialsApplied.size === 0) {
185-
this.needsRetry = true;
186-
}
187-
}
188-
189-
updateUnifiedMaterials() {
190-
// @ts-ignore - gsplat system exists at runtime
191-
const gsplatSystem = this.app.systems.gsplat;
192-
const scene = this.app.scene;
193-
const composition = scene.layers;
194-
195-
// Get all layers this component is on
196-
const componentLayers = this.entity.gsplat?.layers;
197-
if (!componentLayers) return;
198-
199-
// Determine which cameras to target
200-
let targetCameras;
201-
const cam = this.camera?.camera?.camera;
202-
if (cam) {
203-
// Specific camera specified via attribute
204-
targetCameras = [cam];
205-
} else {
206-
// All cameras in the composition
207-
targetCameras = composition.cameras.map(cameraComponent => cameraComponent.camera);
128+
this.material = this.app.scene.gsplat?.material ?? null;
129+
if (!this.material) {
130+
console.warn(`${this.constructor.name}: gsplat template material not available.`);
131+
return;
208132
}
209133

210-
// Iterate through target cameras (already Camera objects, not CameraComponents)
211-
targetCameras.forEach((camera) => {
212-
// For each layer this component is on
213-
componentLayers.forEach((layerId) => {
214-
// Check if this camera renders this layer
215-
if (camera.layers.indexOf(layerId) >= 0) {
216-
const layer = composition.getLayerById(layerId);
217-
if (layer) {
218-
const material = gsplatSystem.getGSplatMaterial(camera, layer);
219-
if (material && !this.materialsApplied.has(material)) {
220-
this.applyShaderToMaterial(material);
221-
this.materialsApplied.add(material);
222-
}
223-
}
224-
}
225-
});
226-
});
227-
228-
if (this.materialsApplied.size > 0) {
229-
this.needsRetry = false;
230-
// Keep event listener active to catch any new materials created later
231-
}
134+
this.applyShaderToMaterial(this.material);
232135
}
233136

234137
applyShaderToMaterial(material) {
@@ -260,30 +163,24 @@ class GsplatShaderEffect extends Script {
260163
this.shadersNeedApplication = false;
261164
}
262165

263-
// Retry applying to unified materials if needed
264-
if (this.entity.gsplat?.unified && this.needsRetry) {
265-
this.updateUnifiedMaterials();
266-
}
267-
268-
if (this.materialsApplied.size === 0) return;
166+
if (!this.material) return;
269167

270168
// Update time
271169
this.effectTime += dt;
272170

273171
// Let subclass update the effect
274172
this.updateEffect(this.effectTime, dt);
173+
174+
// Update material after all parameters have been set (if still valid)
175+
// Note: material may be set to null by removeShaders() if effect disables itself
176+
if (this.material) {
177+
this.material.update();
178+
}
275179
}
276180

277181
destroy() {
278182
// Remove shaders if they're still applied
279183
this.removeShaders();
280-
281-
// Clean up event listener
282-
if (this._materialCreatedHandler) {
283-
// @ts-ignore - gsplat system exists at runtime
284-
this.app.systems.gsplat.off('material:created', this._materialCreatedHandler);
285-
this._materialCreatedHandler = null;
286-
}
287184
}
288185

289186
/**
@@ -307,14 +204,12 @@ class GsplatShaderEffect extends Script {
307204
}
308205

309206
/**
310-
* Set a uniform value on all applied materials.
207+
* Set a uniform value on the material.
311208
* @param {string} name - The uniform name
312209
* @param {*} value - The uniform value
313210
*/
314211
setUniform(name, value) {
315-
this.materialsApplied.forEach((material) => {
316-
material.setParameter(name, value);
317-
});
212+
this.material?.setParameter(name, value);
318213
}
319214

320215
/**

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ class GSplatManager {
434434
this.workBuffer.setOrderData(orderData);
435435

436436
// update renderer with new order data
437-
this.renderer.frameUpdate(this.scene.gsplat);
437+
this.renderer.setOrderData();
438438
}
439439
}
440440

@@ -763,6 +763,9 @@ class GSplatManager {
763763
_splatsNeedingColorUpdate.length = 0;
764764
}
765765

766+
// update renderer with new order data
767+
this.renderer.frameUpdate(this.scene.gsplat);
768+
766769
// Update camera tracking once at the end of the frame
767770
this.updateColorCameraTracking();
768771
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,22 @@ class GSplatRenderer {
192192
this.meshInstance.visible = count > 0;
193193
}
194194

195-
frameUpdate(params) {
196-
195+
setOrderData() {
197196
// Set the appropriate order data resource based on device type
198197
if (this.device.isWebGPU) {
199198
this._material.setParameter('splatOrder', this.workBuffer.orderBuffer);
200199
} else {
201200
this._material.setParameter('splatOrder', this.workBuffer.orderTexture);
202201
}
202+
}
203+
204+
frameUpdate(params) {
203205

204206
// Update colorRampIntensity parameter every frame when overdraw is enabled
205207
if (params.colorRamp) {
206208
this._material.setParameter('colorRampIntensity', params.colorRampIntensity);
207209
}
210+
208211
// Copy material settings from params.material if dirty or on first update
209212
if (this.forceCopyMaterial || params.material.dirty) {
210213
this.copyMaterialSettings(params.material);

0 commit comments

Comments
 (0)