> ## Documentation Index
> Fetch the complete documentation index at: https://docs.comfy.org/llms.txt
> Use this file to discover all available pages before exploring further.

# GLSLShader - Writing Custom GLSL Shaders for ComfyUI

> Learn how to write custom GLSL ES 3.00 fragment shaders for the GLSLShader node in ComfyUI, including available uniforms, multi-pass ping-pong rendering, and multiple render targets.

The **GLSL Shader** node lets you write custom fragment shaders in **GLSL ES 3.00** (WebGL 2.0 compatible) to process images directly on the GPU. You can create image effects like blurs, color grading, film grain, glow, and much more - all running at GPU speed.

<Note>
  The GLSL Shader node is currently marked as **experimental**, so the node may be updated and extended in future releases.
</Note>

## Minimal Shader

The simplest possible shader - a passthrough that outputs the input image unchanged:

```glsl theme={null}
#version 300 es
precision highp float;

uniform sampler2D u_image0;

in vec2 v_texCoord;
layout(location = 0) out vec4 fragColor0;

void main() {
    fragColor0 = texture(u_image0, v_texCoord);
}
```

<Note>
  **Why GLSL ES 3.00?** Shaders need to run in two environments: the **browser** (via WebGL 2.0, which only supports GLSL ES 3.00) for live preview in the ComfyUI frontend, and the **Python backend** (via desktop OpenGL) when the workflow executes. GLSL ES 3.00 is the common denominator that works in both places.
</Note>

## Available Uniforms

These uniforms are automatically set by ComfyUI. You don't need to declare all of them - only declare the ones you use.

### Images

| Uniform                 | Type        | Description                                                                                                                                            |
| ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `u_image0` – `u_image4` | `sampler2D` | Input images (up to 5). Sampled with `texture(u_image0, v_texCoord)`. Images are RGBA float textures with linear filtering and clamp-to-edge wrapping. |

### Floats

| Uniform                  | Type    | Description                                                                                |
| ------------------------ | ------- | ------------------------------------------------------------------------------------------ |
| `u_float0` – `u_float19` | `float` | Up to 20 user-controlled float values. Mapped from the **floats** input group on the node. |

### Integers

| Uniform              | Type  | Description                                                                                |
| -------------------- | ----- | ------------------------------------------------------------------------------------------ |
| `u_int0` – `u_int19` | `int` | Up to 20 user-controlled integer values. Mapped from the **ints** input group on the node. |

<Note>
  **Using int uniforms as dropdowns:** Int uniforms pair well with the **Custom Combo** node's index output - users pick an option from a dropdown and the shader receives the selected item's index.

  ```glsl theme={null}
  const int BLEND_SCREEN  = 0;
  const int BLEND_OVERLAY = 1;
  const int BLEND_MULTIPLY = 2;

  // ...

  if (u_int0 == BLEND_SCREEN) {
      // ...
  }
  ```
</Note>

### Booleans

| Uniform               | Type   | Description                                                                                 |
| --------------------- | ------ | ------------------------------------------------------------------------------------------- |
| `u_bool0` – `u_bool9` | `bool` | Up to 10 user-controlled boolean values. Mapped from the **bools** input group on the node. |

### Curves (1D LUTs)

| Uniform                 | Type        | Description                                                                                                                       |
| ----------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `u_curve0` – `u_curve3` | `sampler2D` | Up to 4 user-editable curve LUTs from the **curves** input group. Each curve is a 1D lookup table stored as a single-row texture. |

<Note>
  **Using curve uniforms:** Curves let users draw arbitrary tone-mapping graphs in the UI (e.g. for contrast, gamma, per-channel grading, or any custom `input → output` remap). Sample the curve using your input value as the X coordinate - remember to clamp it to `[0, 1]` first:

  ```glsl theme={null}
  float applyCurve(sampler2D curve, float value) {
      return texture(curve, vec2(clamp(value, 0.0, 1.0), 0.5)).r;
  }

  // Usage: remap each RGB channel through a master curve
  color.r = applyCurve(u_curve0, color.r);
  color.g = applyCurve(u_curve0, color.g);
  color.b = applyCurve(u_curve0, color.b);
  ```

  Common uses: master RGB curves, per-channel R/G/B curves, luminance-driven remaps, custom gamma, and any effect where you want the user to shape a response curve visually.
</Note>

### Resolution

| Uniform        | Type   | Description                                                                                                                                                                     |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `u_resolution` | `vec2` | **Output** framebuffer dimensions in pixels (`width, height`). This is the size you're writing to, which may differ from any input image's size when `size_mode` is `"custom"`. |

<Note>
  **Computing texel size for sampling:** Don't use `1.0 / u_resolution` to step one pixel in an input texture. `u_resolution` is the *output* size, which may not match the input's size. Instead use `textureSize()` on the actual texture you're sampling:

  ```glsl theme={null}
  vec2 texel = 1.0 / vec2(textureSize(u_image0, 0));
  ```

  Use `u_resolution` only when you need the output framebuffer dimensions themselves (e.g. computing `gl_FragCoord.xy / u_resolution` to get screen-space UVs).
</Note>

### Multi-Pass

| Uniform  | Type  | Description                                                                                                                                                    |
| -------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `u_pass` | `int` | Current pass index (0-based). Only meaningful when using `#pragma passes` - see [Multi-Pass Ping-Pong Rendering](#multi-pass-ping-pong-rendering) for details. |

### Vertex Shader Output

| Varying      | Type   | Description                                                                  |
| ------------ | ------ | ---------------------------------------------------------------------------- |
| `v_texCoord` | `vec2` | Texture coordinates ranging from (0,0) at bottom-left to (1,1) at top-right. |

## Multiple Outputs (MRT)

The node supports up to **4 simultaneous outputs** using Multiple Render Targets. Declare additional outputs with explicit locations:

```glsl theme={null}
#version 300 es
precision highp float;

uniform sampler2D u_image0;

in vec2 v_texCoord;
layout(location = 0) out vec4 fragColor0;
layout(location = 1) out vec4 fragColor1;
layout(location = 2) out vec4 fragColor2;
layout(location = 3) out vec4 fragColor3;

void main() {
    vec4 color = texture(u_image0, v_texCoord);
    fragColor0 = vec4(vec3(color.r), 1.0);  // Red channel
    fragColor1 = vec4(vec3(color.g), 1.0);  // Green channel
    fragColor2 = vec4(vec3(color.b), 1.0);  // Blue channel
    fragColor3 = vec4(vec3(color.a), 1.0);  // Alpha channel
}
```

Each `fragColor` maps to the corresponding `IMAGE` output on the node. ComfyUI auto-detects which outputs you use - unused outputs will be black.

## Multi-Pass Ping-Pong Rendering

Some effects (like separable blur) need multiple passes over the image. Use the `#pragma passes N` directive to enable this:

```glsl theme={null}
#version 300 es
#pragma passes 2
precision highp float;

uniform sampler2D u_image0;
uniform float u_float0;  // Blur radius
uniform int u_pass;

in vec2 v_texCoord;
layout(location = 0) out vec4 fragColor0;

void main() {
    vec2 texel = 1.0 / vec2(textureSize(u_image0, 0));
    int radius = int(ceil(u_float0));

    // Pass 0 = horizontal blur, Pass 1 = vertical blur
    vec2 dir = (u_pass == 0) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);

    vec4 color = vec4(0.0);
    float total = 0.0;

    for (int i = -radius; i <= radius; i++) {
        vec2 offset = dir * float(i) * texel;
        float w = 1.0;  // box blur weight
        color += texture(u_image0, v_texCoord + offset) * w;
        total += w;
    }

    fragColor0 = color / total;
}
```

### How ping-pong works

1. **Pass 0**: Reads from the original `u_image0` input, writes to an internal ping-pong texture.
2. **Pass 1–N**: Reads from the *previous pass output* via `u_image0` (the binding is swapped automatically), writes to the other ping-pong texture.
3. **Final pass**: Writes to the actual output framebuffer (`fragColor0`).

<Note>
  When using multi-pass with MRT (multiple outputs), only the first output (`fragColor0`) participates in ping-pong. The final pass writes all outputs.
</Note>

## Examples

### Grayscale Conversion

```glsl theme={null}
#version 300 es
precision highp float;

uniform sampler2D u_image0;
in vec2 v_texCoord;
layout(location = 0) out vec4 fragColor0;

void main() {
    vec4 color = texture(u_image0, v_texCoord);
    float gray = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
    fragColor0 = vec4(vec3(gray), color.a);
}
```

### Image Blending

Blend two input images using a float parameter as the mix factor:

```glsl theme={null}
#version 300 es
precision highp float;

uniform sampler2D u_image0;
uniform sampler2D u_image1;
uniform float u_float0;  // mix factor [0.0 – 1.0]

in vec2 v_texCoord;
layout(location = 0) out vec4 fragColor0;

void main() {
    vec4 a = texture(u_image0, v_texCoord);
    vec4 b = texture(u_image1, v_texCoord);
    fragColor0 = mix(a, b, clamp(u_float0, 0.0, 1.0));
}
```

## Using an LLM to Generate Shaders

You can use any LLM (Claude, ChatGPT, etc.) to write GLSL shaders for you. Copy the following prompt and fill in your desired effect:

````markdown theme={null}
Write a GLSL ES 3.00 fragment shader for ComfyUI's GLSLShader node.

**Effect I want:** [DESCRIBE YOUR DESIRED EFFECT HERE]

**Requirements:**
- Must start with `#version 300 es`
- Use `precision highp float;`
- The vertex shader provides `in vec2 v_texCoord` (0–1 UV coordinates, bottom-left origin)
- Output to `layout(location = 0) out vec4 fragColor0` (RGBA)
- Additional outputs available: `fragColor1`, `fragColor2`, `fragColor3` at locations 1–3

**Template to follow (minimal passthrough shader - use this exact structure):**

```glsl
#version 300 es
precision highp float;

uniform sampler2D u_image0;

in vec2 v_texCoord;
layout(location = 0) out vec4 fragColor0;

void main() {
    fragColor0 = texture(u_image0, v_texCoord);
}
```

**Available uniforms (declare only what you use):**
- `uniform sampler2D u_image0;` through `u_image4` - up to 5 input images (RGBA float, linear filtering, clamp-to-edge)
- `uniform vec2 u_resolution;` - **output framebuffer** width and height in pixels. This is NOT the input texture size (they can differ when the user sets a custom output size). **To step one pixel in an input texture, use `vec2 texel = 1.0 / vec2(textureSize(u_image0, 0));` - do NOT use `1.0 / u_resolution` for this.**
- `uniform float u_float0;` through `u_float19;` - up to 20 user-controlled float parameters
- `uniform int u_int0;` through `u_int19;` - up to 20 user-controlled integer parameters. Tip: int uniforms can be wired from a **Custom Combo** node's index output, so users select from a dropdown and the shader receives the index. Use this for mode selection (e.g. blend mode, blur type). Define named constants for each option and branch on those - do NOT compare against raw integer literals. For example: `const int BLUR_GAUSSIAN = 0; const int BLUR_BOX = 1; ... if (u_int0 == BLUR_GAUSSIAN) { ... }`.
- `uniform bool u_bool0;` through `u_bool9;` - up to 10 user-controlled boolean parameters (use for feature toggles)
- `uniform sampler2D u_curve0;` through `u_curve3;` - up to 4 user-editable 1D LUT curves. Sample with `texture(u_curve0, vec2(clamp(x, 0.0, 1.0), 0.5)).r` where `x` is 0–1. Use for tone curves, remapping, etc.
- `uniform int u_pass;` - current pass index (when using multi-pass)

**Multi-pass rendering:**
- Add `#pragma passes N` on the second line to enable N passes
- On pass 0, `u_image0` is the original input; on subsequent passes it contains the previous pass output
- Use `u_pass` to vary behavior per pass (e.g., horizontal vs. vertical blur)

**Important constraints:**
- GLSL ES 3.00 only - no GLSL 1.x `varying`/`attribute`, no `gl_FragColor`
- No `#include`, no external textures, no custom vertex shader
- Document each uniform with a comment showing its purpose and expected range
````

### Example prompt fill-in

> **Effect I want:** A chromatic aberration effect that splits RGB channels outward from the center of the image. u\_float0 controls the strength of the offset (0 = no effect, 10 = extremely strong). The offset should scale with distance from the center.
