Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Responsive canvas using webgl and meatballs.js

I'm attempting to implement this codepen as a background of my personal website. I have no real knowledge of WebGL, so please bear with me. I temporarily added an event listener to update the width and height of the canvas when the page is resized. I can tell this works because when the bubbles start going out of bounds, they continue going and don't bounce off the edge of the page, so I know it somewhat works the way I want it to. When the fragment shader source is defined it also defines the width and height and I'm not sure how to change those variables after that. I tried redefining, recompiling, and reattaching the fragment shader source with the new widths and heights. This obviously doesn't work because the bubbles do not render past the size of the page when the canvas was created. I'm not sure if i'm even going about this the right way, if so what am I doing wrong? All/any help is appreciated, thank you.

The code I changed:

var canvas = document.createElement("canvas");
var width = canvas.width = window.innerWidth * 0.75;
var height = canvas.height = window.innerHeight * 0.75;
document.body.appendChild(canvas);
var gl = canvas.getContext('webgl');

var mouse = {x: 0, y: 0};

var numMetaballs = 30;
var metaballs = [];

var first = true

window.addEventListener('resize', function(){
    width = canvas.width = window.innerWidth * 0.75;
    height = canvas.height = window.innerHeight * 0.75;
    shaderStuff()
})

function shaderStuff(){
    if(!first) {
        gl.detachShader(program, gl.getAttachedShaders(program)[1])
    }
    first = false
    
    var fragmentShaderSrc = `
    precision highp float;

    const float WIDTH = ` + (width >> 0) + `.0;
    const float HEIGHT = ` + (height >> 0) + `.0;

    uniform vec3 metaballs[` + numMetaballs + `];

    void main(){
    float x = gl_FragCoord.x;
    float y = gl_FragCoord.y;

    float sum = 0.0;
    for (int i = 0; i < ` + numMetaballs + `; i++) {
    vec3 metaball = metaballs[i];
    float dx = metaball.x - x;
    float dy = metaball.y - y;
    float radius = metaball.z;

    sum += (radius * radius) / (dx * dx + dy * dy);
    }

    if (sum >= 0.99) {
    gl_FragColor = vec4(mix(vec3(x / WIDTH, y / HEIGHT, 1.0), vec3(0, 0, 0), max(0.0, 1.0 - (sum - 0.99) * 100.0)), 1.0);
    return;
    }

    gl_FragColor = vec4(0, 0, 0, 0);
    }

    `;
    
    var fragmentShader = compileShader(fragmentShaderSrc, gl.FRAGMENT_SHADER);
    gl.attachShader(program, fragmentShader);
}

for (var i = 0; i < numMetaballs; i++) {
  var radius = Math.random() * 60 + 10;
  metaballs.push({
    x: Math.random() * (width - 2 * radius) + radius,
    y: Math.random() * (height - 2 * radius) + radius,
    vx: (Math.random() - 0.5) * 3,
    vy: (Math.random() - 0.5) * 3,
    r: radius * 0.75
  });
}

var vertexShaderSrc = `
attribute vec2 position;

void main() {
// position specifies only x and y.
// We set z to be 0.0, and w to be 1.0
gl_Position = vec4(position, 0.0, 1.0);
}
`;



var vertexShader = compileShader(vertexShaderSrc, gl.VERTEX_SHADER);


var program = gl.createProgram();
gl.attachShader(program, vertexShader);
shaderStuff()
gl.linkProgram(program);
gl.useProgram(program);

The whole project https://meatballsjs.000webhostapp.com/

The original https://codepen.io/TC5550/pen/WNNWoaO

like image 902
Assisted Suicide Avatar asked May 21 '26 08:05

Assisted Suicide


1 Answers

The easiest way would be to put all of the background creation code in a function, and call it every time the page is resized.

You will also need to add some code to cause the previous background loops to stop, and you should add some throttling to prevent too many backgrounds to be created at once.

This is somewhat inefficient, but most users don't expect applications to be extremely responsive while they are being resized, and resizing is an infrequent operation.

I added a code snippet, which appears to work, however I could not get my changes to work in codepen. I believe this is because codepen instruments and modifies the code in a certain way that breaks it (jsbin has similar behavior to prevent infinite loops, and to sandbox it). However I tested my changes in just a .html file, and they seemed to work there, so they should work on your site.

On a side note, very cool use of WebGL!

var nextBackgroundId = 1;
var currentBackgroundId = 0;

setupBackground(currentBackgroundId);
window.addEventListener("resize", () => {
    var ourBackgroundId = nextBackgroundId++;
    currentBackgroundId = ourBackgroundId;
    setTimeout(() => {
        setupBackground(ourBackgroundId);
    }, 100);
});

function setupBackground(ourBackgroundId) {
    if (currentBackgroundId !== ourBackgroundId) {
        return;
    }

    var prevCanvas = document.getElementById("blob-canvas");
    if (prevCanvas) {
        prevCanvas.remove();
    }

    var canvas = document.createElement("canvas");
    canvas.id = "blob-canvas";
    var mouse = { x: 0, y: 0 };

    canvas.onmousemove = function (e) {
        mouse.x = e.clientX;
        mouse.y = e.clientY;
    }

    var width = canvas.width = window.innerWidth;
    var height = canvas.height = window.innerHeight;
    document.body.appendChild(canvas);
    var gl = canvas.getContext('webgl');

    var numMetaballs = 30;
    var metaballs = [];

    for (var i = 0; i < numMetaballs; i++) {
        var radius = Math.random() * 60 + 10;
        metaballs.push({
            x: Math.random() * (width - 2 * radius) + radius,
            y: Math.random() * (height - 2 * radius) + radius,
            vx: (Math.random() - 0.5) * 3,
            vy: (Math.random() - 0.5) * 3,
            r: radius * 0.75
        });
    }

    var vertexShaderSrc = `
attribute vec2 position;

void main() {
// position specifies only x and y.
// We set z to be 0.0, and w to be 1.0
gl_Position = vec4(position, 0.0, 1.0);
}
`;

    var fragmentShaderSrc = `
precision highp float;

const float WIDTH = ` + (width >> 0) + `.0;
const float HEIGHT = ` + (height >> 0) + `.0;

uniform vec3 metaballs[` + numMetaballs + `];

void main(){
float x = gl_FragCoord.x;
float y = gl_FragCoord.y;

float sum = 0.0;
for (int i = 0; i < ` + numMetaballs + `; i++) {
vec3 metaball = metaballs[i];
float dx = metaball.x - x;
float dy = metaball.y - y;
float radius = metaball.z;

sum += (radius * radius) / (dx * dx + dy * dy);
}

if (sum >= 0.99) {
gl_FragColor = vec4(mix(vec3(x / WIDTH, y / HEIGHT, 1.0), vec3(0, 0, 0), max(0.0, 1.0 - (sum - 0.99) * 100.0)), 1.0);
return;
}

gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}

`;

    var vertexShader = compileShader(vertexShaderSrc, gl.VERTEX_SHADER);
    var fragmentShader = compileShader(fragmentShaderSrc, gl.FRAGMENT_SHADER);

    var program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    gl.useProgram(program);

    var vertexData = new Float32Array([
        -1.0, 1.0, // top left
        -1.0, -1.0, // bottom left
        1.0, 1.0, // top right
        1.0, -1.0, // bottom right
    ]);
    var vertexDataBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexDataBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);

    var positionHandle = getAttribLocation(program, 'position');
    gl.enableVertexAttribArray(positionHandle);
    gl.vertexAttribPointer(positionHandle,
        2, // position is a vec2
        gl.FLOAT, // each component is a float
        gl.FALSE, // don't normalize values
        2 * 4, // two 4 byte float components per vertex
        0 // offset into each span of vertex data
    );

    var metaballsHandle = getUniformLocation(program, 'metaballs');

    loop();
    function loop() {
        if (currentBackgroundId !== ourBackgroundId) {
            return;
        }
        for (var i = 0; i < numMetaballs; i++) {
            var metaball = metaballs[i];
            metaball.x += metaball.vx;
            metaball.y += metaball.vy;

            if (metaball.x < metaball.r || metaball.x > width - metaball.r) metaball.vx *= -1;
            if (metaball.y < metaball.r || metaball.y > height - metaball.r) metaball.vy *= -1;
        }

        var dataToSendToGPU = new Float32Array(3 * numMetaballs);
        for (var i = 0; i < numMetaballs; i++) {
            var baseIndex = 3 * i;
            var mb = metaballs[i];
            dataToSendToGPU[baseIndex + 0] = mb.x;
            dataToSendToGPU[baseIndex + 1] = mb.y;
            dataToSendToGPU[baseIndex + 2] = mb.r;
        }
        gl.uniform3fv(metaballsHandle, dataToSendToGPU);

        //Draw
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

        requestAnimationFrame(loop);
    }

    function compileShader(shaderSource, shaderType) {
        var shader = gl.createShader(shaderType);
        gl.shaderSource(shader, shaderSource);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            throw "Shader compile failed with: " + gl.getShaderInfoLog(shader);
        }

        return shader;
    }

    function getUniformLocation(program, name) {
        var uniformLocation = gl.getUniformLocation(program, name);
        if (uniformLocation === -1) {
            throw 'Can not find uniform ' + name + '.';
        }
        return uniformLocation;
    }

    function getAttribLocation(program, name) {
        var attributeLocation = gl.getAttribLocation(program, name);
        if (attributeLocation === -1) {
            throw 'Can not find attribute ' + name + '.';
        }
        return attributeLocation;
    }
}
body {
    font-family: 'Alatsi', sans-serif;
    margin: 0;
    overflow: hidden;
    background: black;
}

.container {
    display: flex;
    justify-content: center;
    align-items: center;

    position: absolute;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
}

.title {
    font-size: 10vw;
    color: white;
}

canvas {
    width: 100%;
}
<div class="container">
    <span class="title">MEATBALLS</span>
</div>
like image 121
yeerk Avatar answered May 23 '26 22:05

yeerk



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!