Created
August 2, 2025 09:20
-
-
Save gimulnautti/788840efa47e8a71e903849b1bcfe9dc to your computer and use it in GitHub Desktop.
Hassy Team WebGL Scroller
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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