Skip to content

RadianceLightweight, reactive WebGL library

A toolkit for building shader-driven experiences.

✨ Simple API

Radiance takes care of the WebGL boilerplate, so you can focus on your shader.

import { glCanvas, bloom, linearToneMapping } from "@radiancejs/gl";
import fragment from "./cube.frag?raw";
import "./styles.css";

glCanvas({
  canvas: "#glCanvas",
  fragment,
  postEffects: [
    bloom({ radius: 0.5, mix: 0.8 }), //
    linearToneMapping({ exposure: 1 }),
  ],
  uniforms: {
    uCubeSize: 1,
    uTime: ({ time }) => time / 500,
    uResolution: ({ canvasResolution }) => canvasResolution,
  },
  colorSpace: "display-p3",
});

🪶 Lightweight

The lightest way to render a simple shader, after vanilla WebGL.

vanilla WebGL
0.79 kB
68 sloc
@radiancejs/gl0.12.1
6 kB
6 sloc
twgl.js7.0.0
6.51 kB
39 sloc
ogl1.0.11
12.7 kB
27 sloc
three0.185.1
124.22 kB
37 sloc

weight = gzip sizesloc = source lines of code (JS) to render the shader

Updated Aug 14, 2026. See the benchmark.

↺ Reactive

Radiance automatically re-renders the canvas when uniforms are updated, or when the canvas is resized.

import { glCanvas } from "@radiancejs/gl";
import { Pane } from "tweakpane";
import fragment from "./uniforms.frag?raw";
import "./styles.css";

const { uniforms, onAfterRender } = glCanvas({
  canvas: "#glCanvas",
  fragment,
  uniforms: {
    uRadius: 0.03,
    uSize: 0.2,
    uRotation: 0.1,
    uPosition: [0.5, 0.5],
    uResolution: ({ canvasResolution }) => canvasResolution,
  },
});

const pane = new Pane({ title: "Uniforms" });

// updating the uniforms object will trigger a re-render
pane.addBinding(uniforms, "uSize", { min: 0.01, max: 0.3 });
pane.addBinding(uniforms, "uRadius", { min: 0, max: 0.2 });
pane.addBinding(uniforms, "uRotation", { min: 0, max: 2 * Math.PI });

pane
  .addBinding({ uPosition: { x: 0.5, y: 0.5 } }, "uPosition", {
    x: { min: -1, max: 1 },
    y: { min: -1, max: 1 },
  })
  .on("change", (e) => {
    // uniforms.uPosition is typed : [number, number]
    uniforms.uPosition = [e.value.x / 2 + 0.5, -e.value.y / 2 + 0.5];
  });

const renderCount = document.querySelector("#renderCount")!;
onAfterRender(() => {
  renderCount.firstChild!.nodeValue = `${Number(renderCount.textContent) + 1}`;
});

🛠️ Fully typed

Radiance provides type-safety for everything, including uniforms.

ts
const { uniforms } = glCanvas({
  canvas,
  fragment,
  uniforms: {
    uPointer: [0, 0], 
  },
});

uniforms.uPointer = 42; // Type 'number' is not assignable to type 'number[]'.

Released under the MIT License.