GLSL Shader
Run a custom GLSL fragment shader over an image; the shader’s own uniforms become sliders, colour pickers and point controls
GLSL Shader
Node type: utility:glsl
Category: Editing
Description
Run a custom GLSL fragment shader over an image; the shader’s own uniforms become sliders, colour pickers and point controls
Canvas ports
These appear as port handles on the left side of the node.
| ID | Label | Details |
|---|---|---|
image |
Image | IMAGE (required) |
Sidebar config
These render as form fields in the right-side config panel when the node is selected.
No sidebar config fields.
Outputs
| ID | Label | Type |
|---|---|---|
image |
Shaded Image | IMAGE |
What it is for
A fragment shader is the smallest useful unit of image processing there is, and one node that runs an arbitrary one replaces a dozen single-purpose effect nodes. Vignette, grain, chromatic aberration, halftone, duotone, posterise, scanlines, bloom, a bespoke grade. All of them are a few lines of GLSL, and this node runs them on your wired image in the browser for free.
It is also the most agent-friendly node in the product: an LLM can write GLSL, so "give me a warm halftone with an adjustable dot size" is one prompt away from a working control panel.
The contract
You write a fragment shader BODY. The node declares these for you, so do not declare them yourself:
| Declared for you | What it is |
|---|---|
uniform sampler2D tex |
The wired image |
uniform vec2 resolution |
Output size in pixels |
in vec2 uvs |
Texture coordinates, 0..1 |
out vec4 outColor |
What you write your pixel to |
The #version line and the precision qualifiers are ours too. If you paste a
shader that carries its own #version, it is ignored rather than breaking the
compile.
The smallest shader that works:
void main() {
outColor = texture(tex, uvs);
}
Uniforms become controls
Every float, vec2 and vec3 uniform you declare turns into a control
in the editor, and its value is saved with the node.
| You declare | You get |
|---|---|
uniform float amount; |
A slider, 0..1 |
uniform float amount; // min -2 max 2 |
A slider, -2..2 |
uniform vec2 center; |
An x / y pair |
uniform vec3 tint; |
A colour picker |
Add default to the same comment to set the starting value:
// min 0 max 4 default 1.5, or // default #ff8800 for a colour.
Precision qualifiers are fine: uniform highp float amount; reads exactly
like the plain form.
If you give a range but no default, the control starts at the MIDDLE of it, so a pasted shader shows you its effect instead of previewing as a no-op.
Anything else is refused with a message rather than quietly ignored: arrays,
uniform float a, b; on one line, vec4, int, mat3, extra samplers.
A control the node cannot build is a uniform the node would never bind, and a
shader running on an unbound uniform is worse than one that will not save.
Two more things it will tell you about rather than swallow: a declaration missing its semicolon, and a block comment you never closed (which otherwise hides every declaration after it).
What you cannot do
#extension directives do not work. They have to precede everything else in
the unit, and the node's own declarations come first, so the driver rejects
them. Nothing outside core GLSL ES 3.0 is available.
There is one texture, tex, and no way to add a second, so multi-input
effects (blends, displacement maps) are out. The output is one image at the
source image's size.
Nothing stops a loop that never ends. A for or while whose condition never
goes false compiles fine, and then it runs on the real GPU: the tab freezes
until the browser's watchdog kills the WebGL context, and the editor comes back
with a lost-context error rather than an image. Nothing is charged and nothing
is saved, but you will lose the frame you were rendering. Bound your loops with
a constant.
Presets
Three shaders ship in the preset menu (vignette, film grain, chromatic aberration) plus a passthrough to start from. They are just source text: load one, then change it. There is nothing special about a preset once it is in your editor.
Errors
Compile errors appear under the source with the line numbers remapped onto YOUR lines, not the assembled ones. Save is disabled while any error is showing, and a shader that fails at full resolution writes nothing at all, so the node keeps serving its last good image instead of a blank one.
What it costs
Nothing. The shader runs on your own GPU and the result goes straight to storage. It is a free node, so it has no Run button.
It does not re-render itself during a workflow run, though. A run echoes the image you last saved from the editor — the shader only ever executes in the editor, on your machine. So if the card is showing Re-render needed, running the workflow will not clear it; open the editor and save. Until you do, the run hands downstream the previous image (or, if you have never saved one, the upstream image untouched).
Re-render needed
The output is stamped with the identity of the shader source, every uniform value, and the upstream image. Change any of them and the card shows Re-render needed. It keeps serving the previous image while that badge is up, deliberately: dropping it would fall back to the unshaded upstream photo, and a paid node downstream would then process the wrong image while looking perfectly healthy. Open the editor and save again to clear it.
Auto-generated from the Wireflow node registry.