MiniNodes are reusable effect components that extend miniGL's capabilities with common image processing and visual effects. They are now fully integrated into the miniGL library and provide a clean, composable API for building complex shader pipelines.
import miniGL from './lib/miniGL/miniGL.js';
async function init() {
// Initialize miniGL (miniNodes enabled by default)
const gl = new miniGL('canvas');
// Wait for miniNodes to be ready
await gl.ready();
// Load an image
const texture = gl.image('image.jpg');
// Apply bloom effect
const bloom = gl.bloom(0.3, 0.2, 2.0, 1.5, 1.0);
bloom.connect('uTexture', texture);
gl.output(bloom);
// Animation loop
function animate() {
gl.render();
requestAnimationFrame(animate);
}
animate();
}
// Initialize everything
init().catch(console.error);Since MiniNodes use dynamic imports for optimal loading, they are loaded asynchronously. You must wait for them to be ready before using miniNode factory methods:
const gl = new miniGL('canvas');
// ❌ This will fail - miniNodes not loaded yet
// const bloom = gl.bloom(0.3, 0.2, 2.0, 1.5, 1.0);
// ✅ This is correct - wait for ready
await gl.ready();
const bloom = gl.bloom(0.3, 0.2, 2.0, 1.5, 1.0);Core nodes (shader, image, etc.) are available immediately and don't require waiting.
MiniNode is now part of the main miniGL library, not a separate import:
// ❌ Old way
import { MiniNode } from './lib/miniGL/miniNode.js';
// ✅ New way
import { MiniNode } from './lib/miniGL/miniGL.js';Each node type is in its own file and self-registers with miniGL:
// Each node file exports both the class and a register function
export class LuminanceNode extends MiniNode { ... }
export function registerLuminanceNode(gl) {
if (!gl.ignoreMiniNodes && !gl.luminance) {
gl.luminance = (...args) => {
const node = new LuminanceNode(gl, ...args);
return gl.addNode(node);
};
}
}Disabling MiniNodes
// Initialize miniGL without miniNodes (core nodes only)
const gl = new miniGL('canvas', { ignoreMiniNodes: true });Node Options All nodes accept an options object:
const bloom = gl.bloom(0.3, 0.2, 2.0, 1.5, 1.0, {
name: 'Custom Bloom',
width: 1024,
height: 1024,
filter: 'LINEAR',
wrap: 'CLAMP_TO_EDGE',
mipmap: true
});Luminance Extraction
const luminance = gl.luminance(threshold, knee, options);
luminance.connect('uTexture', sourceNode);Grayscale Conversion
const grayscale = gl.grayscale(strength, options);
grayscale.connect('uTexture', sourceNode);Texture Mixing
const mix = gl.mix(mixFactor, options);
mix.connect('uTextureA', sourceA);
mix.connect('uTextureB', sourceB);Gaussian Blur
// Single direction
const blur = gl.gaussianBlur(radius, direction, options);
blur.connect('uTexture', sourceNode);
// Horizontal/Vertical shortcuts
const blurH = gl.gaussianBlurH(radius);
const blurV = gl.gaussianBlurV(radius);
// Two-pass blur (returns { blurH, blurV, output })
const blur2Pass = gl.gaussianBlur2Pass(radius);
blur2Pass.blurH.connect('uTexture', sourceNode);Brightness/Contrast
const adjust = gl.brightnessContrast(brightness, contrast, options);
adjust.connect('uTexture', sourceNode);Saturation
const saturation = gl.saturation(amount, options);
saturation.connect('uTexture', sourceNode);Bloom Effect
const bloom = gl.bloom(threshold, knee, intensity, blurRadius, mix, options);
bloom.connect('uTexture', sourceNode);const source = gl.image('image.jpg');
const brightness = gl.brightnessContrast(0.2, 1.5);
const blur = gl.gaussianBlur(2.0, [1.0, 0.0]);
brightness.connect('uTexture', source);
blur.connect('uTexture', brightness);
gl.output(blur);const source = gl.image('image.jpg');
const grayscale = gl.grayscale(1.0);
const blur = gl.gaussianBlur(3.0, [1.0, 0.0]);
const mix = gl.mix(0.7);
grayscale.connect('uTexture', source);
blur.connect('uTexture', grayscale);
mix.connect('uTextureA', source);
mix.connect('uTextureB', blur);
gl.output(mix);const bloom = gl.bloom(0.3, 0.2, 2.0, 1.5, 1.0);
// Update parameters at runtime
bloom.updateUniform('uThreshold', 0.5);
bloom.updateUniform('uIntensity', 3.0);import { MiniNode } from './lib/miniGL/miniGL.js';
class InvertNode extends MiniNode {
constructor(gl, strength = 1.0, options = {}) {
super(gl, {
uniforms: {
uStrength: strength,
uTexture: { texture: gl.TransparentPixel }
},
...options
});
this.fragmentShader = `#version 300 es
precision highp float;
in vec2 glUV;
uniform sampler2D uTexture;
uniform float uStrength;
out vec4 fragColor;
void main() {
vec4 color = texture(uTexture, glUV);
vec3 inverted = vec3(1.0) - color.rgb;
fragColor = vec4(mix(color.rgb, inverted, uStrength), color.a);
}`;
this.inputRouting.set('uTexture', 'main');
}
}
// Register function
export function registerInvertNode(gl) {
if (!gl.ignoreMiniNodes && !gl.invert) {
gl.invert = (...args) => {
const node = new InvertNode(gl, ...args);
return gl.addNode(node);
};
}
}
// Usage
const invert = new InvertNode(gl, 1.0);
gl.addNode(invert);
invert.connect('uTexture', sourceNode);class CustomBloomNode extends MiniNode {
constructor(gl, threshold = 0.8, options = {}) {
super(gl, {
uniforms: { uThreshold: threshold, uTexture: { texture: gl.TransparentPixel } },
...options
});
// Define internal node structure
this.nodeDefinitions = [
{
key: 'luminance',
type: 'shader',
name: 'Luminance',
fragmentShader: luminanceShader
},
{
key: 'blur',
type: 'shader',
name: 'Blur',
fragmentShader: blurShader
}
];
// Define connections between internal nodes
this.nodeConnections = [
{ source: 'luminance', target: 'blur', input: 'uTexture' }
];
// Route external inputs to internal nodes
this.inputRouting.set('uTexture', 'luminance');
// Route uniforms to internal nodes
this.uniformBindings.set('uThreshold', ['luminance']);
// Set output node
this.outputNode = 'blur';
}
}lib/miniGL/
├── miniGL.js # Main library with MiniNode base class
└── miniNodes/
└── effects/
├── luminanceNode.js # Luminance extraction
├── grayscaleNode.js # Grayscale conversion
├── mixNode.js # Texture mixing
├── gaussianBlurNode.js # Gaussian blur
├── brightnessContrastNode.js # Brightness/contrast
├── saturationNode.js # Saturation adjustment
└── bloomNode.js # Bloom effect
- Internal nodes are processed in dependency order
- Uniform updates are efficiently propagated
- Texture resources are properly managed
- Supports WebGL2 float textures when available
- Async loading only loads nodes when needed
Check out these example files:
examples/bloom-test.html- Bloom effect with controlsexamples/mininode-test.html- Multiple effects showcase
Core Nodes (always available):
shader()- Custom fragment shaderspingpong()- Feedback/ping-pong buffersimage()- Image texturescanvas2D()- Canvas 2D texturesvideo()- Video texturesblend()- Blend modesmrt()- Multi-render targetsgroup()- Node grouping
MiniNodes (optional, enabled by default):
- All utility and effect nodes listed above
- Custom composable effects
- Reusable shader components