shaderpad 1.0.0-beta.77 → 1.0.0-beta.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +887 -0
- package/dist/chunk-O3TAD633.mjs +2 -0
- package/dist/chunk-O3TAD633.mjs.map +1 -0
- package/dist/chunk-PZ4UVAHU.mjs +10 -0
- package/dist/chunk-PZ4UVAHU.mjs.map +1 -0
- package/dist/dev/{chunk-KRIFZAFR.mjs → chunk-QYD24S7K.mjs} +4 -7
- package/dist/dev/chunk-QYD24S7K.mjs.map +1 -0
- package/dist/dev/chunk-YN3AO6HP.mjs +195 -0
- package/dist/dev/chunk-YN3AO6HP.mjs.map +1 -0
- package/dist/dev/index.js +3 -6
- package/dist/dev/index.js.map +1 -1
- package/dist/dev/index.mjs +1 -1
- package/dist/dev/plugins/pose.js +3 -6
- package/dist/dev/plugins/pose.js.map +1 -1
- package/dist/dev/plugins/pose.mjs +1 -1
- package/dist/dev/plugins/segmenter.js +3 -6
- package/dist/dev/plugins/segmenter.js.map +1 -1
- package/dist/dev/plugins/segmenter.mjs +1 -1
- package/dist/dev/react.js +474 -190
- package/dist/dev/react.js.map +1 -1
- package/dist/dev/react.mjs +303 -186
- package/dist/dev/react.mjs.map +1 -1
- package/dist/dev/web-component.js +1922 -0
- package/dist/dev/web-component.js.map +1 -0
- package/dist/dev/web-component.mjs +499 -0
- package/dist/dev/web-component.mjs.map +1 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/plugins/pose.js +8 -8
- package/dist/plugins/pose.js.map +1 -1
- package/dist/plugins/pose.mjs +1 -1
- package/dist/plugins/segmenter.js +2 -2
- package/dist/plugins/segmenter.js.map +1 -1
- package/dist/plugins/segmenter.mjs +1 -1
- package/dist/react.d.mts +19 -8
- package/dist/react.d.ts +19 -8
- package/dist/react.js +4 -5
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +1 -2
- package/dist/react.mjs.map +1 -1
- package/dist/web-component.css +8 -0
- package/dist/web-component.d.mts +119 -0
- package/dist/web-component.d.ts +119 -0
- package/dist/web-component.js +11 -0
- package/dist/web-component.js.map +1 -0
- package/dist/web-component.mjs +3 -0
- package/dist/web-component.mjs.map +1 -0
- package/package.json +19 -2
- package/dist/chunk-NWUH6TTN.mjs +0 -10
- package/dist/chunk-NWUH6TTN.mjs.map +0 -1
- package/dist/dev/chunk-KRIFZAFR.mjs.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
**Minimal setup. Tiny footprint.**
|
|
4
|
+
|
|
5
|
+
ShaderPad is a lightweight, dependency-free library that reduces boilerplate when working with fragment shaders. It provides a simple interface for setup, texture and uniform management, and user interactions such as mouse movements and window resizing. It comes “batteries included” for common needs, with optional plugins — from PNG export to face/pose tracking — for more advanced use cases. Simple, performant, and portable, ShaderPad lets you focus on the fun part: writing GLSL.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
To install the library into an existing project:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install shaderpad
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Starters
|
|
16
|
+
|
|
17
|
+
To scaffold a local fullscreen starter app:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm create shaderpad@latest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The CLI will walk you through the available starters interactively.
|
|
24
|
+
|
|
25
|
+
To try the same templates online on StackBlitz:
|
|
26
|
+
|
|
27
|
+
- [Open the basic starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-basic-ts?title=ShaderPad%20Basic%20shader%20%28TypeScript%29)
|
|
28
|
+
- [Open the LYGIA starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-lygia-ts?title=ShaderPad%20LYGIA%20%28TypeScript%29)
|
|
29
|
+
- [Open the face tracking starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-face-ts?title=ShaderPad%20with%20face%20tracking%20%28TypeScript%29)
|
|
30
|
+
- [Open the pose tracking starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-pose-ts?title=ShaderPad%20with%20pose%20tracking%20%28TypeScript%29)
|
|
31
|
+
- [Open the hand tracking starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-hands-ts?title=ShaderPad%20with%20hand%20tracking%20%28TypeScript%29)
|
|
32
|
+
- [Open the segmentation starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-segmenter-ts?title=ShaderPad%20with%20segmentation%20%28TypeScript%29)
|
|
33
|
+
|
|
34
|
+
## Links
|
|
35
|
+
|
|
36
|
+
### For humans:
|
|
37
|
+
|
|
38
|
+
- [Docs](https://misery.co/shaderpad/)
|
|
39
|
+
- [Quickstart](https://misery.co/shaderpad/docs/getting-started/quickstart/)
|
|
40
|
+
- [Browse interactive examples](https://misery.co/shaderpad/examples/)
|
|
41
|
+
|
|
42
|
+
### For AIs and coding agents:
|
|
43
|
+
|
|
44
|
+
- [AI agent guide](https://misery.co/shaderpad/docs/getting-started/ai-agent-guide/)
|
|
45
|
+
- [llms.txt](https://misery.co/shaderpad/llms.txt)
|
|
46
|
+
- [Examples on GitHub](https://github.com/miseryco/shaderpad/tree/main/docs/src/examples/demos)
|
|
47
|
+
|
|
48
|
+
## Example
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import ShaderPad from 'shaderpad';
|
|
52
|
+
|
|
53
|
+
const fragmentShaderSrc = `#version 300 es
|
|
54
|
+
precision highp float;
|
|
55
|
+
|
|
56
|
+
// Built-in variables.
|
|
57
|
+
in vec2 v_uv;
|
|
58
|
+
uniform float u_time;
|
|
59
|
+
uniform vec2 u_resolution;
|
|
60
|
+
uniform vec2 u_cursor;
|
|
61
|
+
|
|
62
|
+
// Custom variables.
|
|
63
|
+
uniform vec3 u_cursorColor;
|
|
64
|
+
|
|
65
|
+
out vec4 outColor;
|
|
66
|
+
|
|
67
|
+
void main() {
|
|
68
|
+
vec2 uv = v_uv * u_resolution;
|
|
69
|
+
vec2 dotGrid = mod(uv, 50.) - 25.;
|
|
70
|
+
float dotDist = length(dotGrid);
|
|
71
|
+
float dot = step(dotDist, 5.);
|
|
72
|
+
float cursorDist = distance(uv, u_cursor * u_resolution);
|
|
73
|
+
float cursor = step(cursorDist, 25. + sin(u_time * 5.) * 5.);
|
|
74
|
+
vec3 color = mix(vec3(0., 0., 1.), vec3(1.), dot);
|
|
75
|
+
color = mix(color, u_cursorColor, cursor);
|
|
76
|
+
outColor = vec4(color, 1.);
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
const shader = new ShaderPad(fragmentShaderSrc /* , options */);
|
|
81
|
+
const getColor = (time: number) =>
|
|
82
|
+
[time, time + (Math.PI * 2) / 3, time + (Math.PI * 4) / 3].map(x => 1 + Math.sin(x) / 2);
|
|
83
|
+
shader.initializeUniform('u_cursorColor', 'float', getColor(0));
|
|
84
|
+
shader.play(time => {
|
|
85
|
+
shader.updateUniforms({ u_cursorColor: getColor(time) });
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
See the [interactive examples](https://misery.co/shaderpad/examples/) or browse the source in [`docs/src/examples/demos/`](./docs/src/examples/demos).
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
### Uniforms
|
|
94
|
+
|
|
95
|
+
#### `initializeUniform(name, type, value, options?)`
|
|
96
|
+
|
|
97
|
+
Initialize a uniform, which must be declared in your shader.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
shader.initializeUniform('u_speed', 'float', 1.5);
|
|
101
|
+
// Vectors are passed as arrays. This is a vec3:
|
|
102
|
+
shader.initializeUniform('u_color', 'float', [1.0, 0.5, 0.0]);
|
|
103
|
+
// …but you can also pass an array. This is an array of floats:
|
|
104
|
+
shader.initializeUniform('u_data', 'float', [1.0, 0.5, 0.0], { arrayLength: 3 });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Parameters:**
|
|
108
|
+
|
|
109
|
+
- `name` (string): Uniform name
|
|
110
|
+
- `type` ('float' | 'int' | 'uint'): Uniform type
|
|
111
|
+
- `value` (number | number[] | (number | number[])[]): Initial value(s)
|
|
112
|
+
- `options` (optional): `{ arrayLength?: number }` - Required for arrays
|
|
113
|
+
|
|
114
|
+
#### `updateUniforms(updates, options?)`
|
|
115
|
+
|
|
116
|
+
Update uniform values.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
shader.updateUniforms({
|
|
120
|
+
u_speed: 2.0,
|
|
121
|
+
u_color: [1.0, 0.0, 0.0],
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Parameters:**
|
|
126
|
+
|
|
127
|
+
- `updates`: Object mapping uniform names to their new values
|
|
128
|
+
- `options` (optional): `{ startIndex?: number }` - Starting index for partial array updates
|
|
129
|
+
|
|
130
|
+
#### Uniform arrays
|
|
131
|
+
|
|
132
|
+
ShaderPad supports uniform arrays. Initialize them by passing an `arrayLength` option:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Initialize a vec2 array with 5 elements.
|
|
136
|
+
shader.initializeUniform(
|
|
137
|
+
'u_positions',
|
|
138
|
+
'float',
|
|
139
|
+
[
|
|
140
|
+
[0, 0],
|
|
141
|
+
[1, 1],
|
|
142
|
+
[2, 2],
|
|
143
|
+
],
|
|
144
|
+
{ arrayLength: 3 },
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Update all elements.
|
|
148
|
+
shader.updateUniforms({
|
|
149
|
+
u_positions: [
|
|
150
|
+
[0.1, 0.2],
|
|
151
|
+
[1.1, 1.2],
|
|
152
|
+
[2.1, 2.2],
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Update a subset starting at index 2.
|
|
157
|
+
shader.updateUniforms(
|
|
158
|
+
{
|
|
159
|
+
u_positions: [
|
|
160
|
+
[2.5, 2.5],
|
|
161
|
+
[3.5, 3.5],
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{ startIndex: 2 },
|
|
165
|
+
);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Included uniforms
|
|
169
|
+
|
|
170
|
+
| Uniform | Type | Description |
|
|
171
|
+
| ---------------------- | -------------- | --------------------------------------------------------------------------------- |
|
|
172
|
+
| `u_time` | float | The current time in seconds. |
|
|
173
|
+
| `u_frame` | int | The current frame number. |
|
|
174
|
+
| `u_resolution` | float[2] | The canvas element's dimensions. |
|
|
175
|
+
| `u_cursor` | float[2] | Cursor position (x, y), normalized to [0,1] over the `cursorTarget` bounding box. |
|
|
176
|
+
| `u_click` | float[3] | Click position (x, y) and left click state (z). |
|
|
177
|
+
| `u_history` | sampler2DArray | Buffer texture of prior frames. Available if `history` option is set. |
|
|
178
|
+
| `u_historyFrameOffset` | int | Frame offset for accessing history texture. Available if `history` option is set. |
|
|
179
|
+
|
|
180
|
+
#### Included varyings
|
|
181
|
+
|
|
182
|
+
| Varying | Type | Description |
|
|
183
|
+
| ------- | -------- | ----------------------------------- |
|
|
184
|
+
| `v_uv` | float[2] | The UV coordinates of the fragment. |
|
|
185
|
+
|
|
186
|
+
### Textures
|
|
187
|
+
|
|
188
|
+
#### `initializeTexture(name, source, options?)`
|
|
189
|
+
|
|
190
|
+
Initialize a texture from an image, video, canvas, or typed array.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
shader.initializeTexture('u_texture', img);
|
|
194
|
+
shader.initializeTexture(
|
|
195
|
+
'u_custom',
|
|
196
|
+
{ data: new Float32Array(width * height * 4), width, height },
|
|
197
|
+
{
|
|
198
|
+
internalFormat: 'RGBA32F',
|
|
199
|
+
type: 'FLOAT',
|
|
200
|
+
minFilter: 'NEAREST',
|
|
201
|
+
magFilter: 'NEAREST',
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
shader.initializeTexture('u_webcam', videoElement, { history: 30 });
|
|
205
|
+
shader.initializeTexture('u_canvas', canvasElement, { preserveY: true });
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Parameters:**
|
|
209
|
+
|
|
210
|
+
- `name` (string): The name of the texture uniform as declared in your shader
|
|
211
|
+
- `options` (optional): Texture options (see below)
|
|
212
|
+
- `source`: Image, video, canvas, or `{ data, width, height }`
|
|
213
|
+
|
|
214
|
+
**Texture Options:**
|
|
215
|
+
|
|
216
|
+
- `history?: number` - Number of previous frames to store (creates a `sampler2DArray`)
|
|
217
|
+
- `preserveY?: boolean` - For DOM sources only: if `true`, don't flip vertically (default: `false`, flips to match WebGL's bottom-up convention)
|
|
218
|
+
- `internalFormat?: string` - Storage format in GPU memory (e.g., `'RGBA8'`, `'RGBA32F'`, `'R8'`). Defaults to `'RGBA8'` for 8-bit, or `'RGBA32F'` if `type` is `'FLOAT'`
|
|
219
|
+
- `format?: string` - Source data layout (default: `'RGBA'`). Describes the channels in your input data (e.g., `'RGBA'`, `'RGB'`, `'RED'`)
|
|
220
|
+
- `type?: string` - Source data type (default: `'UNSIGNED_BYTE'` for DOM sources, must be specified for typed arrays). Examples: `'UNSIGNED_BYTE'`, `'FLOAT'`, `'HALF_FLOAT'`
|
|
221
|
+
- `minFilter?: string` - Minification filter (default: `'LINEAR'`). Examples: `'LINEAR'`, `'NEAREST'`
|
|
222
|
+
- `magFilter?: string` - Magnification filter (default: `'LINEAR'`). Examples: `'LINEAR'`, `'NEAREST'`
|
|
223
|
+
- `wrapS?: string` - Wrap mode for S coordinate (default: `'CLAMP_TO_EDGE'`). Examples: `'CLAMP_TO_EDGE'`, `'REPEAT'`, `'MIRRORED_REPEAT'`
|
|
224
|
+
- `wrapT?: string` - Wrap mode for T coordinate (default: `'CLAMP_TO_EDGE'`). Examples: `'CLAMP_TO_EDGE'`, `'REPEAT'`, `'MIRRORED_REPEAT'`
|
|
225
|
+
|
|
226
|
+
**Note:** For typed array sources (`CustomTexture`), you must provide data in bottom-up orientation (WebGL convention). The `preserveY` option is ignored for typed arrays.
|
|
227
|
+
|
|
228
|
+
#### `updateTextures(updates)`
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
shader.updateTextures({ u_webcam: videoElement, u_overlay: overlayCanvas });
|
|
232
|
+
shader.updateTextures({
|
|
233
|
+
u_custom: { data: partialData, width, height, isPartial: true, x: offsetX, y: offsetY },
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Parameters:**
|
|
238
|
+
|
|
239
|
+
- `updates`: Object mapping texture names to updated sources
|
|
240
|
+
|
|
241
|
+
### Lifecycle methods
|
|
242
|
+
|
|
243
|
+
#### `play(onBeforeStep?)`
|
|
244
|
+
|
|
245
|
+
Start the render loop.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
shader.play();
|
|
249
|
+
|
|
250
|
+
// With per-frame callbacks.
|
|
251
|
+
shader.play((_time, frame) => {
|
|
252
|
+
shader.updateTextures({ u_webcam: videoElement });
|
|
253
|
+
// Only save every 10th frame to history.
|
|
254
|
+
return { skipHistory: frame % 10 === 0 };
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Parameters:**
|
|
259
|
+
|
|
260
|
+
- `onBeforeStep?`: `(time: number, frame: number) => StepOptions | void` - Called before each frame
|
|
261
|
+
|
|
262
|
+
#### `step(options?)`
|
|
263
|
+
|
|
264
|
+
Manually advance one frame. Updates `u_time` and `u_frame`, renders, then writes to history if `skipHistory` is `false` (default).
|
|
265
|
+
|
|
266
|
+
**Parameters:**
|
|
267
|
+
|
|
268
|
+
- `options?` (optional): `StepOptions` - Options to control rendering behavior (see below)
|
|
269
|
+
|
|
270
|
+
#### `draw(options?)`
|
|
271
|
+
|
|
272
|
+
Render without updating uniforms, frame counter, or history.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
shader.draw({ skipClear: true });
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Parameters:**
|
|
279
|
+
|
|
280
|
+
- `options?` (optional): `StepOptions` - Options to control rendering behavior (see below)
|
|
281
|
+
|
|
282
|
+
#### `clear()`
|
|
283
|
+
|
|
284
|
+
Clear ShaderPad’s current render target without advancing time, frame, or history.
|
|
285
|
+
|
|
286
|
+
#### `StepOptions`
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
interface StepOptions {
|
|
290
|
+
skipClear?: boolean; // Skip clearing canvas before rendering
|
|
291
|
+
skipHistory?: boolean; // Skip writing to history buffers
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Options:**
|
|
296
|
+
|
|
297
|
+
- `skipClear?: boolean` - If `true`, the canvas is not cleared before rendering. Useful for accumulating effects or multi-pass rendering.
|
|
298
|
+
- `skipHistory?: boolean` - If `true`, history buffers are not updated. Useful when you want to render without affecting the history state.
|
|
299
|
+
|
|
300
|
+
#### `pause()`, `reset()`, `destroy()`
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
shader.pause(); // Pause the render loop.
|
|
304
|
+
shader.reset(); // Reset frame counter, clock, and clear history buffers.
|
|
305
|
+
shader.resetFrame(); // Reset frame counter and clock only.
|
|
306
|
+
shader.destroy(); // Clean up resources.
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Properties
|
|
310
|
+
|
|
311
|
+
- `gl` (WebGL2RenderingContext): The WebGL2 context used for rendering
|
|
312
|
+
- `canvas` (HTMLCanvasElement | OffscreenCanvas): The canvas element used for rendering
|
|
313
|
+
|
|
314
|
+
### Type exports
|
|
315
|
+
|
|
316
|
+
`shaderpad` exports `Options`, `StepOptions`, `TextureOptions`, `InitializeTextureOptions`, `TextureSource`,
|
|
317
|
+
`UpdateTextureSource`, `CustomTexture`, `PartialCustomTexture`, `Plugin`, `PluginContext`, `ShaderPadEventName`, and
|
|
318
|
+
the GL literal string types. `shaderpad/util` exports `ToBlobOptions` and `SaveOptions`.
|
|
319
|
+
|
|
320
|
+
### Event Listeners
|
|
321
|
+
|
|
322
|
+
#### `on(event, callback)`
|
|
323
|
+
|
|
324
|
+
Register a callback for a lifecycle event.
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
shader.on('updateResolution', (width, height) => {
|
|
328
|
+
console.log(`New resolution: ${width}x${height}`);
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Parameters:**
|
|
333
|
+
|
|
334
|
+
- `event` (string): The event name
|
|
335
|
+
- `callback` (Function): The callback function
|
|
336
|
+
|
|
337
|
+
#### `off(event, callback)`
|
|
338
|
+
|
|
339
|
+
Remove a previously registered callback.
|
|
340
|
+
|
|
341
|
+
**Parameters:**
|
|
342
|
+
|
|
343
|
+
- `event` (string): The event name
|
|
344
|
+
- `callback` (Function): The callback function to remove
|
|
345
|
+
|
|
346
|
+
#### Available Events
|
|
347
|
+
|
|
348
|
+
| Event | Callback Arguments | Description |
|
|
349
|
+
| ------------------- | ------------------------------------------------------ | ------------------------------------------------ |
|
|
350
|
+
| `updateResolution` | `(width: number, height: number)` | Fired when the drawing buffer resolution changes |
|
|
351
|
+
| `play` | none | Fired when the render loop starts |
|
|
352
|
+
| `pause` | none | Fired when the render loop is paused |
|
|
353
|
+
| `reset` | none | Fired when the shader is reset |
|
|
354
|
+
| `destroy` | none | Fired when the shader is destroyed |
|
|
355
|
+
| `beforeStep` | `(time: number, frame: number, options?: StepOptions)` | Fired before each render step |
|
|
356
|
+
| `afterStep` | `(time: number, frame: number, options?: StepOptions)` | Fired after each render step |
|
|
357
|
+
| `beforeDraw` | `(options?: StepOptions)` | Fired before each draw call |
|
|
358
|
+
| `afterDraw` | `(options?: StepOptions)` | Fired after each draw call |
|
|
359
|
+
| `initializeTexture` | `(name, source, options?)` | Fired after a texture is initialized |
|
|
360
|
+
| `initializeUniform` | `(name, type, value, options?)` | Fired after a uniform is initialized |
|
|
361
|
+
| `updateTextures` | `(updates)` | Fired after textures are updated |
|
|
362
|
+
| `updateUniforms` | `(updates, options?)` | Fired after uniforms are updated |
|
|
363
|
+
|
|
364
|
+
Plugins may emit additional namespaced events (e.g., `face:ready`, `pose:result`).
|
|
365
|
+
|
|
366
|
+
#### Plugin authoring
|
|
367
|
+
|
|
368
|
+
ShaderPad plugins are plain functions passed in `plugins: [...]`. Each plugin receives the `ShaderPad` instance and a
|
|
369
|
+
small `PluginContext` during construction, before the fragment shader is compiled. In practice, that means a plugin can
|
|
370
|
+
inject GLSL, listen to lifecycle events with `shader.on(...)`, emit its own namespaced events, and publish plugin-owned
|
|
371
|
+
textures with `updateTexture(...)`. If a plugin needs the backing canvas or raw WebGL access, use `shader.canvas` and
|
|
372
|
+
`shader.gl`.
|
|
373
|
+
|
|
374
|
+
The most useful lifecycle hooks are usually `_init` for setup, `beforeStep` / `beforeDraw` for per-frame work, and
|
|
375
|
+
`destroy` for cleanup. Plugin order is stable: plugins install in `plugins[]` order, handlers run in registration
|
|
376
|
+
order, and GLSL injections preserve plugin order. If a plugin touches shared GL state directly, it should restore that
|
|
377
|
+
state before returning.
|
|
378
|
+
|
|
379
|
+
Start with the
|
|
380
|
+
[autosize source](./packages/shaderpad/src/plugins/autosize.ts) if you
|
|
381
|
+
want a compact example that listens to lifecycle events, emits a custom event, and cleans up after itself. For more
|
|
382
|
+
examples, browse the
|
|
383
|
+
[plugins source folder](./packages/shaderpad/src/plugins) or read the
|
|
384
|
+
[plugin guide](https://misery.co/shaderpad/docs/api/plugins/).
|
|
385
|
+
|
|
386
|
+
## Options
|
|
387
|
+
|
|
388
|
+
ShaderPad’s constructor accepts an optional `options` object.
|
|
389
|
+
|
|
390
|
+
In addition to the fields below, constructor options also accept texture storage/filter/wrap settings such as
|
|
391
|
+
`internalFormat`, `format`, `type`, `minFilter`, `magFilter`, `wrapS`, and `wrapT`. These configure ShaderPad’s
|
|
392
|
+
internal render targets and history buffers, which is mainly useful for float/integer pipelines and chained shaders.
|
|
393
|
+
|
|
394
|
+
### canvas
|
|
395
|
+
|
|
396
|
+
The `canvas` option allows you to pass in an existing canvas element, dimensions object, or `null` for headless mode.
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
const canvas = document.createElement('canvas');
|
|
400
|
+
const shader = new ShaderPad(fragmentShaderSrc, { canvas });
|
|
401
|
+
|
|
402
|
+
// Use utilities for a fullscreen canvas.
|
|
403
|
+
import autosize from 'shaderpad/plugins/autosize';
|
|
404
|
+
import { createFullscreenCanvas } from 'shaderpad/util';
|
|
405
|
+
const shader = new ShaderPad(fragmentShaderSrc, { canvas: createFullscreenCanvas(), plugins: [autosize()] });
|
|
406
|
+
|
|
407
|
+
// Create a headless ShaderPad for intermediate processing with initial dimensions.
|
|
408
|
+
const shader = new ShaderPad(fragmentShaderSrc, { canvas: { width: 640, height: 480 } });
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### cursorTarget
|
|
412
|
+
|
|
413
|
+
Element (or `window`) to listen for cursor/click events; coordinates are normalized to [0,1] over its bounding box. If omitted and the canvas is an HTML canvas, the canvas is used.
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// Listen on the window (e.g. full viewport, coordinates 0–1).
|
|
417
|
+
const shader = new ShaderPad(fragmentShaderSrc, { cursorTarget: window });
|
|
418
|
+
|
|
419
|
+
// Listen on a specific container element.
|
|
420
|
+
const container = document.querySelector('.shader-container');
|
|
421
|
+
const shader = new ShaderPad(fragmentShaderSrc, { cursorTarget: container });
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### history
|
|
425
|
+
|
|
426
|
+
Enable framebuffer history to access previous frames.
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
// Store the last 10 frames of shader output.
|
|
430
|
+
const shader = new ShaderPad(fragmentShaderSrc, { history: 10 });
|
|
431
|
+
|
|
432
|
+
// In your shader, access history using the u_history uniform:
|
|
433
|
+
// uniform highp sampler2DArray u_history;
|
|
434
|
+
// It’s stored as a ringbuffer, so you can access specific frames with the provided offset uniform:
|
|
435
|
+
// uniform int u_historyFrameOffset;
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**High-precision history:** By default, history textures use 8-bit precision (RGBA8). For high-precision rendering, specify `internalFormat` and `type` options. This enables FBO rendering and preserves precision in history textures.
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
// For 32-bit float precision (requires EXT_color_buffer_float extension):
|
|
442
|
+
const shader = new ShaderPad(fragmentShaderSrc, {
|
|
443
|
+
history: 60,
|
|
444
|
+
internalFormat: 'RGBA32F',
|
|
445
|
+
type: 'FLOAT',
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
You can also enable history for individual textures:
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// Store the last 30 webcam frames.
|
|
453
|
+
shader.initializeTexture('u_webcam', videoElement, { history: 30 });
|
|
454
|
+
// In your shader, access history using the u_webcam and u_webcamFrameOffset uniforms:
|
|
455
|
+
// uniform highp sampler2DArray u_webcam;
|
|
456
|
+
// uniform int u_webcamFrameOffset;
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
It’s recommended to use the `helpers` plugin to simplify access to history textures:
|
|
460
|
+
|
|
461
|
+
```glsl
|
|
462
|
+
int nFramesAgo = 2; // Get the color 2 frames ago.
|
|
463
|
+
float zIndex = historyZ(u_webcam, u_webcamFrameOffset, nFramesAgo);
|
|
464
|
+
vec4 historyColor = texture(u_webcam, vec3(v_uv, zIndex));
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### plugins
|
|
468
|
+
|
|
469
|
+
ShaderPad adds additional functionality through plugins, which keeps bundle sizes small.
|
|
470
|
+
|
|
471
|
+
#### helpers
|
|
472
|
+
|
|
473
|
+
The `helpers` plugin provides convenience functions and constants. The individual GLSL snippets live in [`src/helpers`](./packages/shaderpad/src/helpers), and the plugin injects the combined set.
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import helpers from 'shaderpad/plugins/helpers';
|
|
477
|
+
const shader = new ShaderPad(fragmentShaderSrc, { plugins: [helpers()] });
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Note:** The `helpers` plugin automatically injects the `u_resolution` uniform into your shader. Do not declare it yourself.
|
|
481
|
+
|
|
482
|
+
If you already have a GLSL-aware bundler pipeline such as `vite-plugin-glsl`, you can also import the raw helper sources individually:
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
import fitCoverGLSL from 'shaderpad/helpers/fitCover.glsl';
|
|
486
|
+
import fitCoverInverseGLSL from 'shaderpad/helpers/fitCoverInverse.glsl';
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
If you want the whole helper set injected for you, use the `helpers()` plugin. The raw GLSL exports are for pulling in specific helpers yourself.
|
|
490
|
+
|
|
491
|
+
If you import `fitContain.glsl`, `fitContainInverse.glsl`, `fitCover.glsl`, or `fitCoverInverse.glsl` directly, you must declare `u_resolution` in your shader:
|
|
492
|
+
|
|
493
|
+
```glsl
|
|
494
|
+
uniform vec2 u_resolution;
|
|
495
|
+
#include "/shaderpad/helpers/fitContain.glsl"
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
For `vite-plugin-glsl`, that leading `/` matches the common `root: '/node_modules/'` setup used in the ShaderPad starters.
|
|
499
|
+
|
|
500
|
+
#### save / toBlob
|
|
501
|
+
|
|
502
|
+
The `shaderpad/util` entrypoint exposes `toBlob(shader, options?)` for custom capture flows and
|
|
503
|
+
`save(shader, filename?, text?, options?)` for download/share behavior. Both render the current frame before
|
|
504
|
+
serializing it.
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
import { save, toBlob } from 'shaderpad/util';
|
|
508
|
+
|
|
509
|
+
await save(shader, 'filename', 'Optional mobile share text', { preventShare: true });
|
|
510
|
+
const blob = await toBlob(shader);
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**`save()` parameters:**
|
|
514
|
+
|
|
515
|
+
- `filename?` (string) - Output filename. Defaults to `export.png`
|
|
516
|
+
- `text?` (string) - Optional share text for platforms that support the Web Share API
|
|
517
|
+
- `options.preventShare?: boolean` - If `true`, the Web Share API is not used and the file is always downloaded. Default: `false`.
|
|
518
|
+
|
|
519
|
+
#### face
|
|
520
|
+
|
|
521
|
+
The `face` plugin uses [MediaPipe](https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker) to detect faces in video or image textures.
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import face from 'shaderpad/plugins/face';
|
|
525
|
+
const shader = new ShaderPad(fragmentShaderSrc, {
|
|
526
|
+
plugins: [face({ textureName: 'u_webcam', options: { maxFaces: 3 } })],
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**Options:**
|
|
531
|
+
|
|
532
|
+
- `maxFaces?: number` - Maximum faces to detect (default: 1)
|
|
533
|
+
- `history?: number` - Frames of history to store for landmarks and mask textures
|
|
534
|
+
|
|
535
|
+
**Events:**
|
|
536
|
+
|
|
537
|
+
| Event | Callback Arguments | Description |
|
|
538
|
+
| ------------- | -------------------------------- | ---------------------------------------- |
|
|
539
|
+
| `face:ready` | none | Fired when the detection model is loaded |
|
|
540
|
+
| `face:result` | `(result: FaceLandmarkerResult)` | Fired with detection results each frame |
|
|
541
|
+
|
|
542
|
+
**Uniforms:**
|
|
543
|
+
|
|
544
|
+
| Uniform | Type | Description |
|
|
545
|
+
| -------------------- | ----------------------------- | ---------------------------------------------------------------------------- |
|
|
546
|
+
| `u_maxFaces` | int | Maximum number of faces to detect |
|
|
547
|
+
| `u_nFaces` | int | Current number of detected faces |
|
|
548
|
+
| `u_faceLandmarksTex` | sampler2D (or sampler2DArray) | Raw landmark data texture (use `faceLandmark()` to access) |
|
|
549
|
+
| `u_faceMask` | sampler2D (or sampler2DArray) | Face mask texture (R: region type, G: face region, B: normalized face index) |
|
|
550
|
+
|
|
551
|
+
**Helper functions:**
|
|
552
|
+
|
|
553
|
+
All region functions return `vec2(confidence, faceIndex)`. faceIndex is 0-indexed (-1 = no face). When `history` is enabled, all functions accept an optional `int framesAgo` parameter.
|
|
554
|
+
|
|
555
|
+
- `faceLandmark(int faceIndex, int landmarkIndex) -> vec4` - Returns landmark data as `vec4(x, y, z, visibility)`. Use `vec2(faceLandmark(...))` to get just the screen position.
|
|
556
|
+
- `leftEyebrowAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in left eyebrow, `vec2(0.0, -1.0)` otherwise.
|
|
557
|
+
- `rightEyebrowAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in right eyebrow, `vec2(0.0, -1.0)` otherwise.
|
|
558
|
+
- `leftEyeAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in left eye, `vec2(0.0, -1.0)` otherwise.
|
|
559
|
+
- `rightEyeAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in right eye, `vec2(0.0, -1.0)` otherwise.
|
|
560
|
+
- `lipsAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in lips, `vec2(0.0, -1.0)` otherwise.
|
|
561
|
+
- `mouthAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in full mouth area (lips + inner mouth), `vec2(0.0, -1.0)` otherwise.
|
|
562
|
+
- `innerMouthAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in inner mouth region, `vec2(0.0, -1.0)` otherwise.
|
|
563
|
+
- `faceOvalAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in face oval contour, `vec2(0.0, -1.0)` otherwise.
|
|
564
|
+
- `faceAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in the full face area, `vec2(0.0, -1.0)` otherwise.
|
|
565
|
+
- `eyeAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in either eye, `vec2(0.0, -1.0)` otherwise.
|
|
566
|
+
- `eyebrowAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in either eyebrow, `vec2(0.0, -1.0)` otherwise.
|
|
567
|
+
|
|
568
|
+
**Convenience functions** (return confidence 0-1 if found, `0.0` otherwise):
|
|
569
|
+
|
|
570
|
+
- `inFace(vec2 pos) -> float` - Returns confidence (0-1) if position is in the full face area, `0.0` otherwise.
|
|
571
|
+
- `inEye(vec2 pos) -> float` - Returns confidence (0-1) if position is in either eye, `0.0` otherwise.
|
|
572
|
+
- `inEyebrow(vec2 pos) -> float` - Returns confidence (0-1) if position is in either eyebrow, `0.0` otherwise.
|
|
573
|
+
- `inMouth(vec2 pos) -> float` - Returns confidence (0-1) if position is in full mouth area (lips + inner mouth), `0.0` otherwise.
|
|
574
|
+
- `inInnerMouth(vec2 pos) -> float` - Returns confidence (0-1) if position is in inner mouth, `0.0` otherwise.
|
|
575
|
+
- `inLips(vec2 pos) -> float` - Returns confidence (0-1) if position is in lips, `0.0` otherwise.
|
|
576
|
+
|
|
577
|
+
**Landmark Constants:**
|
|
578
|
+
|
|
579
|
+
- `FACE_LANDMARK_L_EYE_CENTER` - Left eye center landmark index
|
|
580
|
+
- `FACE_LANDMARK_R_EYE_CENTER` - Right eye center landmark index
|
|
581
|
+
- `FACE_LANDMARK_NOSE_TIP` - Nose tip landmark index
|
|
582
|
+
- `FACE_LANDMARK_FACE_CENTER` - Face center landmark index (custom, calculated from all landmarks)
|
|
583
|
+
- `FACE_LANDMARK_MOUTH_CENTER` - Mouth center landmark index (custom, calculated from inner mouth landmarks)
|
|
584
|
+
|
|
585
|
+
**Example usage:**
|
|
586
|
+
|
|
587
|
+
```glsl
|
|
588
|
+
// Get a specific landmark position.
|
|
589
|
+
vec2 nosePos = vec2(faceLandmark(0, FACE_LANDMARK_NOSE_TIP));
|
|
590
|
+
|
|
591
|
+
// Use in* convenience functions for simple confidence checks.
|
|
592
|
+
float eyeMask = inEye(v_uv);
|
|
593
|
+
|
|
594
|
+
// Use faceLandmark or *At functions when you need to check a specific face index.
|
|
595
|
+
vec2 leftEye = leftEyeAt(v_uv);
|
|
596
|
+
for (int i = 0; i < u_nFaces; ++i) {
|
|
597
|
+
vec4 leftEyeCenter = faceLandmark(i, FACE_LANDMARK_L_EYE_CENTER);
|
|
598
|
+
vec4 rightEyeCenter = faceLandmark(i, FACE_LANDMARK_R_EYE_CENTER);
|
|
599
|
+
if (leftEye.x > 0.0 && int(leftEye.y) == i) {
|
|
600
|
+
// Position is inside the left eye of face i.
|
|
601
|
+
}
|
|
602
|
+
// ...
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
[Landmark indices are documented here.](https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker#face_landmarker_model) This library adds two custom landmarks: `FACE_CENTER` and `MOUTH_CENTER`. This brings the total landmark count to 480.
|
|
607
|
+
|
|
608
|
+
**Note:** Install `@mediapipe/tasks-vision` when using the face plugin.
|
|
609
|
+
|
|
610
|
+
**Note:** Confidence values are currently limited to 0.0 or 1.0.
|
|
611
|
+
|
|
612
|
+
#### pose
|
|
613
|
+
|
|
614
|
+
The `pose` plugin uses [MediaPipe Pose Landmarker](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker) to expose a flat array of 2D landmarks. Each pose contributes 39 landmarks (33 standard + 6 custom), enumerated below.
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
import pose from 'shaderpad/plugins/pose';
|
|
618
|
+
const shader = new ShaderPad(fragmentShaderSrc, {
|
|
619
|
+
plugins: [pose({ textureName: 'u_video', options: { maxPoses: 3 } })],
|
|
620
|
+
});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**Options:**
|
|
624
|
+
|
|
625
|
+
- `maxPoses?: number` - Maximum poses to detect (default: 1)
|
|
626
|
+
- `history?: number` - Frames of history to store for landmarks and mask textures
|
|
627
|
+
|
|
628
|
+
**Note:** Install `@mediapipe/tasks-vision` when using the pose plugin.
|
|
629
|
+
|
|
630
|
+
**Events:**
|
|
631
|
+
|
|
632
|
+
| Event | Callback Arguments | Description |
|
|
633
|
+
| ------------- | -------------------------------- | ---------------------------------------- |
|
|
634
|
+
| `pose:ready` | none | Fired when the detection model is loaded |
|
|
635
|
+
| `pose:result` | `(result: PoseLandmarkerResult)` | Fired with detection results each frame |
|
|
636
|
+
|
|
637
|
+
**Uniforms:**
|
|
638
|
+
|
|
639
|
+
| Uniform | Type | Description |
|
|
640
|
+
| -------------------- | ----------------------------- | ----------------------------------------------------------------------------- |
|
|
641
|
+
| `u_maxPoses` | int | Maximum number of poses to track |
|
|
642
|
+
| `u_nPoses` | int | Current number of detected poses |
|
|
643
|
+
| `u_poseLandmarksTex` | sampler2D (or sampler2DArray) | Raw landmark data texture (RGBA: x, y, z, visibility) |
|
|
644
|
+
| `u_poseMask` | sampler2D (or sampler2DArray) | Pose mask texture (R: body detected, G: confidence, B: normalized pose index) |
|
|
645
|
+
|
|
646
|
+
**Helper functions:**
|
|
647
|
+
|
|
648
|
+
When `history` is enabled, all functions accept an optional `int framesAgo` parameter.
|
|
649
|
+
|
|
650
|
+
- `poseLandmark(int poseIndex, int landmarkIndex) -> vec4` - Returns landmark data as `vec4(x, y, z, visibility)`. Use `vec2(poseLandmark(...))` to get just the screen position.
|
|
651
|
+
- `poseAt(vec2 pos) -> vec2` - Returns `vec2(confidence, poseIndex)`. poseIndex is 0-indexed (-1 = no pose), confidence is the segmentation confidence.
|
|
652
|
+
- `inPose(vec2 pos) -> float` - Returns confidence (0-1) if position is in any pose, `0.0` otherwise.
|
|
653
|
+
|
|
654
|
+
**Constants:**
|
|
655
|
+
|
|
656
|
+
- `POSE_LANDMARK_LEFT_EYE` - Left eye landmark index (2)
|
|
657
|
+
- `POSE_LANDMARK_RIGHT_EYE` - Right eye landmark index (5)
|
|
658
|
+
- `POSE_LANDMARK_LEFT_SHOULDER` - Left shoulder landmark index (11)
|
|
659
|
+
- `POSE_LANDMARK_RIGHT_SHOULDER` - Right shoulder landmark index (12)
|
|
660
|
+
- `POSE_LANDMARK_LEFT_ELBOW` - Left elbow landmark index (13)
|
|
661
|
+
- `POSE_LANDMARK_RIGHT_ELBOW` - Right elbow landmark index (14)
|
|
662
|
+
- `POSE_LANDMARK_LEFT_HIP` - Left hip landmark index (23)
|
|
663
|
+
- `POSE_LANDMARK_RIGHT_HIP` - Right hip landmark index (24)
|
|
664
|
+
- `POSE_LANDMARK_LEFT_KNEE` - Left knee landmark index (25)
|
|
665
|
+
- `POSE_LANDMARK_RIGHT_KNEE` - Right knee landmark index (26)
|
|
666
|
+
- `POSE_LANDMARK_BODY_CENTER` - Body center landmark index (33, custom, calculated from all landmarks)
|
|
667
|
+
- `POSE_LANDMARK_LEFT_HAND_CENTER` - Left hand center landmark index (34, custom, calculated from pinky, thumb, wrist, index)
|
|
668
|
+
- `POSE_LANDMARK_RIGHT_HAND_CENTER` - Right hand center landmark index (35, custom, calculated from pinky, thumb, wrist, index)
|
|
669
|
+
- `POSE_LANDMARK_LEFT_FOOT_CENTER` - Left foot center landmark index (36, custom, calculated from ankle, heel, foot index)
|
|
670
|
+
- `POSE_LANDMARK_RIGHT_FOOT_CENTER` - Right foot center landmark index (37, custom, calculated from ankle, heel, foot index)
|
|
671
|
+
- `POSE_LANDMARK_TORSO_CENTER` - Torso center landmark index (38, custom, calculated from shoulders and hips)
|
|
672
|
+
|
|
673
|
+
**Note:** For connecting pose landmarks (e.g., drawing skeleton lines), `PoseLandmarker.POSE_CONNECTIONS` from `@mediapipe/tasks-vision` provides an array of `{ start, end }` pairs that define which landmarks should be connected.
|
|
674
|
+
|
|
675
|
+
Use `poseLandmark(int poseIndex, int landmarkIndex)` in GLSL to retrieve a specific point. Landmark indices are:
|
|
676
|
+
|
|
677
|
+
| Index | Landmark | Index | Landmark |
|
|
678
|
+
| ----- | ----------------- | ----- | -------------------------- |
|
|
679
|
+
| 0 | nose | 20 | right index |
|
|
680
|
+
| 1 | left eye (inner) | 21 | left thumb |
|
|
681
|
+
| 2 | left eye | 22 | right thumb |
|
|
682
|
+
| 3 | left eye (outer) | 23 | left hip |
|
|
683
|
+
| 4 | right eye (inner) | 24 | right hip |
|
|
684
|
+
| 5 | right eye | 25 | left knee |
|
|
685
|
+
| 6 | right eye (outer) | 26 | right knee |
|
|
686
|
+
| 7 | left ear | 27 | left ankle |
|
|
687
|
+
| 8 | right ear | 28 | right ankle |
|
|
688
|
+
| 9 | mouth (left) | 29 | left heel |
|
|
689
|
+
| 10 | mouth (right) | 30 | right heel |
|
|
690
|
+
| 11 | left shoulder | 31 | left foot index |
|
|
691
|
+
| 12 | right shoulder | 32 | right foot index |
|
|
692
|
+
| 13 | left elbow | 33 | body center (custom) |
|
|
693
|
+
| 14 | right elbow | 34 | left hand center (custom) |
|
|
694
|
+
| 15 | left wrist | 35 | right hand center (custom) |
|
|
695
|
+
| 16 | right wrist | 36 | left foot center (custom) |
|
|
696
|
+
| 17 | left pinky | 37 | right foot center (custom) |
|
|
697
|
+
| 18 | right pinky | 38 | torso center (custom) |
|
|
698
|
+
| 19 | left index | | |
|
|
699
|
+
|
|
700
|
+
[Source](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model)
|
|
701
|
+
|
|
702
|
+
[Landmark indices are documented here.](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model) This library adds six custom landmarks: `BODY_CENTER`, `LEFT_HAND_CENTER`, `RIGHT_HAND_CENTER`, `LEFT_FOOT_CENTER`, `RIGHT_FOOT_CENTER`, and `TORSO_CENTER`. This brings the total landmark count to 39.
|
|
703
|
+
|
|
704
|
+
A minimal fragment shader loop looks like:
|
|
705
|
+
|
|
706
|
+
```glsl
|
|
707
|
+
for (int i = 0; i < u_maxPoses; ++i) {
|
|
708
|
+
if (i >= u_nPoses) break;
|
|
709
|
+
vec2 leftHip = vec2(poseLandmark(i, POSE_LANDMARK_LEFT_HIP));
|
|
710
|
+
vec2 rightHip = vec2(poseLandmark(i, POSE_LANDMARK_RIGHT_HIP));
|
|
711
|
+
// …
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
#### hands
|
|
716
|
+
|
|
717
|
+
The `hands` plugin uses [MediaPipe Hand Landmarker](https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker) to expose a flat array of 2D landmarks. Each hand contributes 22 landmarks, enumerated below.
|
|
718
|
+
|
|
719
|
+
```typescript
|
|
720
|
+
import hands from 'shaderpad/plugins/hands';
|
|
721
|
+
const shader = new ShaderPad(fragmentShaderSrc, {
|
|
722
|
+
plugins: [hands({ textureName: 'u_video', options: { maxHands: 2 } })],
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
**Options:**
|
|
727
|
+
|
|
728
|
+
- `maxHands?: number` - Maximum hands to detect (default: 2)
|
|
729
|
+
- `history?: number` - Frames of history to store for landmarks texture
|
|
730
|
+
|
|
731
|
+
**Events:**
|
|
732
|
+
|
|
733
|
+
| Event | Callback Arguments | Description |
|
|
734
|
+
| -------------- | -------------------------------- | ---------------------------------------- |
|
|
735
|
+
| `hands:ready` | none | Fired when the detection model is loaded |
|
|
736
|
+
| `hands:result` | `(result: HandLandmarkerResult)` | Fired with detection results each frame |
|
|
737
|
+
|
|
738
|
+
**Uniforms:**
|
|
739
|
+
|
|
740
|
+
| Uniform | Type | Description |
|
|
741
|
+
| -------------------- | ----------------------------- | ----------------------------------------------------- |
|
|
742
|
+
| `u_maxHands` | int | Maximum number of hands to track |
|
|
743
|
+
| `u_nHands` | int | Current number of detected hands |
|
|
744
|
+
| `u_handLandmarksTex` | sampler2D (or sampler2DArray) | Raw landmark data texture (RGBA: x, y, z, handedness) |
|
|
745
|
+
|
|
746
|
+
**Helper functions:**
|
|
747
|
+
|
|
748
|
+
When `history` is enabled, all functions accept an optional `int framesAgo` parameter.
|
|
749
|
+
|
|
750
|
+
- `handLandmark(int handIndex, int landmarkIndex) -> vec4` - Returns landmark data as `vec4(x, y, z, handedness)`. Use `vec2(handLandmark(...))` to get just the screen position. Handedness: 0.0 = left hand, 1.0 = right hand.
|
|
751
|
+
- `isRightHand(int handIndex) -> float` - Returns 1.0 if the hand is a right hand, 0.0 if left.
|
|
752
|
+
- `isLeftHand(int handIndex) -> float` - Returns 1.0 if the hand is a left hand, 0.0 if right.
|
|
753
|
+
|
|
754
|
+
**Landmark Indices:**
|
|
755
|
+
|
|
756
|
+
| Index | Landmark | Index | Landmark |
|
|
757
|
+
| ----- | ----------------- | ----- | ----------------- |
|
|
758
|
+
| 0 | WRIST | 11 | MIDDLE_FINGER_DIP |
|
|
759
|
+
| 1 | THUMB_CMC | 12 | MIDDLE_FINGER_TIP |
|
|
760
|
+
| 2 | THUMB_MCP | 13 | RING_FINGER_MCP |
|
|
761
|
+
| 3 | THUMB_IP | 14 | RING_FINGER_PIP |
|
|
762
|
+
| 4 | THUMB_TIP | 15 | RING_FINGER_DIP |
|
|
763
|
+
| 5 | INDEX_FINGER_MCP | 16 | RING_FINGER_TIP |
|
|
764
|
+
| 6 | INDEX_FINGER_PIP | 17 | PINKY_MCP |
|
|
765
|
+
| 7 | INDEX_FINGER_DIP | 18 | PINKY_PIP |
|
|
766
|
+
| 8 | INDEX_FINGER_TIP | 19 | PINKY_DIP |
|
|
767
|
+
| 9 | MIDDLE_FINGER_MCP | 20 | PINKY_TIP |
|
|
768
|
+
| 10 | MIDDLE_FINGER_PIP | 21 | HAND_CENTER |
|
|
769
|
+
|
|
770
|
+
[Source](https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker#models)
|
|
771
|
+
|
|
772
|
+
A minimal fragment shader loop looks like:
|
|
773
|
+
|
|
774
|
+
```glsl
|
|
775
|
+
#define WRIST 0
|
|
776
|
+
#define THUMB_TIP 4
|
|
777
|
+
#define INDEX_TIP 8
|
|
778
|
+
#define HAND_CENTER 21
|
|
779
|
+
|
|
780
|
+
for (int i = 0; i < u_maxHands; ++i) {
|
|
781
|
+
if (i >= u_nHands) break;
|
|
782
|
+
vec2 wrist = vec2(handLandmark(i, WRIST));
|
|
783
|
+
vec2 thumbTip = vec2(handLandmark(i, THUMB_TIP));
|
|
784
|
+
vec2 indexTip = vec2(handLandmark(i, INDEX_TIP));
|
|
785
|
+
vec2 handCenter = vec2(handLandmark(i, HAND_CENTER));
|
|
786
|
+
|
|
787
|
+
// Use handedness for coloring (0.0 = left/black, 1.0 = right/white).
|
|
788
|
+
vec3 handColor = vec3(isRightHand(i));
|
|
789
|
+
// …
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
**Note:** Install `@mediapipe/tasks-vision` when using the hands plugin.
|
|
794
|
+
|
|
795
|
+
#### segmenter
|
|
796
|
+
|
|
797
|
+
The `segmenter` plugin uses [MediaPipe Image Segmenter](https://ai.google.dev/edge/mediapipe/solutions/vision/image_segmenter) to segment objects in video or image textures. It supports models with multiple categories (e.g., background, hair, chair, dog…). By default, it uses MediaPipe's selfie segmenter model.
|
|
798
|
+
|
|
799
|
+
```typescript
|
|
800
|
+
import ShaderPad from 'shaderpad';
|
|
801
|
+
import segmenter from 'shaderpad/plugins/segmenter';
|
|
802
|
+
|
|
803
|
+
const shader = new ShaderPad(fragmentShaderSrc, {
|
|
804
|
+
plugins: [
|
|
805
|
+
segmenter({
|
|
806
|
+
textureName: 'u_webcam',
|
|
807
|
+
options: {
|
|
808
|
+
modelPath:
|
|
809
|
+
'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_multiclass_256x256/float32/latest/selfie_multiclass_256x256.tflite',
|
|
810
|
+
},
|
|
811
|
+
}),
|
|
812
|
+
],
|
|
813
|
+
});
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
**Options:**
|
|
817
|
+
|
|
818
|
+
- `modelPath?: string` - Path to the segmentation model (default: MediaPipe selfie segmenter)
|
|
819
|
+
- `outputConfidenceMasks?: boolean` - Whether to output per-category confidence masks (default: `false`). If `false`, confidence is always 1.
|
|
820
|
+
- `history?: number` - Frames of history to store for mask texture
|
|
821
|
+
|
|
822
|
+
**Events:**
|
|
823
|
+
|
|
824
|
+
| Event | Callback Arguments | Description |
|
|
825
|
+
| ------------------ | -------------------------------- | ------------------------------------------- |
|
|
826
|
+
| `segmenter:ready` | none | Fired when the segmentation model is loaded |
|
|
827
|
+
| `segmenter:result` | `(result: ImageSegmenterResult)` | Fired with segmentation results each frame |
|
|
828
|
+
|
|
829
|
+
**Uniforms:**
|
|
830
|
+
|
|
831
|
+
| Uniform | Type | Description |
|
|
832
|
+
| ----------------- | ----------------------------- | ----------------------------------------------------------------------- |
|
|
833
|
+
| `u_segmentMask` | sampler2D (or sampler2DArray) | Segment mask texture (R: confidence, G: normalized category, B: unused) |
|
|
834
|
+
| `u_numCategories` | int | Number of segmentation categories (including background) |
|
|
835
|
+
|
|
836
|
+
**Helper functions:**
|
|
837
|
+
|
|
838
|
+
When `history` is enabled, all functions accept an optional `int framesAgo` parameter.
|
|
839
|
+
|
|
840
|
+
- `segmentAt(vec2 pos) -> vec2` - Returns `vec2(confidence, category)`. confidence is the segmentation confidence (0–1, or 1 if `outputConfidenceMasks` is false). category is the normalized category index (0–1).
|
|
841
|
+
|
|
842
|
+
**Example usage:**
|
|
843
|
+
|
|
844
|
+
```glsl
|
|
845
|
+
vec2 segment = segmentAt(v_uv);
|
|
846
|
+
float confidence = segment.x;
|
|
847
|
+
float category = segment.y;
|
|
848
|
+
float isForeground = (1.0 - step(category, 0.0)) * confidence;
|
|
849
|
+
color = mix(color, vec3(1.0, 0.0, 1.0), isForeground);
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
**Note:** Install `@mediapipe/tasks-vision` when using the segmenter plugin.
|
|
853
|
+
|
|
854
|
+
#### autosize
|
|
855
|
+
|
|
856
|
+
The `autosize` plugin handles automatic canvas resolution updates with ResizeObserver.
|
|
857
|
+
|
|
858
|
+
**Options:**
|
|
859
|
+
|
|
860
|
+
- `scale?: number` - Resolution multiplier relative to CSS pixels (default: current `devicePixelRatio`; use `1` to match CSS pixel size)
|
|
861
|
+
- `target?: Element | Window` - What to observe for resize (default: canvas itself for HTMLCanvasElement, window for OffscreenCanvas)
|
|
862
|
+
- `throttle?: number` - Throttle interval in milliseconds (default: 33ms)
|
|
863
|
+
|
|
864
|
+
**Events:**
|
|
865
|
+
|
|
866
|
+
| Event | Callback Arguments | Description |
|
|
867
|
+
| ----------------- | --------------------------------- | ---------------------------------------------------- |
|
|
868
|
+
| `autosize:resize` | `(width: number, height: number)` | Fired when the canvas drawing buffer size is updated |
|
|
869
|
+
|
|
870
|
+
## Contributing
|
|
871
|
+
|
|
872
|
+
### Running an example
|
|
873
|
+
|
|
874
|
+
```bash
|
|
875
|
+
git clone https://github.com/miseryco/shaderpad.git
|
|
876
|
+
cd shaderpad
|
|
877
|
+
npm install
|
|
878
|
+
npm run docs:dev
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
### Adding an example
|
|
882
|
+
|
|
883
|
+
Add a `.ts` file in `docs/src/examples/demos/` that exports `init` and `destroy` functions. Then register it in [`docs/src/examples/registry-data.mjs`](./docs/src/examples/registry-data.mjs) and [`docs/src/examples/registry.ts`](./docs/src/examples/registry.ts).
|
|
884
|
+
|
|
885
|
+
## License
|
|
886
|
+
|
|
887
|
+
This project is licensed under [MIT](./LICENSE).
|