GLSL Shader
A mini Shadertoy living inside your ComfyUI graph
- images
- floats
- ints
- bools
- curves
- IMAGE0
- IMAGE1
- IMAGE2
- IMAGE3
The whole image-editing toolbox, in one node
Every custom effect you've ever wanted - a film-grade color grade, a proper vignette, a Gaussian blur that doesn't make you install a pack, per-channel tone curves, depth-based bokeh - can be written as a fragment shader, and this node runs it on your GPU. It's ComfyUI core's answer to the post-processing packs like WAS Node Suite and Impact Pack that everyone historically installed for this stuff: instead of one node per effect, you get an open-ended one that does any effect you can express in GLSL. It landed in core in February 2026 and is still marked experimental, so expect the API to shift under you - but it's real, and it works.
You'll reach for it after VAE decode, before the Save node, or anywhere you'd drop an Image Filter in an img2img or upscaling pass. If you've ever poked at Shadertoy, this is that, wired into your graph instead of a browser tab.
How it works
The node renders your fragment shader over a fullscreen triangle (the vertex shader is fixed; you only write the fragment stage). A fragment shader runs once per output pixel, so you're writing a tiny program that, given a pixel's texture coordinate, decides its color. The fun bit: the same shader runs twice. In your browser it runs live as WebGL 2.0, giving you a real-time preview as you type; when you execute the workflow, the backend spins up a headless OpenGL ES context (via EGL/ANGLE, with a Vulkan fallback for machines with no display server) and renders the real frames. Both environments require GLSL ES 3.00 - that's the compatibility denominator, and it's why the syntax is what it is.
The inputs that matter
The one that matters is the first: fragment_shader, the shader source. This is the default - a passthrough that outputs the image unchanged, and your starting point:
#version 300 es
precision highp float;
uniform sampler2D u_image0;
uniform vec2 u_resolution;
in vec2 v_texCoord;
layout(location = 0) out vec4 fragColor0;
void main() {
fragColor0 = texture(u_image0, v_texCoord);
}
Everything else is autogrow - click to add more. The mapping is fixed:
- images →
u_image0…u_image4(sampler2D). Up to 5 inputs, and no, they don't need to match in size - the node scales them into the framebuffer. - floats →
u_float0…u_float19. Your knobs: mix factors, blur radius, strength. - ints →
u_int0…u_int19. Great for mode switches (blend mode, filter type). - bools →
u_bool0…u_bool9. Feature toggles. - curves →
u_curve0…u_curve3, 1D LUTs from the core Curve Editor utility node. Sample one withtexture(u_curve0, vec2(clamp(x, 0.0, 1.0), 0.5)).r. This is your interactive tone curve - draw the grade, don't hardcode it. u_resolutionis always available, and size_mode decides the output size:from_input(first input's dimensions) orcustom(manual width/height, up to 16384).
Outputs and multi-pass
You get four IMAGE outputs (IMAGE0–IMAGE3), declared as fragColor0…fragColor3 at layout locations 0–3 - that's MRT, multiple render targets. The node detects which you actually use; unused ones come back black. A neat pattern: split an image into R, G, B and Bokeh mask in one render, then composite them downstream.
For separable effects like a Gaussian blur, add #pragma passes 2 on the second line and use the u_pass uniform to branch (pass 0 = horizontal, pass 1 = vertical). Each pass reads the previous one's output through u_image0 automatically.
Where people get burned
The big three, from the source and the docs:
- Old GLSL won't compile. No
gl_FragColor, novarying/attribute. GLSL ES 3.00 only. Compile errors surface as a RuntimeError with the GLSL info log in your console - not in a pretty dialog, so keep the console open. u_resolutionis the output size, not your input's. If they differ,1.0 / u_resolutiongives wrong texel steps. UsetextureSize(u_image0, 0)to step one input pixel.- Preview vs. backend drift. Your browser preview is WebGL 2.0; the backend is GLES 3.0 via ANGLE. Keep to the shared subset (that's the whole reason it's ES 3.00) or the two won't match. On a headless box with broken GPU drivers, the backend error lists every EGL strategy it tried before giving up.
It ships with ComfyUI core - no install, no Manager, no model files to fetch. It's a tool for people who think in pixels, and honestly, once you've dialed in one shader you'll never look at the WAS Node Suite the same way again.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| fragment_shader | STRING | #version 300 es precision highp float; uniform sampler2D u_image0; uniform vec2 u_resolution; in vec2 v_texCoord; layout(location = 0) out vec4 fragColor0; void main() { fragColor0 = texture(u_image0, v_texCoord); } | GLSL fragment shader source code (GLSL ES 3.00 / WebGL 2.0 compatible) |
| size_mode | COMBO | Output size: 'from_input' uses first input image dimensions, 'custom' allows manual size | |
| images | COMFY_AUTOGROW_V3 | Images are available as u_image0-4 (sampler2D) in the shader code | |
| floats | COMFY_AUTOGROW_V3 | Floats are available as u_float0-19 in the shader code | |
| ints | COMFY_AUTOGROW_V3 | Ints are available as u_int0-19 in the shader code | |
| bools | COMFY_AUTOGROW_V3 | Booleans are available as u_bool0-9 (bool) in the shader code | |
| curves | COMFY_AUTOGROW_V3 | Curves are available as u_curve0-3 (sampler2D, 1D LUT) in the shader code. Sample with texture(u_curve0, vec2(x, 0.5)).r |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| IMAGE0 | IMAGE | Available via layout(location = 0) out vec4 fragColor0 in the shader code |
| IMAGE1 | IMAGE | Available via layout(location = 1) out vec4 fragColor1 in the shader code |
| IMAGE2 | IMAGE | Available via layout(location = 2) out vec4 fragColor2 in the shader code |
| IMAGE3 | IMAGE | Available via layout(location = 3) out vec4 fragColor3 in the shader code |