Skip to content
Rendering · Pipeline stagesBeginner

Shader anatomy & data flow

What goes into a shader and what comes out of it, from the vertex buffer to the color on screen.

Live demo coming soon

How it works

A shader never runs on its own. Code on the CPU (JavaScript, in WebGL2) prepares everything and then issues a draw call: “draw these 3000 vertices as triangles, with this program”. The program is a pair of shaders linked together, a vertex shader and a fragment shader, and the GPU runs them thousands or millions of times in parallel during that one call. Each run sees only its own data, so the whole craft of writing shaders starts with knowing what data reaches each stage and how.

Four kinds of data

  1. Attributes are per vertex: a position, a normal, a UV, read from buffers the host code filled. The vertex shader declares them with in, and each run gets the values of one vertex.
  2. Uniforms are per draw call: the time, the canvas size, a color, a transform matrix. The host sets them with gl.uniform*() before drawing, and every run of both shaders sees the same value. Textures arrive this way too, as sampler2D uniforms.
  3. Varyings carry data from the vertex shader to the fragment shader. The vertex shader writes an out, the fragment shader reads an in with the same name and type, and in between the rasterizer blends the three vertices’ values for every pixel of the triangle.
  4. Outputs leave the fragment shader with out vec4 and land in the framebuffer: the canvas, or a texture the host chose to draw into.

Some values are built in rather than declared. The vertex shader must write gl_Position, where the vertex lands on screen. The fragment shader can read gl_FragCoord, the pixel’s position in the window.

A complete program

This is the smallest useful pair in GLSL ES 3.00, the language of WebGL2. The vertex shader passes the UV along and places the vertex; the fragment shader turns the interpolated UV into a color that pulses with time:

#version 300 es
// vertex shader
in vec3 aPosition;              // attribute: this vertex's position
in vec2 aUv;                    // attribute: this vertex's UV
uniform mat4 uModelViewProjection; // uniform: the same for the whole draw
out vec2 vUv;                   // varying: handed to the fragment shader

void main() {
    vUv = aUv;
    gl_Position = uModelViewProjection * vec4(aPosition, 1.0);
}
#version 300 es
// fragment shader
precision highp float;

in vec2 vUv;          // the varying, interpolated for this pixel
uniform float uTime;  // seconds, set by the host every frame
out vec4 fragColor;   // what lands in the framebuffer

void main() {
    float pulse = 0.5 + 0.5 * sin(uTime);
    fragColor = vec4(vUv, pulse, 1.0);
}

A few lines here are boilerplate that the compiler insists on. #version 300 es must be the very first line, with nothing before it, not even a comment or a blank line; a shader without it is read as the older GLSL ES 1.00. The fragment shader has no default precision for floats, so precision highp float; is required there; the vertex shader defaults to highp. When the program is linked, every in of the fragment shader must match an out of the vertex shader by name and type, or linking fails.

The same data, hidden

Most tools write this boilerplate for you and hand you only the interesting part:

  • Shadertoy-style pages draw one rectangle over the whole canvas and call your mainImage(out vec4 fragColor, in vec2 fragCoord) for every pixel, with uniforms like the time and resolution already declared.
  • Godot writes main(), the attributes and the matrices itself. You fill in vertex() and fragment() functions and talk to it through built-ins: read UV, NORMAL and TIME, write VERTEX in vertex() and ALBEDO or ALPHA in fragment(). A uniform you declare becomes a property in the material’s inspector (a slider when it has hint_range), and a varying carries your own data between the two functions.
  • The cards on this site go one step further: you write vec4 effect(Inputs i) and read i.uv, i.time, i.normal and the rest from one struct. The site’s frame declares the real attributes, uniforms and varyings around it, for WebGL2 and for Godot alike (see UV coordinates).

The names change from tool to tool, but the flow doesn’t: per-vertex data in, per-draw settings beside it, values handed from vertex to fragment, one color out.

Further reading