Skip to main content

Shaders cookbook

These snippets show the smallest useful descriptor or assembly action. Add both WGSL and GLSL source when the feature must run on both backends.

GoalUseResult
Package reusable shader behaviorShaderModuleSource plus typed configuration
Convert props to shader valuesuniformTypes and getUniforms()Uniforms and bindings for ShaderInputs
Declare an extension pointaddShaderInjectionTarget()A stable callable target
Extend a shaderinjectOrdered code at a supported target
Share lower-level behaviordependenciesDe-duplicated module ordering
Package optional behaviorShaderPluginModules, inputs, defines, and injections
Define an image operationShaderPassA reusable fullscreen stage

Author a module​

const tint = {
name: 'tint',
source: 'struct Tint { color: vec4f }; @group(0) @binding(auto) var<uniform> tint: Tint;',
uniformTypes: {color: 'vec4<f32>'}
} as const satisfies ShaderModule<{color: [number, number, number, number]}>;

Keep the name stable and supply a GLSL path too when WebGL 2 is supported.

Expose props and bindings​

const exposure = {
name: 'exposure',
uniformTypes: {value: 'f32'},
getUniforms: ({stops = 0}) => ({value: 2 ** stops})
} as const satisfies ShaderModule<{stops?: number}, {value: number}>;

ShaderInputs.setProps() calls getUniforms() and separates ordinary values from resource bindings.

Add a callable target​

import {WGSLShaderAssembler} from '@luma.gl/shaders/wgsl';

const assembler = new WGSLShaderAssembler();
assembler.addShaderInjectionTarget({name: 'FILTER_COLOR', stage: 'fragment', wgsl: {parameters: 'color: ptr<function, vec4<f32>>'}});

Pass this assembler to the model through its shaderAssembler prop. The application shader calls FILTER_COLOR(&color). With no contributors, the generated target is a no-op.

Inject source​

import type {ShaderPlugin} from '@luma.gl/shaders';

const fade: ShaderPlugin = {
name: 'fade',
injections: [{stage: 'fragment', target: 'FILTER_COLOR', wgsl: '(*color).a *= 0.5;'}]
};

Prefer callable targets for supported extension contracts; use built-in targets for declarations and small entry-point statements.

Compose dependencies​

const lighting = {
name: 'lighting',
dependencies: [math],
source: 'fn shade(color: vec3f) -> vec3f { return saturate(color); }'
};

Pass only direct dependencies. The assembler orders and de-duplicates the transitive graph.

Create a plugin​

const tintPlugin: ShaderPlugin = {
name: 'tint-plugin',
module: tint,
injections: [{stage: 'fragment', target: '#main-end', glsl: 'color *= tint.color;', wgsl: 'color *= tint.color;'}]
};

Attach it with new Model(device, {...props, plugins: [tintPlugin]}).

Author a pass​

const vignette = {
name: 'vignette',
source: vignetteWGSL,
fs: vignetteGLSL,
uniformTypes: {amount: 'f32'},
passes: [{sampler: true}]
} as const satisfies ShaderPass<{amount: number}>;

Execute it with Engine’s ShaderPassRenderer. Each subpass is a draw and may need an intermediate texture.