ShaderMate
ShaderMate is a lightweight JavaScript library that simplifies WebGL shader development. Ideal for ShaderToy users, TWGL.js developers, or beginners, it offers a streamlined way to build stunning visual effects with ease.
Install / Use
/learn @Tezumie/ShaderMateREADME
Open this repo in Codevre (free browser editor – no signup or setup)
Or start with a minimal ShaderMate template
ShaderMate : A Robust WebGL(2) Multi-Pass Playground
ShaderMate is a lightweight and powerful JavaScript library designed to simplify WebGL shader development. Whether you're transitioning from platforms like ShaderToy, familiar with WebGL frameworks like TWGL.js, or just starting your journey into the world of shaders, ShaderMate provides a streamlined environment for creating stunning visual effects.
It handles much of the boilerplate WebGL setup, allowing you to focus on writing your GLSL code. With support for both WebGL1 and WebGL2, multi-pass rendering, and automatic uniform injection, you can quickly prototype and build complex shader experiences.
Getting Started
To begin using ShaderMate, you'll typically set up a project with four core files: index.html, sketch.js, style.css, and your primary fragment shader, frag.glsl.
Use via CDN
You can quickly include ShaderMate in any HTML page using the CDN:
<script src="https://cdn.jsdelivr.net/gh/Tezumie/ShaderMate@main/src/shaderMate.js"></script>
1. index.html
This is your main HTML file. It sets up the basic page structure, includes your JavaScript and CSS files, and provides the <canvas> element where your shaders will be rendered.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ShaderMate</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="glcanvas"></canvas>
<script src="https://cdn.jsdelivr.net/gh/Tezumie/ShaderMate@main/src/shaderMate.js"></script>
<script src="sketch.js"></script>
</body>
</html>
Key Points:
<canvas id="glcanvas"></canvas>: This is the HTML element that ShaderMate will use to create the WebGL context and render your shaders. You can give it any ID, butglcanvasis the default that ShaderMate looks for. If you use a different ID, you'll need to specify it in thestartShaderMateoptions.path/to/shaderMate.js: Replace this with the actual path to the ShaderMate library JavaScript file. It should be loaded before yoursketch.jsfile.sketch.js: Your application logic and ShaderMate setup will reside here.style.css: Used for basic styling, typically to make the canvas fill the screen.
2. style.css
A minimal CSS file to ensure your canvas occupies the entire viewport:
html,
body {
margin: 0;
height: 100%;
overflow: hidden;
background: #000
}
canvas {
display: block;
width: 100%;
height: 100%
}
3. frag.glsl
This is where you'll write your GLSL fragment shader code. For a basic single-pass setup, your shader will typically contain a mainImage function, similar to ShaderToy:
// frag.glsl
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord.xy / iResolution.xy;
fragColor = vec4(uv, 0.5 + 0.5 * sin(iTime), 1.0);
}
4. sketch.js
This JavaScript file is where you initiate ShaderMate and define your shader passes.
// sketch.js
document.addEventListener('DOMContentLoaded', () => {
// Start ShaderMate with a single pass
ShaderMate.startShaderMate('frag.glsl');
});
This minimal setup will load frag.glsl and render it to the screen. The DOMContentLoaded listener ensures the canvas is ready before startShaderMate is called.
Core Concepts
Shader Passes
ShaderMate organizes your shader effects into passes. Each pass represents a single render operation using a specific fragment shader. Passes can render to the screen or to an offscreen texture (Framebuffer Object or FBO), allowing for complex multi-pass effects.
A pass is defined as an object with various properties:
{
name: 'A', // (Optional) A unique name for the pass (e.g., 'A', 'B', 'BufferA')
src: 'fragA.glsl', // Path to the fragment shader file, or a direct GLSL string
size: 'screen', // Output size: 'screen', 'half', or [width, height]
screen: false, // If true, this pass renders directly to the canvas
pingpong: false, // If true, enables double buffering for feedback effects
channels: [], // Array of input textures/buffers for this pass (iChannel0, iChannel1, etc.)
uniforms: {}, // Custom uniforms you want to send to this shader
defines: [], // Custom GLSL #define directives for this pass
float: false, // If true, the FBO texture will be high precision (float/half-float)
wrap: 'clamp', // Texture wrap mode for FBO: 'clamp' (CLAMP_TO_EDGE) or 'repeat' (REPEAT)
filter: 'linear', // Texture filter for FBO: 'linear' (LINEAR) or 'nearest' (NEAREST)
depth: false // If true, the FBO will have a depth attachment (WebGL2 only)
}
Single Pass Rendering
For simpler effects, you might only need one shader that renders directly to the screen.
Example: sketch.js for single pass
// sketch.js
document.addEventListener('DOMContentLoaded', () => {
ShaderMate.startShaderMate('frag.glsl');
});
Here, frag.glsl will be the only shader. By default, if only one pass is provided as a string, ShaderMate assumes it's a screen-rendering pass.
Alternatively, you can specify it as an array of passes:
// sketch.js
document.addEventListener('DOMContentLoaded', () => {
ShaderMate.startShaderMate([
{
name: 'MainPass',
src: 'frag.glsl',
screen: true, // Explicitly render to screen
size: 'screen'
}
]);
});
Multi-Pass Rendering
Multi-pass rendering is crucial for advanced effects like blurring, post-processing, simulations, and feedback loops. In ShaderMate, you define an array of passes, where each pass can render to an offscreen buffer (FBO) which can then be used as input for subsequent passes.
Example: sketch.js for multi-pass (Buffer A to Screen)
// sketch.js
document.addEventListener('DOMContentLoaded', () => {
ShaderMate.startShaderMate([
{
name: 'BufferA',
src: 'bufferA.glsl',
size: 'screen', // Render BufferA to an offscreen texture the size of the screen
channels: [], // No input textures for now
screen: false // Don't render this directly to screen
},
{
name: 'Image',
src: 'image.glsl',
size: 'screen',
screen: true, // Render Image pass to the screen
channels: [
{ id: 'BufferA' } // Use the output of BufferA as iChannel0
]
}
]);
});
In this setup:
bufferA.glslwill compute something and render it to an internal texture managed by ShaderMate.image.glslwill then take the output ofBufferAas itsiChannel0and render the final result to the canvas.
Ping-Pong Buffers
For feedback effects (like blurring over time, or dynamic simulations), you often need to read from the previous frame's output and write to the current frame's output. ShaderMate simplifies this with the pingpong: true option on a pass.
When pingpong is enabled, ShaderMate creates two internal buffers for that pass. In each frame, it swaps them: the iChannel0 input will receive the previous frame's output, and the current frame's rendering will go into the other buffer.
Example: sketch.js with a ping-pong buffer
// sketch.js
document.addEventListener('DOMContentLoaded', () => {
ShaderMate.startShaderMate([
{
name: 'FeedbackBuffer',
src: 'feedback.glsl',
size: 'screen',
pingpong: true, // Enable ping-pong buffering
channels: [
{ id: 'FeedbackBuffer' } // iChannel0 will receive the *previous* frame's output from this pass
],
screen: false
},
{
name: 'RenderToScreen',
src: 'render.glsl',
size: 'screen',
screen: true,
channels: [
{ id: 'FeedbackBuffer' } // iChannel0 will receive the *current* output of FeedbackBuffer
]
}
]);
});
🔄 Common Gotcha: Ping-Pong Starts Black and Stays Black
When using pingpong: true, the very first frame's buffer is all zeros (black). If your shader reads the previous frame and does:
fragColor = texture(iChannel0, uv) * 0.97;
…you're just multiplying black forever. If nothing ever adds color (e.g., on mouse input), your output stays black indefinitely.
✅ Fixes:
-
Seed the buffer on the first frame:
if (iFrame < 2) { fragColor = vec4(noise(uv) * 0.01, 0.0, 0.0, 1.0); } -
Or add a small bias:
fragColor += vec4(0.01); -
Or inject color conditionally, e.g. on mouse press:
if (iMouse.z > 0.0) { fragColor += vec4(0.5, 0.2, 1.0, 1.0); }
Inside feedback.glsl:
// feedback.glsl
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord.xy / iResolution.xy;
vec4 prevFrame = texture2D(iChannel0, uv); // Read from the previous frame
// Simple feedback: blend previous frame with
Related Skills
node-connect
343.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
92.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
343.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
343.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
