Chapter 7

JSON vs GLSL in ISF Shaders — Chapter 7

Learn how JSON sliders become GLSL uniforms in any .fs file—NAME, TYPE, TIME, isf_FragNormCoord. Copy the wave shader and test it live on this page.

By

In earlier chapters you learned what ISF is, where GLSL and ISF came from, and why shaders can outperform MP4/MOV loops on a live rig. You also installed hosts in Install ISF Shaders in Magic Visuals — Chapter 4, debugged files in the ISF Editor (Chapter 5), and built a library from isf.video (Chapter 6).

Until now, shaders felt like finished instruments. This chapter opens the case and shows the wiring: every .fs file is JSON + GLSL working as one system. Use the ISF for VJs manual index if you need to jump between modules.

JSON vs GLSL in ISF shaders: two layers of one file

An ISF shader is not “JSON or GLSL.” It is both: the JSON block between /*{ and }*/ defines what you can control on stage; the GLSL fragment shader below defines what the GPU paints every frame. When those two layers disagree—wrong NAME, mismatched TYPE, or a missing uniform—the shader breaks or ignores your sliders. This chapter maps that relationship so you can open any downloaded .fs file and know exactly what each section does.

ISF Desktop Editor showing JSON metadata panel and GLSL code panel in the same .fs shader file
An ISF file always has two layers: JSON defines controls, GLSL generates pixels. Two languages, one performance tool.

Inside the .fs file: JSON first, GLSL second

If you open a .fs file in any text editor, you will not find a binary blob. It is plain text you can copy, diff, and version-control like source code.

Every ISF shader follows the same layout:

  • Part 1 — JSON block: wrapped between /*{ and }*/. It declares interactive controls (sliders, toggles, color pickers) and their ranges.
  • Part 2 — GLSL code: everything after the JSON block. This is the GPU program that paints each pixel every frame.

Think of JSON as the script and GLSL as the actor: JSON defines what can be controlled; GLSL interprets those values and renders the image.

GLSL: the engine that paints pixels

GLSL in VJ language

GLSL (OpenGL Shading Language) tells the GPU what color to output for each pixel. For live visuals, the important part is parallelism: the GPU evaluates the fragment shader across the full frame at once, not pixel-by-pixel like a printer.

On a 1920×1080 output, that means more than two million evaluations per frame, which is why complex generative looks can still run in real time when the math stays efficient.

The main() function

Every fragment shader has a main() entry point. The final color is written to gl_FragColor as RGBA channels from 0.0 to 1.0.

A full-screen solid red shader looks like this:

void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

vec4(1.0, 0.0, 0.0, 1.0) means maximum red, no green, no blue, full opacity. If you understand that line, you understand the core idea behind every ISF shader, no matter how complex the math becomes.

ISF Desktop Editor with minimal red GLSL shader and solid red output preview
The simplest possible shader: main() writes one color to gl_FragColor, and the GPU fills the frame.

Built-in variables: TIME and isf_FragNormCoord

Static color shaders are useful for tests, but VJ workflows need motion and spatial variation. ISF passes two foundational values automatically:

  • TIME — seconds since playback started. Any formula that uses TIME animates frame by frame.
  • isf_FragNormCoord — normalized pixel position (x, y from 0.0 to 1.0). Each pixel knows where it sits on screen.

TIME drives temporal change. isf_FragNormCoord drives spatial change. Together they are the base fuel for generative ISF looks in Resolume, VDMX, and other ISF hosts.

Diagram explaining TIME animation axis and isf_FragNormCoord normalized coordinate grid
TIME creates motion over frames. isf_FragNormCoord gives each pixel a position. Most animated ISF shaders rely on both.

JSON: the control panel on top of GLSL

If GLSL is the engine, JSON is the dashboard. The metadata block between /*{ and }*/ defines what a host exposes to the performer.

A minimal JSON header looks like this:

/*{
  "DESCRIPTION": "Descriptive shader name",
  "CATEGORIES": ["Generative"],
  "INPUTS": [
    {
      "NAME": "speed",
      "TYPE": "float",
      "DEFAULT": 1.0,
      "MIN": 0.0,
      "MAX": 5.0
    }
  ]
}*/

Each object inside INPUTS becomes a control in your host UI: sliders in Resolume, faders in VDMX, knobs in Magic Visuals. You define the control once in JSON; each application maps it to its own interface.

Anatomy of one JSON input

  • NAME — internal parameter ID. Must match the variable name used in GLSL.
  • TYPE — data type (float, bool, color, int).
  • DEFAULT — startup value when the shader loads.
  • MIN — lower slider bound.
  • MAX — upper slider bound.
Annotated ISF JSON INPUT block highlighting NAME TYPE DEFAULT MIN MAX fields
Five JSON fields define how a control appears and how it maps into GLSL as a uniform variable.

The bridge: how JSON talks to GLSL

When you declare a NAME in JSON, ISF exposes it as a uniform in GLSL automatically. No extra declaration is required.

{
  "NAME": "speed",
  "TYPE": "float",
  "DEFAULT": 1.0,
  "MIN": 0.0,
  "MAX": 5.0
}
void main() {
    float t = TIME * speed; // speed comes from the JSON slider
}

When a performer moves speed from 1.0 to 3.0, the GLSL variable updates on the next frame and motion accelerates. That is the practical power of ISF for live sets: no recompile, no code edits mid-show.

Diagram linking JSON NAME field to matching GLSL uniform variable
The JSON NAME becomes a GLSL uniform. That is the direct bridge between UI controls and shader math.

Build the full shader layer by layer

We will build a real pulsing wave-field shader with two controls: speed and scale. The goal is not memorization, but reading any downloaded .fs file with confidence.

Step 1: JSON with two parameters

/*{
  "DESCRIPTION": "Pulsing wave field with speed and scale control.",
  "CATEGORIES": ["Generative", "Abstract"],
  "INPUTS": [
    {
      "NAME": "speed",
      "TYPE": "float",
      "DEFAULT": 1.0,
      "MIN": 0.0,
      "MAX": 4.0
    },
    {
      "NAME": "scale",
      "TYPE": "float",
      "DEFAULT": 6.0,
      "MIN": 1.0,
      "MAX": 20.0
    }
  ]
}*/

speed controls animation rate. scale controls pattern density. Setting MIN to 0.0 on speed is useful live: the shader freezes on the current frame when the slider hits zero.

Step 2: GLSL body

void main() {
    vec2 uv = isf_FragNormCoord * 2.0 - 1.0;
    float t = TIME * speed;
    float distance = length(uv);
    float wave = sin(distance * scale - t);
    float brightness = wave * 0.5 + 0.5;
    vec3 color = vec3(brightness * 0.2, brightness * 0.7, brightness);
    gl_FragColor = vec4(color, 1.0);
}

Step 3: complete .fs file (copy into ISF Editor)

/*{
  "DESCRIPTION": "Pulsing wave field with speed and scale control.",
  "CATEGORIES": ["Generative", "Abstract"],
  "INPUTS": [
    {
      "NAME": "speed",
      "TYPE": "float",
      "DEFAULT": 1.0,
      "MIN": 0.0,
      "MAX": 4.0
    },
    {
      "NAME": "scale",
      "TYPE": "float",
      "DEFAULT": 6.0,
      "MIN": 1.0,
      "MAX": 20.0
    }
  ]
}*/

void main() {
    vec2 uv = isf_FragNormCoord * 2.0 - 1.0;
    float t = TIME * speed;
    float distance = length(uv);
    float wave = sin(distance * scale - t);
    float brightness = wave * 0.5 + 0.5;
    vec3 color = vec3(brightness * 0.2, brightness * 0.7, brightness);
    gl_FragColor = vec4(color, 1.0);
}
ISF Desktop Editor with wave shader JSON controls and animated output preview
Full wave shader loaded in ISF Editor: JSON created the sliders, GLSL renders the animated field in real time.

Line-by-line GLSL breakdown

vec2 uv = isf_FragNormCoord * 2.0 - 1.0; — remaps coordinates from 0–1 to a centered -1–+1 space. Most generative patterns start here for symmetry.

float t = TIME * speed; — time scaled by the JSON slider. speed = 0 freezes motion; higher values accelerate it.

float distance = length(uv); — radial distance from center. This creates circular symmetry.

float wave = sin(distance * scale - t); — core pattern. scale controls ring density; subtracting t pushes rings outward over time.

float brightness = wave * 0.5 + 0.5; — remaps sine output (-1..1) into color range (0..1).

vec3 color = vec3(brightness * 0.2, brightness * 0.7, brightness); — channel weights define palette. Change multipliers to shift color without changing motion.

gl_FragColor = vec4(color, 1.0); — writes final pixel color and alpha.

Guided experiments: change one value at a time

Open the shader in ISF Desktop Editor, edit one number, save, and observe. If something breaks, undo and retry.

Experiment 1: palette swap

// Original (cyan-blue):
vec3 color = vec3(brightness * 0.2, brightness * 0.7, brightness);

// Orange-red:
vec3 color = vec3(brightness, brightness * 0.3, brightness * 0.05);

// Lime-green:
vec3 color = vec3(brightness * 0.1, brightness, brightness * 0.2);

Experiment 2: invert wave direction

float wave = sin(distance * scale - t); // expanding rings
float wave = sin(distance * scale + t); // collapsing rings

Experiment 3: change geometry

float wave = sin(distance * scale - t);      // circular
float wave = sin(uv.y * scale - t);          // horizontal lines
float wave = sin((uv.x + uv.y) * scale - t); // diagonal lines
ISF Editor output showing wave shader recolored to a warm palette
Changing three channel multipliers transforms palette instantly while keeping the same motion pattern.

JSON input types you will see in the wild

Beyond float, library shaders commonly use:

float — continuous slider for speed, intensity, scale, smoothing.

{
  "NAME": "intensity",
  "TYPE": "float",
  "DEFAULT": 0.5,
  "MIN": 0.0,
  "MAX": 1.0
}

bool — on/off toggle for modes or inversions.

{
  "NAME": "invert",
  "TYPE": "bool",
  "DEFAULT": false
}

color — RGBA picker for explicit palette control.

{
  "NAME": "baseColor",
  "TYPE": "color",
  "DEFAULT": [1.0, 0.5, 0.0, 1.0]
}

int — discrete mode selector or integer count.

{
  "NAME": "mode",
  "TYPE": "int",
  "DEFAULT": 0,
  "MIN": 0,
  "MAX": 3
}
ISF Editor UI showing float slider bool toggle and color picker controls generated from JSON
Each JSON TYPE maps to a different host control: slider, toggle, color picker, or integer selector.

Quick reference table: JSON ↔ GLSL

JSON element UI behavior GLSL meaning
"NAME": "speed" Control label and mapping key Uniform variable name
"TYPE": "float" Slider Decimal number
"TYPE": "bool" Toggle true or false
"TYPE": "color" Color picker vec4 RGBA
"TYPE": "int" Discrete selector Integer value
"DEFAULT": 1.0 Startup control position Initial uniform value
"MIN" / "MAX" Slider limits in UI Not used directly in GLSL math

Live shader preview on this page

Control speed and scale live

The demo above runs the same wave shader from this chapter. The speed and scale sliders map to JSON-defined uniforms exactly like they would in Resolume, VDMX, or Magic Visuals.

Set speed to zero and the pattern freezes on the current frame. Raise scale to tighten the rings. Under the hood, the GPU executes the GLSL once per pixel per frame while your slider values update uniforms in real time.

For official references while you practice, use editor.isf.video and the ISF documentation. Test edits locally in the ISF Desktop Editor before moving files into live host software.

Frequently Asked Questions

No. Most VJs perform with downloaded shaders and only adjust JSON-exposed controls. Understanding GLSL at a conceptual level still helps when you need to tweak behavior, fix a broken uniform name, or adapt a shader to your show.

The control will not drive the shader correctly. GLSL is case-sensitive: speed, Speed, and SPEED are different identifiers. Keep the JSON NAME and GLSL variable identical.

Each author sets MIN and MAX in JSON for their own math. If a useful range is 0–2 but the slider goes to 100, edit MAX in JSON to make the control more playable in live performance.

No. JSON only defines UI metadata. Performance depends on GLSL complexity per pixel, texture lookups, and render passes—not on how many sliders exist.

Use valid GLSL identifiers: letters, numbers, and underscores, no spaces or hyphens, and do not start with a number. Examples: speed, base_scale, color1.

Technical Appendix

This appendix centralizes quick references for this chapter, including cited links and chapter navigation for faster study and review.

Technical shader thumbnail for JSON vs GLSL in ISF Shaders — Chapter 7
Chapter 7 pulsing wave-field ISF shader thumbnail for VJs, showing JSON-driven speed and scale controls mapped to real-time GLSL output.

Referenced Links

Continue Reading