shadertools is a GLSL shader module system built around a GLSL "assembler" that allows you build modular shaders. It addresses the lack of a module/import system in the GLSL language and allows you to import chunks of reusable shader code from modules into your shader source code, and organize your shader code in reusable modules.
To add/inject existing modules into your shaders, just add the modules parameter to your assembleShaders
call:
import {shaderModule} from 'library-of-shader-modules';
const {vs, fs, getUniforms, moduleMap} = assembleShaders(gl, {
fs: '...',
vs: '...',
modules: [shaderModule],
...
})
To create a new shader module, you need to create a descriptor object.
const MY_SHADER_MODULE = {
name: 'my-shader-module',
vs: ....
fs: null,
inject: {},
dependencies: [],
deprecations: [],
getUniforms
};
This object can be used as shader module directly:
assembleShaders(gl, {..., modules: [MY_SHADER_MODULE]});
A shader module is either:
fp64
module is a good example of this type of module.varying
used by the fragment shader part.To define a new shader module, you create a descriptor object that brings together all the necessary pieces:
export const MY_SHADER_MODULE = {
name: 'my-shader-module',
vs: '...',
fs: '...',
inject: {},
dependencies: [],
deprecations: [],
getUniforms
};
Descriptor objects can define the following fields:
name
(String, Required) - The name of the shader module.vs
- (String | null)fs
- (String | null)getUniforms
JavaScript function that maps JavaScript parameter keys to uniforms used by this moduleuniforms
(Object) - a light alternative to getUniforms
, see belowinject
(Object) - injections the module will make into shader hooks, see belowdependencies
(Array) - a list of other shader modules that this module is dependent ondeprecations
(Array) - a list of deprecated APIs.If deprecations
is supplied, assembleShaders
will scan GLSL source code for the deprecated constructs and issue a console warning if found. Each API is described in the following format:
type
: uniform <type>
or function
old
: name of the deprecated uniform/functionnew
: name of the new uniform/functiondeprecated
: whether the old API is still supported.The GLSL code for a shader module typically contains:
Each shader module provides a method to get a map of uniforms for the shader. This function will be called with two arguments:
opts
- the module settings to update. This argument may not be provided when getUniforms
is called to generate a set of default uniform values.context
- the uniforms generated by this module's dependencies.The function should return a JavaScript object with keys representing uniform names and values representing uniform values.
The function should expect the shape of the dependency uniforms to vary based on what's passed in opts
. This behavior is intended because we only want to recalculate a uniform if the uniforms that it depends on are changed. An example is the project
and project64
modules in deck.gl. When opts.viewport
is provided, project64
will receive the updated projection matrix generated by the project
module. If opts.viewport
is empty, then the project
module generates nothing and so should project64
.
If the uniforms of this module can be directly pulled from user settings, they may declaratively defined by a uniforms
object:
{
name: 'my-shader-module',
uniforms: {
strength: {type: 'number', value: 1, min: 0, max: 1},
center: [0.5, 0.5]
}
}
At runtime, this map will be used to generate the uniforms needed by the shaders. If either strength
or center
is present in the user's module settings, then the user's value will be used; otherwise, the default value in the original definition will be used.
Each uniform definition may contain the following fields:
type
(String) - one of number
, boolean
, array
or object
value
- the default value of this uniformWith type: 'number'
, the following additional fields may be added for validation:
min
(Number)max
(Number)Note: uniforms
is ignored if getUniforms
is provided.
A map of hook function signatures to either the injection code string, or an object containing the injection code and an order
option indicating ordering within the hook function. See assembleShaders documentation for more information on shader hooks.
For example:
{
picking: {
'vs:VERTEX_HOOK_FUNCTION': 'picking_setPickingColor(color.rgb);',
'fs:FRAGMENT_HOOK_FUNCTION': {
injection: 'color = picking_filterColor(color);',
order: Number.POSITIVE_INFINITY
},
'fs:#main-end': 'gl_FragColor = picking_filterColor(gl_FragColor);'
}
}
Shader modules will undergo some basic text transformations in order to match the GLSL version of the shaders they are injected into. These transformations are generally limited to the naming of input variables, output variables and texture sampling functions. See assembleShaders documentation for more information.