Shaders
Custom shaders let you generate or process cell data on the GPU. textmode.js uses WebGL2 and expects GLSL ES 3.00 for custom fragment shaders.
Use shaders when a visual is easier to express procedurally or when a CPU loop would be too slow. (。◕‿◕。)
Filter shaders
createFilterShader() creates a shader from fragment source or a file path:
let waveShader;
t.setup(async () => {
waveShader = await t.createFilterShader("./wave.frag");
});
t.draw(() => {
t.shader(waveShader);
t.setUniform("u_time", t.secs);
t.rect(t.grid.cols, t.grid.rows);
t.shader(null);
});shader() sets the current shader. Pass null or call resetShader() to return to the default drawing pipeline.
Uniforms
Set one uniform with setUniform():
t.setUniform("u_time", t.secs);Set multiple uniforms with setUniforms():
t.setUniforms({
u_time: t.secs,
u_center: [0.5, 0.5],
});MRT outputs
Custom filter shaders write three render targets:
#version 300 es
precision highp float;
in vec2 v_uv;
uniform float u_time;
layout(location = 0) out vec4 o_character;
layout(location = 1) out vec4 o_primaryColor;
layout(location = 2) out vec4 o_secondaryColor;
void main() {
float wave = sin(v_uv.x * 12.0 + u_time) * 0.5 + 0.5;
o_character = vec4(wave, 0.0, 0.0, 0.0);
o_primaryColor = vec4(wave, 1.0 - wave, 1.0, 1.0);
o_secondaryColor = vec4(0.0, 0.0, 0.0, 1.0);
}o_character stores glyph selection, transform flags, and character rotation. o_primaryColor stores the glyph color. o_secondaryColor stores the cell color.
Character attachment packing
The o_character attachment uses these channels:
- red: lower 8 bits of the glyph index
- green: upper 8 bits of the glyph index
- blue: transform flags
- alpha: character rotation, where
0.0to1.0maps to0to360degrees
int glyph = 65;
float low = float(glyph % 256) / 255.0;
float high = float(glyph / 256) / 255.0;
o_character = vec4(low, high, 0.0, 0.0);Transform flags are packed into the blue channel:
- bit 0: invert colors
- bit 1: flip X
- bit 2: flip Y
int invertFlag = 1;
int flipXFlag = 0;
int flipYFlag = 1;
o_character.b = float(invertFlag | (flipXFlag << 1) | (flipYFlag << 2)) / 255.0;Full shader example
Framebuffers and shaders
Framebuffers are the usual way to build multi-pass shader pipelines:
fb.begin();
t.shader(noiseShader);
t.setUniform("u_time", t.secs);
t.rect(t.grid.cols, t.grid.rows);
t.shader(null);
fb.end();
t.image(fb);See Framebuffers for offscreen rendering patterns.