Writing a custom ShaderMaterial
Writing custom materials using ShaderMaterial
s and RawShaderMaterial
s is possible in webgi, and should work similarly as it does in three.js.
Most of the boilerplate is included in the abstract class AShaderMaterial2
, which takes parameters similar to ShaderMaterial
in three.js. The second parameter in the constructor of AShaderMaterial2
can be set to true
to make it a RawShaderMaterial
.
Writing the shader material class
Here is a sample material in typescript that has 2 uniforms for color and color intensity. This class implements toJSON
, fromJSON
, copyProps
and uiConfig
which are present to interface with webgi framework.
export interface SampleShaderMaterialParameters{
color?: ColorRepresentation
intensity?: number
}
/**
* A sample boilerplate for wrapping a shader material from three.js.
*/
export class SampleShaderMaterial extends AShaderMaterial2 {
public static readonly TypeSlug = 'mymat'
public readonly typeSlug = SampleShaderMaterial.TypeSlug
public static readonly TYPE = 'SampleShaderMaterial'
constructor(parameters?: SampleShaderMaterialParameters) {
super({
uniforms: {
intensity: {value: 0},
color: {value: new Color()},
},
vertexShader: glsl`
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`,
fragmentShader: glsl`
uniform vec3 color;
uniform float intensity;
void main() {
gl_FragColor = vec4( color * intensity, 1.0 );
// This include is required to make the material work with RGBM pipeline.
#include <encodings_fragment>
}
`,
})
if (parameters) this.setParameters(parameters)
}
setParameters(parameters: SampleShaderMaterialParameters) {
if (parameters.intensity !== undefined) this.uniforms.intensity.value = parameters.intensity
if (parameters.color !== undefined) this.uniforms.color.value.set(parameters.color)
this.uniformsNeedUpdate = true
this.setDirty()
}
toJSON(meta?: any): any {
return {
type: SampleShaderMaterial.TYPE,
intensity: this.uniforms.intensity.value,
color: this.uniforms.color.value,
}
}
fromJSON(data: any, meta?: any): this | null {
this.setParameters(data)
return this
}
copyProps(oldMaterial: any): this {
if (oldMaterial.typeSlug !== this.typeSlug) {
console.warn('Cannot copy from incompatible material')
return this
}
const data = oldMaterial.toJSON()
this.fromJSON(data)
return this
}
uiConfig: UiObjectConfig = {
type: 'folder',
label: 'SampleShaderMaterial',
children: [
{
type: 'color',
label: 'color',
getValue: () => this.uniforms.color?.value.getHex() ?? 0,
setValue: (v) => {
this.uniforms.color.value.set(v)
this.uniformsNeedUpdate = true
this.setDirty()
},
},
{
type: 'slider',
bounds: [0, 10],
label: 'intensity',
getValue: () => this.uniforms.intensity?.value ?? 0,
setValue: (v) => {
this.uniforms.intensity.value = v
this.uniformsNeedUpdate = true
this.setDirty()
},
},
],
}
}
Now the material can be instantiated normally and used in the scene. If any UI plugin like TweakpaneUiPlugin
is available, the material properties can also be edited from the UI.
Creating a material template
To make it easy to generate the material, we can register it as a template in the material manager, and later create it using viewer.createMaterial
const manager = viewer.getPlugin(AssetManagerPlugin)!
manager?.materials?.registerMaterialTemplate({
name: 'sample',
materialType: SampleShaderMaterial.TYPE,
generator: (params: any, oldMaterial?: any) => {
const mat = new SampleShaderMaterial(params)
if (oldMaterial) mat.copyProps(oldMaterial)
return mat
},
})
Using the material in a scene
// Create an object in scene and add a sphere with the sample material.
const obj = await viewer.createObject3D()
obj!.modelObject.name = 'sphere'
const model = new Mesh(
new SphereGeometry(1, 32, 32),
viewer.createMaterial('sample')?.materialObject
)
model.material.setParameters({color: 0xff0000, intensity: 0.1})
obj!.modelObject.add(model)
Serialization in GLTF and draco.
Coming Soon