Shaders for three.js (ShaderMaterial)

three.js renders with WebGL, so your effects are GLSL. A ShaderMaterial takes vertex and fragment shader source plus uniforms. The export gives you the complete material; paste it onto a plane or any mesh that has UVs.

Things to know about GLSL

  • Uniforms such as time are values you update every frame: material.uniforms.time.value = clock.getElapsedTime().
  • UV coordinates arrive from the vertex shader as a varying vec2 vUv.
  • ShaderBattle's mesh lessons use three.js directly, with real vertex and fragment shaders on 3D shapes.

Cheat sheet

IdeaWrite
Vectorsvec2 vec3 vec4
Declare a valuefloat d = length(p);
A functionfloat circle(vec2 p, float r) { ... }
Matricesmat2 mat3, multiply with M * v (this site writes v * M for lesson rotations)
Blendmix(a, b, t)
Fractional partfract(x)
Clamp to 0..1clamp(x, 0.0, 1.0)
Remaindermod(a, b) (always positive for positive b)
Choosec ? a : b
Angle of a vectoratan(y, x)
Time in secondstime
Pixel positionuv (0..1, bottom-left)
Width / heightaspect
Entry pointvec4 frag(vec2 uv) { return …; }

Handy built-ins: sin cos abs sign floor ceil fract step smoothstep clamp min max pow exp sqrt length distance dot cross normalize reflect

Gotchas

  • Every pixel runs on its own, with no memory of other pixels.
  • Declare a function above the place you use it.
  • Prefer 2.0 over 2 in maths. This site fixes most cases for you; real engines may not.
  • Loops need a small fixed count: for (int i = 0; i < 5; i++).

Example: a glowing ring

The same shader as it is written for three.js:

vec4 frag(vec2 uv)
{
    vec2 p = (uv - 0.5) * vec2(aspect, 1.0);
    float d = length(p);
    float m = 1.0 - smoothstep(0.0, 0.02, abs(d - 0.3));
    vec3 bg = vec3(0.08, 0.08, 0.12);
    vec3 fg = vec3(0.4, 0.9, 1.0);
    return vec4(mix(bg, fg, m), 1.0);
}

Exporting to three.js

Put the material on a PlaneGeometry (or any mesh with UVs) and update the time uniform every frame.

// ShaderBattle: Glowing ring
import * as THREE from 'three';

export const material = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 },
    aspect: { value: 1 },   // width / height of your plane
  },
  vertexShader: /* glsl */ `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: /* glsl */ `
    uniform float time;
    uniform float aspect;
    varying vec2 vUv;

    vec4 frag(vec2 uv)
    {
        vec2 p = (uv - 0.5) * vec2(aspect, 1.0);
        float d = length(p);
        float m = 1.0 - smoothstep(0.0, 0.02, abs(d - 0.3));
        vec3 bg = vec3(0.08, 0.08, 0.12);
        vec3 fg = vec3(0.4, 0.9, 1.0);
        return vec4(mix(bg, fg, m), 1.0);
    }

    void main() {
      gl_FragColor = frag(vUv);
    }
  `,
});

// in your render loop:  material.uniforms.time.value = clock.getElapsedTime();

Questions

Can I use this on a 3D model and not just a plane?

Yes. Any mesh with UV coordinates works. The mesh lessons teach vertex shaders as well.

Other languages

Unity (Built-in) · Unity (URP) · Unreal · Shadertoy / WebGL · OpenGL (LWJGL, raylib) · GameMaker · LÖVE (Lua) · Godot · Apple (iOS, macOS) · WebGPU / Bevy