luma.gl/docs/api-reference/shadertools/shader-assembler.md

195 lines
8.8 KiB
Markdown

# ShaderAssembler
The functionality of the `shadertools` module shader system is primarily exposed
via the `ShaderAssembler` class.
`shaderAssebler.assembleShaders()` composes base vertex and fragment shader source with
- shader modules
- hook functions
- and injections
to generate the final vertex and fragment shader source that can be used to create a program.
## Static Methods
### `getDefaultShaderAssembler()`
Most applications that register default modules and hooks will want to use a single `Shader`
## Methods
### `addDefaultModule(module: ShaderModule)`
Add a module that will automatically be added to any programs created by the program manager.
### `removeDefaultModule(module: ShaderModule)`
Remove a module that is automatically being added to programs created by the program manager.
### `addShaderHook(hook: string, [opts: Object])`
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.
- `hook`: `vs:` or `fs:` followed by the name and arguments of the function, e.g. `vs:MYHOOK_func(inout vec4 value)`. Hook name without arguments
will also be used as the name of the shader hook
- `opts.header` (optional): code always included at the beginning of a hook function
- `opts.footer` (optional): code always included at the end of a hook function
### `assembleShaders`
`ahaderAssebler.assembleShaders()` composes base vertex and fragment shader source with shader modules,
hook functions and injections to generate the final vertex and
fragment shader source that can be used to create a program.
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).
- `vs` - vertex shader source
- `fs` - fragment shader source code
- `id` - `id` for the shader, will be used to inject shader names (using `#define SHADER_NAME`) if not already present in the source.
- `prologue`=`true` (Boolean) - Will inject platform prologue (see below)
- `defines`=`{}` (Object) - a map of key/value pairs representing custom `#define`s to be injected into the shader source
- `modules`=`[]` (Array) - list of shader modules (either objects defining the module, or names of previously registered modules)
- `inject`=`{}` (Object) - map of substituions,
- `hookFunctions`=`[]` Array of hook functions descriptions. Descriptions can simply be the hook function signature (with a prefix `vs` for vertex shader, or `fs` for fragment shader) or an object with the hook signature, and a header and footer that will always appear in the hook function. For example:
- `transpileToGLSL100`: force transpilation to GLSL ES 1.0 (see below)
```typescript
[
'vs:MY_HOOK_FUNCTION1(inout vec4 color)',
{
hook: 'fs:MY_HOOK_FUNCTION2(inout vec4 color)',
header: 'if (color.a == 0.0) discard;\n',
footer: 'color.a *= 1.2;\n'
}
];
```
Returns:
- `vs` - the resolved vertex shader
- `fs` - the resolved fragment shader
- `getUniforms` - a combined `getUniforms` function covering all modules.
- `moduleMap` - a map with all resolved modules, keyed by name
## Shader Module Assembly
luma.gl's module shader system is primarily exposed via the function `assembleShaders` which composes base vertex and fragment shader source with shader modules, hook functions and injections to generate the final vertex and fragment shader source that can be used to create a program.
## Shader Hooks and Module Injections
Shader hooks and module injections are a system that allows for shader to be written in a generic manner, with behaviour modified when modules are included. For example if we define a shader hook as `fs:MY_HOOK_FUNCTION(inout vec4 color)`, `assembleShader` will inject the following function automatically into our fragment shader:
```c
void MY_HOOK_FUNCTION(inout vec4 color) {
}
```
We can the write our fragment shader as follows:
```c
precision highp float;
void main() {
vec4 color = vec4(1.0);
gl_FragColor = MY_HOOK_FUNCTION(color)
}
```
By default, the hook function is a no-op, so this doesn't do anything. However, if we add a module injection like the following:
```typescript
{
picking: {
'fs:VERTEX_HOOK_FUNCTION': 'color = vec4(1.0, 0.0, 0.0, 1.0);'
}
}
```
And pass the `picking` module to `assembledShaders`, the hook function will be updated as follows:
```c
void MY_HOOK_FUNCTION(inout vec4 color) {
color = vec4(1.0, 0.0, 0.0, 1.0);
}
```
The hook function now changes the color from white to red.
## 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 |
**NOTE**: Injections assume that the `main` function appears last in a shader.
## Usage
### Injection Map
`assembleShaders` (and `Model` constructor) will take an `inject` argument that contains a map of:
- keys indicating hooks (predefined or functions)
- values representing code to be injected. This can be either a simple string or an object containing the `injection` string and an `order` indicating its priority.
Examples:
```
inject: {
'fs:#main-end': ' gl_FragColor = picking_filterColor(gl_FragColor)'
}
```
```typescript
ProgramManager.getDefaultProgramManager(gl).addShaderHook('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
}
});
```
## Transpilation
If the `transpileToGLSL100` option is used, `assembleShaders` will attempt to transpile shaders to GLSL ES 1.0. This is a limited text replacement and requires that certain conventions be followed:
- Statements are written one per line.
- Only one fragment shader output is supported.
- GLSL 3.0-only features, such as 3D textures are not supported.
Text transformations are performed according to the following tables:
Vertex Shaders
| 3.00 ES | 1.00 ES | Comment |
| ------- | ----------- | ------- |
| `in` | `attribute` | |
| `out` | `varying` | |
Fragment Shaders
| 3.00 ES | 1.00 ES | Comment |
| -------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `in` | `varying` | |
| `out vec4 <varName>` | `gl_FragColor` | `<varName>` declaration is removed and usage in the code are replaced with `gl_FragColor` |
| `texture` | `texture2D` | `texture` will be replaced with `texture2D` to ensure 1.00 code is correct. See note on `textureCube` below. |
| `textureCube` \* | `textureCube` | `textureCube` is not valid 3.00 syntax, but must be used to ensure 1.00 code is correct, because `texture` will be substituted with `texture2D` when transpiled to 100. Also `textureCube` will be replaced with correct `texture` syntax when transpiled to 300. |