Your First Shader
On this page you write a fragment shader — a small program that the GPU runs for every pixel — and make it move with your music. At the end, you change it while it plays.
You need VSCode with the resonon extension, as in
Patterns, Tracks & Effects. Write each step into a
.non file, select it, and press Cmd+Enter. Every step adds to what the previous one built.
Before you start
Section titled “Before you start”Finish Signals and Modulation. Open your
project’s src/main.non, evaluate RESET; on its own, then remove that line.
The session should be silent with no earlier visuals routed to screen. You need a working
audio output and graphics display; the drums use a bundled kit.
Open the visuals window
Section titled “Open the visuals window”Visuals are drawn in a window that resonon’s server opens. Run any line first, so that VSCode starts the server and connects to it:
print("Hello, screen!");Then open the resonon sidebar and click Visuals in the Views section — or run the command Open Visuals. An empty window opens.
Draw a gradient
Section titled “Draw a gradient”A shader lives in a file of its own. Make a folder shaders beside main.non, and in it a file
plasma.glsl:
#version 300 esprecision highp float;
uniform vec2 iResolution;
out vec4 fragColor;
void main() { vec2 uv = gl_FragCoord.xy / iResolution; fragColor = vec4(uv.x, uv.y, 0.5, 1.0);}Save it, and run this in main.non:
use "std/visuals" as gfx;
let v = gfx.load_shader("plasma", "shaders/plasma.glsl");v >> screen;The window fills with a gradient: red to the right, green to the top.
- The file is the shader, in GLSL.
mainruns once for every pixel and writes its colour tofragColor. iResolutionis the size of the window in pixels. resonon gives it to every shader, souvgoes from0to1across the window.gfx.load_shader("plasma", "shaders/plasma.glsl")makes a visual from the file, with the nameplasma. The path is relative tomain.non.v >> screenshows it in the window, the way>> mastersends a track to the master.
In VSCode, a GLSL extension such as Shader languages support highlights the file.
Make it move
Section titled “Make it move”iTime is the playhead’s position in seconds. Add it to the shader:
#version 300 esprecision highp float;
uniform float iTime;uniform vec2 iResolution;
out vec4 fragColor;
void main() { vec2 uv = gl_FragCoord.xy / iResolution; float wave = 0.5 + 0.5 * sin(iTime + uv.x * 6.2831); fragColor = vec4(uv.x, uv.y, wave, 1.0);}Save the file. Nothing watches it, so run the load_shader line again, and start the
transport:
let v = gfx.load_shader("plasma", "shaders/plasma.glsl");PLAY;A blue wave moves across the gradient. You did not route it again: the visual with the name
plasma is already on the screen, and it took the new shader.
iTime moves with the playhead, as the music does: PAUSE; holds the wave where it is, and
STOP; puts it back where it started.
Give it a knob
Section titled “Give it a knob”Any other uniform that you declare is a knob that you set from resonon. Add a brightness:
#version 300 esprecision highp float;
uniform float iTime;uniform vec2 iResolution;uniform float brightness;
out vec4 fragColor;
void main() { vec2 uv = gl_FragCoord.xy / iResolution; float wave = 0.5 + 0.5 * sin(iTime + uv.x * 6.2831); fragColor = vec4(vec3(uv.x, uv.y, wave) * brightness, 1.0);}Save it, load it again, and set the knob:
let v = gfx.load_shader("plasma", "shaders/plasma.glsl");v.parameter("brightness") << 0.4;The picture is darker. As with an effect’s knob, << also takes a signal:
use "std/signals" { Sine };
v.parameter("brightness") << Sine(4).range(0.2, 1.0);The picture now fades up and down four times every cycle.
React to the drums
Section titled “React to the drums”Every track tells you how loud it is, as .rms. Add a beat, and send its level to the knob:
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.set_instrument(Sampler(Kit("CR-78")));drums << [bd [sd _ _ bd] bd sd];
v.parameter("brightness") << drums.rms.smooth(20, 300).range(0.2, 3.0);The picture flashes on every hit and fades out after it.
drums.rmsgoes from0(silent) to1(full scale). On its own, it jumps from one frame to the next, so the picture flickers..smooth(20, 300)makes it rise in 20 ms and fall over 300 ms..range(0.2, 3.0)maps0–1to0.2–3.0, because a drum track is seldom near full scale.
Change it while it plays
Section titled “Change it while it plays”Change the shader: make the wave move faster and draw rings instead of stripes.
#version 300 esprecision highp float;
uniform float iTime;uniform vec2 iResolution;uniform float brightness;
out vec4 fragColor;
void main() { vec2 uv = (gl_FragCoord.xy * 2.0 - iResolution) / min(iResolution.x, iResolution.y); float rings = 0.5 + 0.5 * sin(length(uv) * 10.0 - iTime * 4.0); fragColor = vec4(vec3(rings, rings * 0.5, 1.0 - rings) * brightness, 1.0);}Save it, and run the load_shader line again:
let v = gfx.load_shader("plasma", "shaders/plasma.glsl");The rings replace the stripes, and they still flash with the drums: the new shader kept the knob and what it is connected to. The music did not stop.
Now make a mistake: delete a ; in plasma.glsl, save it and run the line again. The last good
picture stays on the screen, the music plays on, and VSCode marks the error in plasma.glsl, on
the line you broke. Put the ; back, save, and run the line again.
Next steps
Section titled “Next steps”You wrote a shader, gave it knobs, connected it to the drums, and changed it while it played.
- Bind Audio to a Shader — levels, envelopes and the frequency spectrum
- Load a Shader From a File — reload a file and share GLSL between shaders
- Build an Operator Graph — make pictures from ready-made operators instead of GLSL