Skip to main content

Writing a custom ShaderMaterial

Writing custom materials using ShaderMaterials and RawShaderMaterials 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