Skip to content

Instantly share code, notes, and snippets.

@gimulnautti
Created August 2, 2025 09:20
Show Gist options
  • Select an option

  • Save gimulnautti/788840efa47e8a71e903849b1bcfe9dc to your computer and use it in GitHub Desktop.

Select an option

Save gimulnautti/788840efa47e8a71e903849b1bcfe9dc to your computer and use it in GitHub Desktop.
Hassy Team WebGL Scroller
<!DOCTYPE html>
<canvas></canvas>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<image src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAAAFCAYAAACgnQlfAAAAAXNSR0IB2cksfwAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+kIAQgQJuOPCloAAACySURBVEjHrVVJEsAgCCP8/8/05AwybEK5WItA0ERBRELKRIQAXOOx6RzAld/zv9Sr1ldjhk9j7MTrPfP68/wZviofU8NsQwCuf5W/yuf5svxeTLWmW18f1rSWJag9aP3dxXtycgZ6WuA1PvN7m+OpYmMikirbMv51P7KeIpVHWLirqCmgKP5FQVUTHcJMFR9dzX8Rxiqwehp40rBlWcefzb3aGxZbMkRv1GZucW5V11X/Bzad/QM1UTWIAAAAAElFTkSuQmCC' id='texture'/>
<script type='glsl/vertex'>
attribute vec2 coords;
void main(void) {
gl_Position = vec4(coords.xy, 0.0, 1.0);
}
</script>
<script type='glsl/fragment'>
precision highp float;
precision highp int;
uniform vec3 iResolution;
uniform int iMS;
uniform float iScreen;
uniform int theme;
uniform int textLen;
uniform int textChars[40];
uniform sampler2D iChannel0;
const float totalChars = 27.;
const float charWidth = 4.;
const float charHeight = 1.2;
float time()
{
return float(iMS) / 1000.;
}
vec3 readchar(int n, in vec2 p)
{
if (p.x < 0. || p.y < 0. || p.x > charWidth || p.y > charHeight) return vec3(0.);
return texture2D(iChannel0, vec2(.99 * p.x / charWidth / totalChars + float(n) * (1. / totalChars), p.y / charHeight)).rgb;
}
vec3 text( in vec2 p )
{
vec3 res = vec3(0);
for (int i=0; i<40; i++)
{
if (i == textLen) break;
res = max(res, readchar(textChars[i], p - float(i) * vec2(charWidth, 0.) - vec2(float(textLen) * -charWidth * .5, -.5) - vec2(p.y/2., 0.)));
}
return res;
}
void main( void )
{
vec2 p = (-iResolution.xy + 2.0*gl_FragCoord.xy)/iResolution.y;
vec2 bounce = vec2(0, -.5 + .7 * abs(sin(time() * 2.)));
vec2 scrolloffset = vec2(time() * 6., 0.);
vec2 dypp = vec2(0, .1 * sin(time() + p.x));
vec2 screenoffset = vec2(iScreen * 2.5, 0);
vec2 scanpos = p + screenoffset + scrolloffset - bounce - dypp;
float textWidth = float(textLen) * 4.3;
vec2 wrappos = scanpos - textWidth * floor(scanpos/textWidth + .5);
vec3 textColor = text(wrappos);
if (textColor.r > 0.)
{
gl_FragColor = vec4(textColor * vec3(1., abs(sin(p.y * 5. - time() * 6. - bounce.y)) + .25, 1.), 1.);
}
else
{
gl_FragColor = vec4(abs(sin(p.y * 4. + time() * 4.)), 0, .5 * abs(sin(p.y * 6. + time() * 12.)), 1);
}
}
</script>
<script>
// get url parameters
const urlParams = new URLSearchParams(window.location.search);
const screenParam = urlParams.get('screen');
const screen = screenParam == null ? 0 : parseFloat(screenParam);
const theme = parseInt(urlParams.get('theme'));
const textParam = urlParams.get('text');
const textLen = textParam == null ? 10 : textParam.length;
const text = textParam == null ? 'hassy_team' : textParam;
// scan text to array of letter indexes
const letters = [['a', 1],
['b',2],
['c',3],
['d',4],
['e',5],
['f',6],
['g',7],
['h',8],
['i',9],
['j',10],
['k',11],
['l',12],
['m',13],
['n',14],
['o',15],
['p',16],
['q',17],
['r',18],
['s',19],
['t',20],
['u',21],
['v',22],
['w',23],
['x',24],
['y',25],
['z',26]];
let letterIndexes = [];
for (let i=0; i < textLen; i++)
{
var s = text.substring(i, i+1);
let found = false;
for (let j=0; j < 26; j++)
{
if (letters[j][0] === s)
{
letterIndexes.push(letters[j][1]);
found = true;
break;
}
}
if (!found) letterIndexes.push(0);
}
// create canvas
let canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let gl = canvas.getContext('webgl2');
var h = gl.drawingBufferHeight;
var w = gl.drawingBufferWidth;
// create program
let pid = gl.createProgram();
shader('glsl/vertex', gl.VERTEX_SHADER);
shader('glsl/fragment', gl.FRAGMENT_SHADER);
gl.linkProgram(pid);
gl.useProgram(pid);
// create triangle that goes over entire screen (overflows)
let array = new Float32Array([-1, 3, -1, -1, 3, -1]);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
let al = gl.getAttribLocation(pid, "coords");
gl.vertexAttribPointer(al, 2 /*components per vertex */, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(al);
// Create a texture.
var texture = gl.createTexture();
// use texture unit 0
gl.activeTexture(gl.TEXTURE0 + 0);
// bind to the TEXTURE_2D bind point of texture unit 0
gl.bindTexture(gl.TEXTURE_2D, texture);
// Fill the texture with a 1x1 blue pixel.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
new Uint8Array([0, 0, 255, 255]));
// Asynchronously load an image
const image = document.getElementById('texture');
image.addEventListener('load', function() {
// Now that the image has loaded make copy it to the texture.
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.generateMipmap(gl.TEXTURE_2D);
});
// get uniform locations
let res = gl.getUniformLocation(pid, 'iResolution');
let msU = gl.getUniformLocation(pid, 'iMS');
let textU = gl.getUniformLocation(pid, 'textChars');
let textLenU = gl.getUniformLocation(pid, 'textLen');
let screenU = gl.getUniformLocation(pid, 'iScreen');
let themeU = gl.getUniformLocation(pid, 'theme');
// sync to last passed hour
let d = new Date(Date.now());
let year = d.getFullYear();
let month = d.getMonth();
let day = d.getDate();
let hour = d.getHours();
let start = Date.UTC(year, month, day, hour);
draw();
function draw(e) {
gl.uniform3f(res, w, h, 0);
let now = Date.now();
gl.uniform1i(msU, (now - start));
gl.uniform1f(screenU, screen);
gl.uniform1i(themeU, theme);
gl.uniform1iv(textU, letterIndexes);
gl.uniform1i(textLenU, textLen);
gl.viewport(0, 0, w, h);
gl.clearColor(0, 0, 0, 0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.drawArrays(gl.TRIANGLES, 0, 3);
requestAnimationFrame(draw);
}
function shader(name, type) {
let src = [].slice.call(document.scripts).find(s => s.type === name).innerText;
let sid = gl.createShader(type);
gl.shaderSource(sid, src);
gl.compileShader(sid);
gl.attachShader(pid, sid);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment