@stevejtrettel/shader-sandbox 0.1.2 → 0.1.4
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 +259 -235
- package/bin/cli.js +106 -14
- package/dist-lib/app/App.d.ts +143 -15
- package/dist-lib/app/App.d.ts.map +1 -1
- package/dist-lib/app/App.js +1343 -108
- package/dist-lib/app/app.css +349 -24
- package/dist-lib/app/types.d.ts +48 -5
- package/dist-lib/app/types.d.ts.map +1 -1
- package/dist-lib/editor/EditorPanel.d.ts +2 -2
- package/dist-lib/editor/EditorPanel.d.ts.map +1 -1
- package/dist-lib/editor/EditorPanel.js +1 -1
- package/dist-lib/editor/editor-panel.css +55 -32
- package/dist-lib/editor/prism-editor.css +16 -16
- package/dist-lib/embed.js +1 -1
- package/dist-lib/engine/{ShadertoyEngine.d.ts → ShaderEngine.d.ts} +134 -10
- package/dist-lib/engine/ShaderEngine.d.ts.map +1 -0
- package/dist-lib/engine/ShaderEngine.js +1523 -0
- package/dist-lib/engine/glHelpers.d.ts +24 -0
- package/dist-lib/engine/glHelpers.d.ts.map +1 -1
- package/dist-lib/engine/glHelpers.js +88 -0
- package/dist-lib/engine/std140.d.ts +47 -0
- package/dist-lib/engine/std140.d.ts.map +1 -0
- package/dist-lib/engine/std140.js +119 -0
- package/dist-lib/engine/types.d.ts +55 -5
- package/dist-lib/engine/types.d.ts.map +1 -1
- package/dist-lib/engine/types.js +1 -1
- package/dist-lib/index.d.ts +4 -3
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +2 -1
- package/dist-lib/layouts/SplitLayout.d.ts +2 -1
- package/dist-lib/layouts/SplitLayout.d.ts.map +1 -1
- package/dist-lib/layouts/SplitLayout.js +3 -0
- package/dist-lib/layouts/TabbedLayout.d.ts.map +1 -1
- package/dist-lib/layouts/UILayout.d.ts +55 -0
- package/dist-lib/layouts/UILayout.d.ts.map +1 -0
- package/dist-lib/layouts/UILayout.js +147 -0
- package/dist-lib/layouts/default.css +2 -2
- package/dist-lib/layouts/index.d.ts +11 -1
- package/dist-lib/layouts/index.d.ts.map +1 -1
- package/dist-lib/layouts/index.js +17 -1
- package/dist-lib/layouts/split.css +33 -31
- package/dist-lib/layouts/tabbed.css +127 -74
- package/dist-lib/layouts/types.d.ts +14 -3
- package/dist-lib/layouts/types.d.ts.map +1 -1
- package/dist-lib/main.js +33 -0
- package/dist-lib/project/configHelpers.d.ts +45 -0
- package/dist-lib/project/configHelpers.d.ts.map +1 -0
- package/dist-lib/project/configHelpers.js +196 -0
- package/dist-lib/project/generatedLoader.d.ts +2 -2
- package/dist-lib/project/generatedLoader.d.ts.map +1 -1
- package/dist-lib/project/generatedLoader.js +23 -5
- package/dist-lib/project/loadProject.d.ts +6 -6
- package/dist-lib/project/loadProject.d.ts.map +1 -1
- package/dist-lib/project/loadProject.js +396 -144
- package/dist-lib/project/loaderHelper.d.ts +4 -4
- package/dist-lib/project/loaderHelper.d.ts.map +1 -1
- package/dist-lib/project/loaderHelper.js +278 -116
- package/dist-lib/project/types.d.ts +292 -13
- package/dist-lib/project/types.d.ts.map +1 -1
- package/dist-lib/project/types.js +13 -1
- package/dist-lib/styles/base.css +5 -1
- package/dist-lib/uniforms/UniformControls.d.ts +60 -0
- package/dist-lib/uniforms/UniformControls.d.ts.map +1 -0
- package/dist-lib/uniforms/UniformControls.js +518 -0
- package/dist-lib/uniforms/UniformStore.d.ts +74 -0
- package/dist-lib/uniforms/UniformStore.d.ts.map +1 -0
- package/dist-lib/uniforms/UniformStore.js +145 -0
- package/dist-lib/uniforms/UniformsPanel.d.ts +53 -0
- package/dist-lib/uniforms/UniformsPanel.d.ts.map +1 -0
- package/dist-lib/uniforms/UniformsPanel.js +124 -0
- package/dist-lib/uniforms/index.d.ts +11 -0
- package/dist-lib/uniforms/index.d.ts.map +1 -0
- package/dist-lib/uniforms/index.js +8 -0
- package/package.json +16 -1
- package/src/app/App.ts +1469 -126
- package/src/app/app.css +349 -24
- package/src/app/types.ts +53 -5
- package/src/editor/EditorPanel.ts +5 -5
- package/src/editor/editor-panel.css +55 -32
- package/src/editor/prism-editor.css +16 -16
- package/src/embed.ts +1 -1
- package/src/engine/ShaderEngine.ts +1934 -0
- package/src/engine/glHelpers.ts +117 -0
- package/src/engine/std140.ts +136 -0
- package/src/engine/types.ts +69 -5
- package/src/index.ts +4 -3
- package/src/layouts/SplitLayout.ts +8 -3
- package/src/layouts/TabbedLayout.ts +3 -3
- package/src/layouts/UILayout.ts +185 -0
- package/src/layouts/default.css +2 -2
- package/src/layouts/index.ts +20 -1
- package/src/layouts/split.css +33 -31
- package/src/layouts/tabbed.css +127 -74
- package/src/layouts/types.ts +19 -3
- package/src/layouts/ui.css +289 -0
- package/src/main.ts +39 -1
- package/src/project/configHelpers.ts +225 -0
- package/src/project/generatedLoader.ts +27 -6
- package/src/project/loadProject.ts +459 -173
- package/src/project/loaderHelper.ts +377 -130
- package/src/project/types.ts +360 -14
- package/src/styles/base.css +5 -1
- package/src/styles/theme.css +292 -0
- package/src/uniforms/UniformControls.ts +660 -0
- package/src/uniforms/UniformStore.ts +166 -0
- package/src/uniforms/UniformsPanel.ts +163 -0
- package/src/uniforms/index.ts +13 -0
- package/src/uniforms/uniform-controls.css +342 -0
- package/src/uniforms/uniforms-panel.css +277 -0
- package/templates/shaders/example-buffer/config.json +1 -0
- package/dist-lib/engine/ShadertoyEngine.d.ts.map +0 -1
- package/dist-lib/engine/ShadertoyEngine.js +0 -704
- package/src/engine/ShadertoyEngine.ts +0 -929
package/README.md
CHANGED
|
@@ -1,105 +1,83 @@
|
|
|
1
1
|
# Shader Sandbox
|
|
2
2
|
|
|
3
|
-
A lightweight, Shadertoy-compatible GLSL shader
|
|
3
|
+
A lightweight, Shadertoy-compatible GLSL shader development environment. Copy shaders directly from Shadertoy and run them locally with live editing.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Shadertoy Compatibility** - Copy/paste shaders directly from Shadertoy
|
|
8
|
-
- **Full Shadertoy Uniforms** - `iTime`, `iResolution`, `iFrame`, `iMouse`, `iTimeDelta`, `iChannel0-3`
|
|
8
|
+
- **Full Shadertoy Uniforms** - `iTime`, `iResolution`, `iFrame`, `iMouse`, `iTimeDelta`, `iDate`, `iFrameRate`, `iChannel0-3`
|
|
9
9
|
- **Multi-Buffer Rendering** - BufferA-D passes with correct ping-pong semantics
|
|
10
|
-
- **Texture Support** - Load
|
|
11
|
-
- **Keyboard Input** - Full keyboard state via
|
|
10
|
+
- **Texture Support** - Load images (including cubemaps) with configurable filtering and wrapping
|
|
11
|
+
- **Keyboard Input** - Full keyboard state via texture. In standard mode, key constants (`KEY_A`–`KEY_Z`, `KEY_SPACE`, etc.) and helpers (`isKeyDown`, `keyToggle`) are auto-injected
|
|
12
|
+
- **Audio Input** - Microphone FFT spectrum and waveform as a texture, as in shadertoy
|
|
13
|
+
- **Webcam & Video** - Live webcam or video files as channel inputs, as in shadertoy
|
|
14
|
+
- **Custom Uniforms** - Float, int, bool, vec2, vec3, vec4 sliders and color pickers via config. *An extension beyond shadertoy*
|
|
15
|
+
- **UBO Array Uniforms** - Large data arrays (positions, colors, matrices) via Uniform Buffer Objects. *An extension beyond shadertoy*
|
|
16
|
+
- **Scripting API** - JavaScript hooks for per-frame computation, texture upload, and GPU readback. *An extension beyond shadertoy*
|
|
17
|
+
- **Touch Support** - Multi-touch, pinch, and gesture uniforms for mobile
|
|
18
|
+
- **Live Code Editing** - Edit shaders in the browser with instant recompilation
|
|
19
|
+
- **Multiple Layouts** - Fullscreen, split-view, or tabbed code display
|
|
12
20
|
- **Playback Controls** - Play/pause, reset, and screenshot capture
|
|
13
|
-
- **
|
|
14
|
-
- **Zero Runtime Dependencies** - Pure WebGL2
|
|
15
|
-
- **Tiny Builds** - ~26KB JS (gzipped)
|
|
21
|
+
- **Themes** - Light, dark, or system-following theme
|
|
16
22
|
|
|
17
23
|
## Quick Start
|
|
18
24
|
|
|
19
25
|
```bash
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
npm run dev:demo my-shader
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Open `http://localhost:3000` to see your shader.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Use as NPM Package
|
|
26
|
+
# Create a new shader project
|
|
27
|
+
npx @stevejtrettel/shader-sandbox create my-shaders
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
# Create a new project (does everything in one step)
|
|
35
|
-
npx shader-sandbox create my-shaders
|
|
36
|
-
|
|
37
|
-
# Run a shader
|
|
29
|
+
# Enter the project
|
|
38
30
|
cd my-shaders
|
|
31
|
+
|
|
32
|
+
# Run an example shader
|
|
39
33
|
shader dev example-gradient
|
|
40
34
|
```
|
|
41
35
|
|
|
42
|
-
|
|
36
|
+
Open http://localhost:3000 to see your shader running.
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
## CLI Commands
|
|
45
39
|
|
|
46
40
|
```bash
|
|
47
|
-
shader create <name> # Create a new shader project
|
|
48
|
-
shader
|
|
49
|
-
shader list # List all shaders
|
|
50
|
-
shader dev <name> # Run shader in development mode
|
|
41
|
+
shader create <name> # Create a new shader project
|
|
42
|
+
shader dev <name> # Run shader with live reload
|
|
51
43
|
shader build <name> # Build shader for production
|
|
52
44
|
shader new <name> # Create a new shader
|
|
45
|
+
shader list # List all shaders
|
|
46
|
+
shader init # Initialize shaders in current directory
|
|
53
47
|
```
|
|
54
48
|
|
|
55
|
-
|
|
49
|
+
## Project Structure
|
|
56
50
|
|
|
57
|
-
After `shader create`:
|
|
51
|
+
After running `shader create my-shaders`:
|
|
58
52
|
|
|
59
53
|
```
|
|
60
54
|
my-shaders/
|
|
61
55
|
├── shaders/
|
|
62
56
|
│ ├── example-gradient/
|
|
63
|
-
│ │ ├── image.glsl
|
|
64
|
-
│ │ └── config.json
|
|
57
|
+
│ │ ├── image.glsl # Main shader code
|
|
58
|
+
│ │ └── config.json # Optional configuration
|
|
65
59
|
│ └── example-buffer/
|
|
66
|
-
│ ├── image.glsl
|
|
67
|
-
│ ├── bufferA.glsl
|
|
60
|
+
│ ├── image.glsl # Final output
|
|
61
|
+
│ ├── bufferA.glsl # Feedback buffer
|
|
68
62
|
│ └── config.json
|
|
69
|
-
├── main.ts
|
|
70
|
-
├── vite.config.js
|
|
63
|
+
├── main.ts # Entry point
|
|
64
|
+
├── vite.config.js # Vite configuration
|
|
71
65
|
└── package.json
|
|
72
66
|
```
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
shader new my-cool-shader
|
|
78
|
-
# Creates shaders/my-cool-shader/image.glsl
|
|
79
|
-
|
|
80
|
-
shader dev my-cool-shader
|
|
81
|
-
# Opens browser with live reload
|
|
82
|
-
```
|
|
68
|
+
## Creating Shaders
|
|
83
69
|
|
|
84
|
-
|
|
70
|
+
### Simple Shader
|
|
85
71
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
### 1. Simple Shader (just image.glsl)
|
|
89
|
-
|
|
90
|
-
The simplest setup - no config needed.
|
|
72
|
+
Create a new shader with just an image pass:
|
|
91
73
|
|
|
92
74
|
```bash
|
|
93
|
-
|
|
75
|
+
shader new my-shader
|
|
76
|
+
shader dev my-shader
|
|
94
77
|
```
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
demos/my-shader/
|
|
99
|
-
└── image.glsl
|
|
100
|
-
```
|
|
79
|
+
Edit `shaders/my-shader/image.glsl`:
|
|
101
80
|
|
|
102
|
-
**image.glsl:**
|
|
103
81
|
```glsl
|
|
104
82
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
105
83
|
vec2 uv = fragCoord / iResolution.xy;
|
|
@@ -108,45 +86,26 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
108
86
|
}
|
|
109
87
|
```
|
|
110
88
|
|
|
111
|
-
|
|
89
|
+
### Copy from Shadertoy
|
|
112
90
|
|
|
113
|
-
|
|
91
|
+
1. Find a shader on [Shadertoy](https://www.shadertoy.com)
|
|
92
|
+
2. Copy the code from the "Image" tab
|
|
93
|
+
3. Paste into `shaders/my-shader/image.glsl`
|
|
94
|
+
4. Run `shader dev my-shader`
|
|
114
95
|
|
|
115
|
-
|
|
96
|
+
Most single-pass shaders work immediately. For multi-buffer shaders, you'll need to create the buffer files and config.
|
|
116
97
|
|
|
117
|
-
|
|
118
|
-
npm run new my-shader 1
|
|
119
|
-
```
|
|
98
|
+
### Multi-Buffer Shaders
|
|
120
99
|
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
demos/my-shader/
|
|
124
|
-
├── bufferA.glsl
|
|
125
|
-
├── image.glsl
|
|
126
|
-
└── config.json
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**config.json:**
|
|
130
|
-
```json
|
|
131
|
-
{
|
|
132
|
-
"BufferA": {
|
|
133
|
-
"iChannel0": "BufferA"
|
|
134
|
-
},
|
|
135
|
-
"Image": {
|
|
136
|
-
"iChannel0": "BufferA"
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
100
|
+
For feedback effects (trails, fluid, etc.), create a buffer:
|
|
140
101
|
|
|
141
|
-
**bufferA.glsl:**
|
|
102
|
+
**shaders/my-effect/bufferA.glsl:**
|
|
142
103
|
```glsl
|
|
143
104
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
144
105
|
vec2 uv = fragCoord / iResolution.xy;
|
|
106
|
+
vec4 prev = texture(iChannel0, uv) * 0.98; // Previous frame with fade
|
|
145
107
|
|
|
146
|
-
//
|
|
147
|
-
vec4 prev = texture(iChannel0, uv) * 0.98;
|
|
148
|
-
|
|
149
|
-
// Draw at mouse
|
|
108
|
+
// Draw at mouse position
|
|
150
109
|
vec2 mouse = iMouse.xy / iResolution.xy;
|
|
151
110
|
float d = length(uv - mouse);
|
|
152
111
|
float spot = smoothstep(0.05, 0.0, d);
|
|
@@ -155,7 +114,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
155
114
|
}
|
|
156
115
|
```
|
|
157
116
|
|
|
158
|
-
**image.glsl:**
|
|
117
|
+
**shaders/my-effect/image.glsl:**
|
|
159
118
|
```glsl
|
|
160
119
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
161
120
|
vec2 uv = fragCoord / iResolution.xy;
|
|
@@ -163,228 +122,293 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
163
122
|
}
|
|
164
123
|
```
|
|
165
124
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
### 3. Multiple Buffers (interacting simulations)
|
|
169
|
-
|
|
170
|
-
For reaction-diffusion, fluid dynamics, etc. All buffers can read all other buffers.
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
npm run new my-shader 2
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
**Files:**
|
|
177
|
-
```
|
|
178
|
-
demos/my-shader/
|
|
179
|
-
├── bufferA.glsl
|
|
180
|
-
├── bufferB.glsl
|
|
181
|
-
├── image.glsl
|
|
182
|
-
└── config.json
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**config.json:**
|
|
125
|
+
**shaders/my-effect/config.json:**
|
|
186
126
|
```json
|
|
187
127
|
{
|
|
188
128
|
"BufferA": {
|
|
189
|
-
"iChannel0": "BufferA"
|
|
190
|
-
"iChannel1": "BufferB"
|
|
191
|
-
},
|
|
192
|
-
"BufferB": {
|
|
193
|
-
"iChannel0": "BufferA",
|
|
194
|
-
"iChannel1": "BufferB"
|
|
129
|
+
"iChannel0": "BufferA"
|
|
195
130
|
},
|
|
196
131
|
"Image": {
|
|
197
|
-
"iChannel0": "BufferA"
|
|
198
|
-
"iChannel1": "BufferB"
|
|
132
|
+
"iChannel0": "BufferA"
|
|
199
133
|
}
|
|
200
134
|
}
|
|
201
135
|
```
|
|
202
136
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
---
|
|
137
|
+
### Using Textures
|
|
206
138
|
|
|
207
|
-
|
|
139
|
+
Place an image in your shader folder and reference it in config:
|
|
208
140
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
├── photo.jpg
|
|
216
|
-
└── config.json
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"Image": {
|
|
144
|
+
"iChannel0": "photo.jpg"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
217
147
|
```
|
|
218
148
|
|
|
219
|
-
|
|
149
|
+
With options:
|
|
220
150
|
```json
|
|
221
151
|
{
|
|
222
152
|
"Image": {
|
|
223
|
-
"iChannel0":
|
|
153
|
+
"iChannel0": {
|
|
154
|
+
"texture": "photo.jpg",
|
|
155
|
+
"filter": "nearest",
|
|
156
|
+
"wrap": "clamp"
|
|
157
|
+
}
|
|
224
158
|
}
|
|
225
159
|
}
|
|
226
160
|
```
|
|
227
161
|
|
|
228
|
-
|
|
229
|
-
```glsl
|
|
230
|
-
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
231
|
-
vec2 uv = fragCoord / iResolution.xy;
|
|
232
|
-
vec4 img = texture(iChannel0, uv);
|
|
162
|
+
### Custom Uniforms (Slider Controls)
|
|
233
163
|
|
|
234
|
-
|
|
235
|
-
float gray = dot(img.rgb, vec3(0.299, 0.587, 0.114));
|
|
164
|
+
Add interactive controls to your shader by defining uniforms in config:
|
|
236
165
|
|
|
237
|
-
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"controls": true,
|
|
169
|
+
"uniforms": {
|
|
170
|
+
"uSpeed": { "type": "float", "value": 1.0, "min": 0.0, "max": 5.0, "label": "Speed" },
|
|
171
|
+
"uColor": { "type": "vec3", "value": [1.0, 0.5, 0.2], "color": true, "label": "Color" },
|
|
172
|
+
"uAnimate": { "type": "bool", "value": true, "label": "Animate" }
|
|
173
|
+
},
|
|
174
|
+
"Image": {}
|
|
238
175
|
}
|
|
239
176
|
```
|
|
240
177
|
|
|
241
|
-
**
|
|
242
|
-
```json
|
|
243
|
-
{ "texture": "photo.jpg", "filter": "linear", "wrap": "repeat", "type": "2d" }
|
|
244
|
-
```
|
|
245
|
-
- `filter`: `"linear"` (smooth, default) or `"nearest"` (pixelated)
|
|
246
|
-
- `wrap`: `"repeat"` (tile, default) or `"clamp"` (stretch edges)
|
|
247
|
-
- `type`: `"2d"` (standard, default) or `"cubemap"` (equirectangular environment map)
|
|
178
|
+
Uniforms declared in config are **auto-injected** into your shader code — you don't need to write `uniform` declarations. Just use them directly:
|
|
248
179
|
|
|
249
|
-
**Cubemap textures:** Use `"type": "cubemap"` for equirectangular environment maps (360° panoramas). The engine will automatically convert 3D direction lookups to 2D coordinates:
|
|
250
|
-
```json
|
|
251
|
-
{ "texture": "environment.jpg", "type": "cubemap" }
|
|
252
|
-
```
|
|
253
180
|
```glsl
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
181
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
182
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
183
|
+
float t = uAnimate ? iTime * uSpeed : 0.0;
|
|
184
|
+
vec3 col = uColor * (0.5 + 0.5 * sin(uv.x * 10.0 - t));
|
|
185
|
+
fragColor = vec4(col, 1.0);
|
|
186
|
+
}
|
|
257
187
|
```
|
|
258
188
|
|
|
259
|
-
|
|
189
|
+
#### Supported Uniform Types
|
|
260
190
|
|
|
261
|
-
|
|
191
|
+
| Type | UI Control | Config Fields |
|
|
192
|
+
|------|-----------|---------------|
|
|
193
|
+
| `float` | Slider | `value`, `min` (0), `max` (1), `step` (0.01) |
|
|
194
|
+
| `int` | Discrete slider | `value`, `min` (0), `max` (10), `step` (1) |
|
|
195
|
+
| `bool` | Toggle | `value` |
|
|
196
|
+
| `vec2` | XY pad | `value`, `min` ([0,0]), `max` ([1,1]) |
|
|
197
|
+
| `vec3` | 3 sliders or color picker | `value`, `color` (false), `min`, `max`, `step` |
|
|
198
|
+
| `vec4` | 4 sliders or color+alpha picker | `value`, `color` (false), `min`, `max`, `step` |
|
|
262
199
|
|
|
263
|
-
|
|
200
|
+
Set `"hidden": true` on any scalar uniform to exclude it from the UI panel (useful for script-controlled values).
|
|
264
201
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
├── bufferA.glsl
|
|
269
|
-
├── image.glsl
|
|
270
|
-
├── photo.jpg
|
|
271
|
-
└── config.json
|
|
272
|
-
```
|
|
202
|
+
#### Array Uniforms (UBOs)
|
|
203
|
+
|
|
204
|
+
For large data arrays (positions, matrices, etc.), use array uniforms backed by Uniform Buffer Objects:
|
|
273
205
|
|
|
274
|
-
**config.json:**
|
|
275
206
|
```json
|
|
276
207
|
{
|
|
277
|
-
"
|
|
278
|
-
"
|
|
279
|
-
"
|
|
280
|
-
},
|
|
281
|
-
"Image": {
|
|
282
|
-
"iChannel0": "BufferA",
|
|
283
|
-
"iChannel1": "photo.jpg"
|
|
208
|
+
"uniforms": {
|
|
209
|
+
"matrices": { "type": "mat3", "count": 128 },
|
|
210
|
+
"matrixCount": { "type": "int", "value": 1, "hidden": true }
|
|
284
211
|
}
|
|
285
212
|
}
|
|
286
213
|
```
|
|
287
214
|
|
|
288
|
-
|
|
215
|
+
Array uniforms support types: `float`, `vec2`, `vec3`, `vec4`, `mat3`, `mat4`. They have no UI — data is provided from JavaScript via `engine.setUniformValue()`.
|
|
216
|
+
|
|
217
|
+
In the compiled shader, array uniforms are wrapped in a `layout(std140)` uniform block with a `_ub_` prefix (e.g., `_ub_matrices`). The array variable itself uses the original name, so you reference it directly in GLSL:
|
|
218
|
+
|
|
289
219
|
```glsl
|
|
220
|
+
// Auto-injected by the engine (you don't write this):
|
|
221
|
+
// layout(std140) uniform _ub_matrices {
|
|
222
|
+
// mat3 matrices[128];
|
|
223
|
+
// };
|
|
224
|
+
|
|
290
225
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
291
|
-
|
|
226
|
+
for (int i = 0; i < matrixCount; i++) {
|
|
227
|
+
vec3 p = matrices[i] * vec3(fragCoord, 1.0);
|
|
228
|
+
// ...
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
292
232
|
|
|
293
|
-
|
|
294
|
-
vec4 img = texture(iChannel1, uv); // Original image
|
|
233
|
+
See the [Configuration Reference](docs/learn/configuration.md) for all uniform types.
|
|
295
234
|
|
|
296
|
-
|
|
297
|
-
vec2 mouse = iMouse.xy / iResolution.xy;
|
|
298
|
-
float d = length(uv - mouse);
|
|
299
|
-
float brush = smoothstep(0.05, 0.0, d);
|
|
235
|
+
### Scripting (JavaScript Hooks)
|
|
300
236
|
|
|
301
|
-
|
|
302
|
-
|
|
237
|
+
For computed data that changes every frame, add a `script.js` to your shader folder:
|
|
238
|
+
|
|
239
|
+
**shaders/my-shader/script.js:**
|
|
240
|
+
```js
|
|
241
|
+
const COUNT = 32;
|
|
242
|
+
|
|
243
|
+
export function onFrame(engine, time) {
|
|
244
|
+
const data = new Float32Array(COUNT * 4);
|
|
245
|
+
for (let i = 0; i < COUNT; i++) {
|
|
246
|
+
const phase = (i / COUNT) * Math.PI * 2.0;
|
|
247
|
+
data[i * 4 + 0] = 0.5 + Math.cos(time + phase) * 0.3; // x
|
|
248
|
+
data[i * 4 + 1] = 0.5 + Math.sin(time + phase) * 0.3; // y
|
|
249
|
+
data[i * 4 + 2] = 0.02; // radius
|
|
250
|
+
data[i * 4 + 3] = i / COUNT; // hue
|
|
251
|
+
}
|
|
252
|
+
engine.setUniformValue('positions', data);
|
|
303
253
|
}
|
|
304
254
|
```
|
|
305
255
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
## Buffer Execution & Frame Timing
|
|
256
|
+
Scripts can export `setup(engine)` (called once) and/or `onFrame(engine, time, deltaTime, frame)` (called every frame).
|
|
309
257
|
|
|
310
|
-
|
|
258
|
+
The script API provides:
|
|
259
|
+
- `engine.setUniformValue(name, value)` — set any uniform
|
|
260
|
+
- `engine.getUniformValue(name)` — read current value
|
|
261
|
+
- `engine.updateTexture(name, width, height, data)` — upload a texture from JS
|
|
262
|
+
- `engine.readPixels(passName, x, y, w, h)` — read pixels from a buffer (GPU readback)
|
|
263
|
+
- `engine.width` / `engine.height` — canvas dimensions
|
|
311
264
|
|
|
312
|
-
|
|
313
|
-
- Self-reference (feedback effects)
|
|
314
|
-
- Reading buffers that haven't run yet this frame
|
|
315
|
-
- Reading buffers that have already run (you get their latest output)
|
|
265
|
+
## Channel Types
|
|
316
266
|
|
|
317
|
-
|
|
267
|
+
Channels can be bound using string shortcuts or full objects:
|
|
318
268
|
|
|
319
|
-
|
|
269
|
+
| Shorthand | Object Form | Description |
|
|
270
|
+
|-----------|-------------|-------------|
|
|
271
|
+
| `"BufferA"` | `{ "buffer": "BufferA" }` | Buffer pass output |
|
|
272
|
+
| `"photo.jpg"` | `{ "texture": "photo.jpg" }` | Image texture |
|
|
273
|
+
| `"keyboard"` | `{ "keyboard": true }` | Keyboard state |
|
|
274
|
+
| `"audio"` | `{ "audio": true }` | Microphone FFT + waveform |
|
|
275
|
+
| `"webcam"` | `{ "webcam": true }` | Live webcam feed |
|
|
276
|
+
| — | `{ "video": "clip.mp4" }` | Video file |
|
|
277
|
+
| — | `{ "script": "myData" }` | Script-uploaded texture |
|
|
320
278
|
|
|
321
279
|
## Layouts
|
|
322
280
|
|
|
323
|
-
Control how the shader is displayed
|
|
281
|
+
Control how the shader is displayed in `config.json`:
|
|
324
282
|
|
|
325
283
|
```json
|
|
326
284
|
{
|
|
327
|
-
"layout": "split"
|
|
328
|
-
"BufferA": { ... },
|
|
329
|
-
"Image": { ... }
|
|
285
|
+
"layout": "split"
|
|
330
286
|
}
|
|
331
287
|
```
|
|
332
288
|
|
|
333
|
-
| Layout | Description |
|
|
334
|
-
|
|
335
|
-
| `fullscreen` | Canvas fills
|
|
336
|
-
| `default` |
|
|
337
|
-
| `tabbed` | Tabs to switch between shader and code |
|
|
338
|
-
| `split` | Side-by-side
|
|
289
|
+
| Layout | Description |
|
|
290
|
+
|--------|-------------|
|
|
291
|
+
| `fullscreen` | Canvas fills the viewport |
|
|
292
|
+
| `default` | Centered canvas with controls |
|
|
293
|
+
| `tabbed` | Tabs to switch between shader and code |
|
|
294
|
+
| `split` | Side-by-side shader and code editor |
|
|
339
295
|
|
|
340
|
-
|
|
341
|
-
```json
|
|
342
|
-
{ "layout": "fullscreen" }
|
|
343
|
-
```
|
|
296
|
+
## Shadertoy Uniforms
|
|
344
297
|
|
|
345
|
-
|
|
346
|
-
```json
|
|
347
|
-
{ "layout": "default" }
|
|
348
|
-
```
|
|
298
|
+
All standard Shadertoy uniforms are supported:
|
|
349
299
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
300
|
+
| Uniform | Type | Description |
|
|
301
|
+
|---------|------|-------------|
|
|
302
|
+
| `iResolution` | `vec3` | Viewport resolution (width, height, 1) |
|
|
303
|
+
| `iTime` | `float` | Elapsed time in seconds |
|
|
304
|
+
| `iTimeDelta` | `float` | Time since last frame |
|
|
305
|
+
| `iFrame` | `int` | Frame counter |
|
|
306
|
+
| `iFrameRate` | `float` | Frames per second |
|
|
307
|
+
| `iMouse` | `vec4` | Mouse position and click state |
|
|
308
|
+
| `iChannel0-3` | `sampler2D` | Input textures/buffers |
|
|
309
|
+
| `iChannelResolution[4]` | `vec3[]` | Resolution of each channel |
|
|
310
|
+
| `iDate` | `vec4` | Year, month, day, time in seconds |
|
|
354
311
|
|
|
355
|
-
|
|
356
|
-
```json
|
|
357
|
-
{ "layout": "split" }
|
|
358
|
-
```
|
|
312
|
+
### Touch Uniforms (Mobile / Multi-touch)
|
|
359
313
|
|
|
360
|
-
|
|
314
|
+
| Uniform | Type | Description |
|
|
315
|
+
|---------|------|-------------|
|
|
316
|
+
| `iTouchCount` | `int` | Number of active touches |
|
|
317
|
+
| `iTouch0-2` | `vec4` | Per-touch position and state |
|
|
318
|
+
| `iPinch` | `float` | Current pinch distance |
|
|
319
|
+
| `iPinchDelta` | `float` | Change in pinch distance |
|
|
320
|
+
| `iPinchCenter` | `vec2` | Center point between pinch fingers |
|
|
361
321
|
|
|
362
322
|
## Keyboard Shortcuts
|
|
363
323
|
|
|
364
324
|
| Key | Action |
|
|
365
325
|
|-----|--------|
|
|
366
326
|
| **S** | Save screenshot (PNG) |
|
|
367
|
-
| **Space** | Play/Pause
|
|
368
|
-
| **R** | Reset to frame 0
|
|
327
|
+
| **Space** | Play/Pause |
|
|
328
|
+
| **R** | Reset to frame 0 |
|
|
369
329
|
|
|
370
|
-
|
|
330
|
+
## Recording and Export
|
|
371
331
|
|
|
372
|
-
|
|
332
|
+
### Video Recording
|
|
373
333
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
334
|
+
When `controls: true`, click the record button (or use the controls menu) to capture your shader as a WebM video. Recording uses the browser's `MediaRecorder` API at 60fps with VP9 encoding. Click the stop button to end recording — the video downloads automatically.
|
|
335
|
+
|
|
336
|
+
### HTML Export
|
|
337
|
+
|
|
338
|
+
Click the export button in the controls menu to generate a standalone HTML file with your shader embedded. The exported file includes:
|
|
339
|
+
- All shader passes and common code
|
|
340
|
+
- Current custom uniform values baked in
|
|
341
|
+
- Full WebGL2 rendering pipeline (no dependencies)
|
|
342
|
+
- Mouse interaction support
|
|
343
|
+
- Resize handling
|
|
344
|
+
|
|
345
|
+
**Limitations:** Array uniforms (UBOs), audio, webcam, video, and script hooks are not included in the export. Textures are replaced with a procedural checkerboard pattern.
|
|
346
|
+
|
|
347
|
+
## Embedding as a Library
|
|
348
|
+
|
|
349
|
+
The package exports its core classes for use in custom applications:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { App, createLayout, loadDemo } from '@stevejtrettel/shader-sandbox';
|
|
353
|
+
import type { ShaderProject, LayoutMode } from '@stevejtrettel/shader-sandbox';
|
|
378
354
|
```
|
|
379
355
|
|
|
380
|
-
|
|
356
|
+
### Exports
|
|
357
|
+
|
|
358
|
+
| Export | Description |
|
|
359
|
+
|--------|-------------|
|
|
360
|
+
| `App` | Main application class — creates canvas, engine, and animation loop |
|
|
361
|
+
| `createLayout(mode, options)` | Factory to create a layout (fullscreen, default, split, tabbed) |
|
|
362
|
+
| `applyTheme(mode)` | Apply a theme (light, dark, system) |
|
|
363
|
+
| `loadDemo(files)` | Load a shader project from bundled file data |
|
|
364
|
+
|
|
365
|
+
### Example: Embedding a shader
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { App, createLayout } from '@stevejtrettel/shader-sandbox';
|
|
369
|
+
|
|
370
|
+
const project = /* your ShaderProject object */;
|
|
381
371
|
|
|
382
|
-
|
|
372
|
+
const layout = createLayout(project.layout, {
|
|
373
|
+
container: document.getElementById('shader-container'),
|
|
374
|
+
project,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const app = new App({
|
|
378
|
+
container: layout.getCanvasContainer(),
|
|
379
|
+
project,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
if (!app.hasErrors()) {
|
|
383
|
+
app.start();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Clean up when done
|
|
387
|
+
app.dispose();
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Embed entry point
|
|
391
|
+
|
|
392
|
+
For build-time embedding of a specific shader, the package also provides an `embed()` function:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { embed } from '@stevejtrettel/shader-sandbox/embed';
|
|
396
|
+
|
|
397
|
+
const { app, destroy } = await embed({
|
|
398
|
+
container: '#my-container', // CSS selector or HTMLElement
|
|
399
|
+
pixelRatio: window.devicePixelRatio,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Later: destroy() to clean up
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Building for Production
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
shader build my-shader
|
|
409
|
+
```
|
|
383
410
|
|
|
384
|
-
|
|
385
|
-
- [Buffers and Channels](docs/learn/buffers-and-channels.md) - Multi-pass rendering
|
|
386
|
-
- [Configuration](docs/learn/configuration.md) - Full config reference
|
|
387
|
-
- [Architecture](docs/dev/architecture.md) - How the engine works
|
|
411
|
+
Output is in `dist/` - a single HTML file with embedded JavaScript that can be hosted anywhere.
|
|
388
412
|
|
|
389
413
|
## License
|
|
390
414
|
|