Home/Topic

GLSL Shader Basics

What is a shader?

A shader is a small program that runs on the GPU for every pixel (fragment shader) or every vertex (vertex shader) of a rendered object. Fragment shaders determine the final color of each pixel. They run massively in parallel, which is why they can produce complex visual effects at 60fps.

The minimal fragment shader

A fragment shader must output a color. In GLSL, this is typically done by setting gl_FragColor (older GLSL) or an out variable. The shader receives information about the current pixel through built-in variables and uniforms you define.

glsl
precision mediump float;

uniform float u_time;
varying vec2 vUv;

void main() {
  // vUv goes from (0,0) at bottom-left to (1,1) at top-right
  vec3 color = vec3(vUv.x, vUv.y, 0.5 + 0.5 * sin(u_time));
  gl_FragColor = vec4(color, 1.0);
}

Uniforms and varyings

Uniforms are values sent from JavaScript to the shader — they stay constant for all pixels in a draw call. Common uniforms include time, resolution, and mouse position. Varyings are values interpolated from the vertex shader (like UV coordinates or normals).

glsl
// Common uniforms you'll use
uniform float u_time;       // elapsed seconds
uniform vec2 u_resolution;  // canvas size in pixels
uniform vec2 u_mouse;       // mouse position

// Common varyings from vertex shader
varying vec2 vUv;           // texture coordinates [0,1]
varying vec3 vNormal;       // surface normal
varying vec3 vPosition;     // world position

Useful math functions

GLSL has built-in functions that are essential for creative coding: sin/cos for waves, mix for blending, smoothstep for soft transitions, fract for repeating patterns, and length/distance for circles and radial effects.

glsl
// Soft circle
float circle = 1.0 - smoothstep(0.3, 0.32, length(vUv - 0.5));

// Animated stripes
float stripes = 0.5 + 0.5 * sin(vUv.x * 20.0 + u_time * 3.0);

// Blend between two colors
vec3 a = vec3(0.1, 0.8, 0.9); // cyan
vec3 b = vec3(0.9, 0.3, 0.1); // orange
vec3 mixed = mix(a, b, vUv.y);

// Repeating tiles
vec2 tiled = fract(vUv * 5.0);

Procedural noise

Noise functions generate pseudo-random smooth patterns. They're the foundation of almost every interesting shader effect: clouds, fire, water, terrain, organic textures. The most common is Simplex/Perlin noise, but even simple hash-based noise can create compelling visuals.

glsl
// Simple hash-based noise
float hash(vec2 p) {
  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}

float noise(vec2 p) {
  vec2 i = floor(p);
  vec2 f = fract(p);
  f = f * f * (3.0 - 2.0 * f); // smoothstep

  float a = hash(i);
  float b = hash(i + vec2(1.0, 0.0));
  float c = hash(i + vec2(0.0, 1.0));
  float d = hash(i + vec2(1.0, 1.0));

  return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

Tips for the shader playground

Start by modifying existing presets rather than writing from scratch. Use u_time to animate everything. Multiply UV coordinates to zoom in/out. Use the color sliders to experiment with palettes. If you get a black screen, check for division by zero or uninitialized variables.

Try it yourself

Open the editor and experiment with what you just learned. No signup needed.

Open Shader Playground