Skip to content

Commit 9652b7b

Browse files
authored
[BREAKING] Update gsplat component material API (#7749)
1 parent 2e19507 commit 9652b7b

File tree

9 files changed

+163
-148
lines changed

9 files changed

+163
-148
lines changed

examples/src/examples/loaders/gsplat-many.example.mjs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,13 @@ assetListLoader.load(() => {
7070
camera.setLocalPosition(-3, 1, 2);
7171

7272
// instantiate guitar with a custom shader
73-
const guitar = assets.guitar.resource.instantiate({
74-
vertex: files['shader.vert']
73+
const guitar = new pc.Entity('guitar');
74+
guitar.addComponent('gsplat', {
75+
asset: assets.guitar
7576
});
76-
guitar.name = 'guitar';
77+
guitar.gsplat.material.getShaderChunks('glsl').set('gsplatVS', files['shader.vert']);
7778
guitar.setLocalPosition(0, 0.8, 0);
79+
guitar.setLocalEulerAngles(0, 0, 180);
7880
guitar.setLocalScale(0.4, 0.4, 0.4);
7981
app.root.addChild(guitar);
8082

@@ -118,25 +120,35 @@ assetListLoader.load(() => {
118120
data.on('shader:set', () => {
119121
// Apply custom or default material options to the splats when the button is clicked. Note
120122
// that this uses non-public API, which is subject to change when a proper API is added.
121-
const materialOptions = {
122-
fragment: files['shader.frag'],
123-
vertex: files['shader.vert']
124-
};
125-
biker1.gsplat.materialOptions = useCustomShader ? materialOptions : null;
126-
biker2.gsplat.materialOptions = useCustomShader ? materialOptions : null;
123+
const vs = files['shader.vert'];
127124

128-
// biker 2 uses a different shader variant
129-
biker2.gsplat.material.setDefine('CUTOUT', true);
125+
const mat1 = biker1.gsplat.material;
126+
if (useCustomShader) {
127+
mat1.getShaderChunks('glsl').set('gsplatVS', vs);
128+
} else {
129+
mat1.getShaderChunks('glsl').delete('gsplatVS');
130+
}
131+
mat1.update();
132+
133+
const mat2 = biker2.gsplat.material;
134+
if (useCustomShader) {
135+
mat2.getShaderChunks('glsl').set('gsplatVS', vs);
136+
} else {
137+
mat2.getShaderChunks('glsl').delete('gsplatVS');
138+
}
139+
mat2.setDefine('CUTOUT', true);
140+
mat2.update();
130141

131142
useCustomShader = !useCustomShader;
132143
});
133144

145+
const uTime = app.graphicsDevice.scope.resolve('uTime');
146+
134147
let currentTime = 0;
135148
app.on('update', (dt) => {
136149
currentTime += dt;
137150

138-
const material = guitar.gsplat?.material;
139-
material?.setParameter('uTime', currentTime);
151+
uTime.setValue(currentTime);
140152

141153
biker2.rotate(0, 80 * dt, 0);
142154
});

src/framework/components/gsplat/component.js

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import { Component } from '../component.js';
99
* @import { Entity } from '../../entity.js'
1010
* @import { EventHandle } from '../../../core/event-handle.js'
1111
* @import { GSplatComponentSystem } from './system.js'
12-
* @import { Material } from '../../../scene/materials/material.js'
13-
* @import { SplatMaterialOptions } from '../../../scene/gsplat/gsplat-material.js'
12+
* @import { ShaderMaterial } from '../../../scene/materials/shader-material.js'
1413
*/
1514

1615
/**
@@ -56,22 +55,22 @@ class GSplatComponent extends Component {
5655
_instance = null;
5756

5857
/**
59-
* @type {BoundingBox|null}
58+
* @type {ShaderMaterial|null}
6059
* @private
6160
*/
62-
_customAabb = null;
61+
_materialTmp = null;
6362

6463
/**
65-
* @type {AssetReference}
64+
* @type {BoundingBox|null}
6665
* @private
6766
*/
68-
_assetReference;
67+
_customAabb = null;
6968

7069
/**
71-
* @type {SplatMaterialOptions|null}
70+
* @type {AssetReference}
7271
* @private
7372
*/
74-
_materialOptions = null;
73+
_assetReference;
7574

7675
/**
7776
* @type {EventHandle|null}
@@ -158,22 +157,16 @@ class GSplatComponent extends Component {
158157

159158
this._instance = value;
160159

161-
if (this._instance?.meshInstance) {
160+
if (this._instance) {
162161

163162
// if mesh instance was created without a node, assign it here
164163
const mi = this._instance.meshInstance;
165164
if (!mi.node) {
166165
mi.node = this.entity;
167166
}
168-
169167
mi.castShadow = this._castShadows;
170168
mi.setCustomAabb(this._customAabb);
171169

172-
// if we have custom shader options, apply them
173-
if (this._materialOptions) {
174-
this._instance.createMaterial(this._materialOptions);
175-
}
176-
177170
if (this.enabled && this.entity.enabled) {
178171
this.addToLayers();
179172
}
@@ -190,26 +183,26 @@ class GSplatComponent extends Component {
190183
return this._instance;
191184
}
192185

193-
set materialOptions(value) {
194-
this._materialOptions = Object.assign({}, value);
195-
196-
// apply them on the instance if it exists
186+
/**
187+
* Sets the material used to render the gsplat.
188+
*
189+
* @param {ShaderMaterial} value - The material instance.
190+
*/
191+
set material(value) {
197192
if (this._instance) {
198-
this._instance.createMaterial(this._materialOptions);
193+
this._instance.material = value;
194+
} else {
195+
this._materialTmp = value;
199196
}
200197
}
201198

202-
get materialOptions() {
203-
return this._materialOptions;
204-
}
205-
206199
/**
207200
* Gets the material used to render the gsplat.
208201
*
209-
* @type {Material|undefined}
202+
* @type {ShaderMaterial|null}
210203
*/
211204
get material() {
212-
return this._instance?.material;
205+
return this._instance?.material ?? this._materialTmp ?? null;
213206
}
214207

215208
/**
@@ -326,18 +319,6 @@ class GSplatComponent extends Component {
326319
return this._assetReference.id;
327320
}
328321

329-
/**
330-
* Assign asset id to the component, without updating the component with the new asset.
331-
* This can be used to assign the asset id to already fully created component.
332-
*
333-
* @param {Asset|number} asset - The gsplat asset or asset id to assign.
334-
* @ignore
335-
*/
336-
assignAsset(asset) {
337-
const id = asset instanceof Asset ? asset.id : asset;
338-
this._assetReference.id = id;
339-
}
340-
341322
/** @private */
342323
destroyInstance() {
343324
if (this._instance) {
@@ -487,7 +468,8 @@ class GSplatComponent extends Component {
487468
// create new instance
488469
const asset = this._assetReference.asset;
489470
if (asset) {
490-
this.instance = new GSplatInstance(asset.resource, this._materialOptions || {});
471+
this.instance = new GSplatInstance(asset.resource, this._materialTmp);
472+
this._materialTmp = null;
491473
this.customAabb = this.instance.resource.aabb.clone();
492474
}
493475
}

src/framework/components/gsplat/system.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ const _schema = [
1616
// order matters here
1717
const _properties = [
1818
'castShadows',
19-
'instance',
2019
'asset',
21-
'layers'
20+
'layers',
21+
'material'
2222
];
2323

2424
/**
@@ -71,22 +71,16 @@ class GSplatComponentSystem extends ComponentSystem {
7171

7272
// copy properties
7373
const data = {};
74-
for (let i = 0; i < _properties.length; i++) {
75-
data[_properties[i]] = gSplatComponent[_properties[i]];
76-
}
74+
_properties.forEach((prop) => {
75+
if (prop !== 'material') {
76+
data[prop] = gSplatComponent[prop];
77+
}
78+
});
7779
data.enabled = gSplatComponent.enabled;
7880

79-
// gsplat instance cannot be used this way, remove it and manually clone it later
80-
delete data.instance;
81-
8281
// clone component
8382
const component = this.addComponent(clone, data);
8483

85-
// clone gsplat instance
86-
if (gSplatComponent.instance) {
87-
component.instance = gSplatComponent.instance.clone();
88-
}
89-
9084
if (gSplatComponent.customAabb) {
9185
component.customAabb = gSplatComponent.customAabb.clone();
9286
}

src/scene/gsplat/gsplat-compressed-resource.js

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@ import { Vec2 } from '../../core/math/vec2.js';
22
import {
33
PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA32U
44
} from '../../platform/graphics/constants.js';
5-
import { createGSplatMaterial } from './gsplat-material.js';
65
import { GSplatResourceBase } from './gsplat-resource-base.js';
76

87
/**
98
* @import { GSplatCompressedData } from './gsplat-compressed-data.js'
109
* @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
1110
* @import { Texture } from '../../platform/graphics/texture.js';
12-
* @import { Material } from '../materials/material.js'
13-
* @import { SplatMaterialOptions } from './gsplat-material.js'
1411
*/
1512

1613
// copy data with padding
@@ -93,24 +90,18 @@ class GSplatCompressedResource extends GSplatResourceBase {
9390
this.shTexture2?.destroy();
9491
}
9592

96-
/**
97-
* @param {SplatMaterialOptions} options - The splat material options.
98-
* @returns {Material} material - The material to set up for the splat rendering.
99-
*/
100-
createMaterial(options) {
101-
const result = createGSplatMaterial(this.device, options);
102-
result.setDefine('GSPLAT_COMPRESSED_DATA', true);
103-
result.setParameter('packedTexture', this.packedTexture);
104-
result.setParameter('chunkTexture', this.chunkTexture);
93+
configureMaterial(material) {
94+
material.setDefine('GSPLAT_COMPRESSED_DATA', true);
95+
material.setParameter('packedTexture', this.packedTexture);
96+
material.setParameter('chunkTexture', this.chunkTexture);
10597
if (this.shTexture0) {
106-
result.setDefine('SH_BANDS', 3);
107-
result.setParameter('shTexture0', this.shTexture0);
108-
result.setParameter('shTexture1', this.shTexture1);
109-
result.setParameter('shTexture2', this.shTexture2);
98+
material.setDefine('SH_BANDS', 3);
99+
material.setParameter('shTexture0', this.shTexture0);
100+
material.setParameter('shTexture1', this.shTexture1);
101+
material.setParameter('shTexture2', this.shTexture2);
110102
} else {
111-
result.setDefine('SH_BANDS', 0);
103+
material.setDefine('SH_BANDS', 0);
112104
}
113-
return result;
114105
}
115106

116107
/**

0 commit comments

Comments
 (0)