luma.gl

Shadertools API Reference

Functions

registerShaderModules

Can be used to “name” shader modules, making them available to assembleShaders using module names rather than the module definitions.

Note: Can defeat three-shaking of unused shader modules (affects size of application JavaScript bundle).

getModuleUniforms

Takes a list of shader module names and an object with options, and creates a combined uniform object that contains all necessary uniforms for all the modules injected into your shader.

assembleShaders

Takes the source code of a vertex shader and a fragment shader, and a list of modules, defines, etc. Outputs resolved source code for both shaders, after adding prologue, adding defines, importing and transpiling modules, and injecting any shader fragments).

Returns:

createShaderHook(hook, [opts])

Creates a shader hook function that shader modules can injection code into. Shaders can call these functions, which will be no-ops by default. If a shader module injects code it will be executed upon the hook function call. This mechanism allows the application to create shaders that can be automatically extended by included shader modules.

createModuleInjection(moduleName, opts)

Define a code injection for a particular hook function (defined by createShaderHook) and shader module. The injection code will be inserted into the hook function whenever the shader module is included.

Constants and Values

Predefined Injection Hooks

Key Shader Description
vs:#decl Vertex Inject at top of shader (declarations)
vs:#main-start Vertex Injected at the very beginning of main function
vs:#main-end Vertex Injected at the very end of main function
fs:#decl Fragment Inject at top of shader (declarations)
fs:#main-start Fragment Injected at the very beginning of main function
fs:#main-end Fragment Injected at the very end of main function

Usage

Shader Module Code Injection

Shader module code injections involve three steps:

For example, if the application wanted to automatically enable picking color filtering when the picking module is included in a program, first the shader hook would be defined:

createShaderHook('fs:MYHOOK_fragmentColor(inout vec4 color)');

In the fragment shader main function, the new hook function would called as follows:

void main() {
  //...
  MYHOOK_fragmentColor(gl_FragColor)
}

And the injection for the picking module would be defined as follows:

createModuleInjection('picking', {
  hook: 'fs:MYHOOK_fragmentColor',
  injection: 'color = picking_filterColor(color);',
  order: Number.POSITIVE_INFINITY
});

If the picking module were included, the function MYHOOK_fragmentColor would be updated to modify the input color. Without the picking module, the function would remain a no-op. The priority ensures the injection always appears last in the hook function, which is necessary for picking color filtering to work correctly.

Injecting to a predefined hook would be done as follows:

createModuleInjection('picking', {
  hook: 'fs:#main-end',
  injection: 'gl_FragColor = picking_filterColor(gl_FragColor);',
  order: Number.POSITIVE_INFINITY
});

Injection Map

assembleShaders (and Model constructor) will take a new inject argument that contains a map of:

Examples:

  inject: {
    'fs:#main-end': '  gl_FragColor = picking_filterColor(gl_FragColor)'
  }
createShaderHook('fs:MYHOOK_fragmentColor(inout vec4 color)');

new Model(gl, {
  vs,
  fs: `void main() {
    MYHOOK_fragmentColor(gl_FragColor);
  }`,
  modules: [picking]
  inject: {
    'fs:#main-start': 'gl_FragColor = vec4(1., 0., 0., 1.);';
    'fs:MYHOOK_fragmentColor': {
      injection: '  color = picking_filterColor(color);',
      order: 9999
  }
});

Remarks