Адаптивный холст с использованием webgl и фрикаделек. js - PullRequest
3 голосов
/ 21 июня 2020

Я пытаюсь реализовать этот кодовый ключ в качестве фона моего личного веб-сайта. У меня нет реальных знаний о WebGL, поэтому, пожалуйста, потерпите меня. Я временно добавил слушателя событий, чтобы обновлять ширину и высоту холста при изменении размера страницы. Я могу сказать, что это работает, потому что, когда пузыри начинают выходить за пределы, они продолжают двигаться и не отскакивают от края страницы, поэтому я знаю, что это работает так, как я хочу. Когда определен источник фрагментного шейдера, он также определяет ширину и высоту, и я не уверен, как изменить эти переменные после этого. Я попытался переопределить, перекомпилировать и повторно присоединить источник фрагментного шейдера с новой шириной и высотой. Это, очевидно, не работает, потому что пузырьки не отображают размер страницы, когда был создан холст. Я не уверен, правильно ли я поступаю, если да, то что я делаю не так? Вся / любая помощь приветствуется, спасибо.

Код, который я изменил:

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);

Весь проект https://meatballsjs.000webhostapp.com/

Оригинал https://codepen.io/TC5550/pen/WNNWoaO

Ответы [ 2 ]

2 голосов
/ 21 июня 2020

Самый простой способ - поместить весь код создания фона в функцию и вызывать ее каждый раз при изменении размера страницы.

Вам также нужно будет добавить код, чтобы вызвать предыдущие циклы фона чтобы остановить, и вам следует добавить некоторое регулирование, чтобы предотвратить создание слишком большого количества фонов одновременно.

Это несколько неэффективно, но большинство пользователей не ожидают, что приложения будут очень быстро реагировать при изменении их размера, и изменение размера - нечастая операция.

Я добавил фрагмент кода, который, похоже, работает, однако мне не удалось заставить свои изменения работать в коде. Я считаю, что это потому, что codepen инструменты и модифицируют код определенным образом, что нарушает его (jsbin имеет аналогичное поведение, чтобы предотвратить бесконечные циклы и изолировать его). Однако я протестировал свои изменения только в файле. html, и, похоже, они там работают, поэтому они должны работать на вашем сайте.

Кстати, очень крутое использование 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>
0 голосов
/ 22 июня 2020

Есть много проблем с этим кодом.

  1. Он взламывает размер холста вместо того, чтобы CSS изменять размер холста.

    В коде размер холста устанавливается с помощью

    var width = canvas.width = window.innerWidth * 0.75;
    var height = canvas.height = window.innerHeight * 0.75;
    

    Возможно, лучше всего позволить браузеру изменять размер холста

    html, body {
      height: 100%;
      overflow: hidden;
    }     
    canvas {
      width: 100%;
      height: 100%;
    }
    

    , а затем спрашивать браузер, какой размер холста, и устанавливать разрешение холста, чтобы оно соответствовало

    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;
    
  2. Он запрашивает, чтобы вещи были больше, чем окно, чтобы получить полосу прокрутки, а затем скрыть этот факт, скрывая полосу прокрутки. Это не имеет никакого смысла. Если вы не хотите, чтобы полоса прокрутки не запрашивала контент, для которого требуется полоса прокрутки.

    html, body {
      height: 100%;
      /* removed overflow: hidden */
    }     
    canvas {
      width: 100%;
      height: 100%;
      display: block;
    }
    
  3. Он использует строки шаблона, но не использует их как шаблоны

    var fragmentShaderSrc = `
    precision highp float;
    
    const float WIDTH = ` + (width >> 0) + `.0;
    const float HEIGHT = ` + (height >> 0) + `.0;
    
    uniform vec3 metaballs[` + numMetaballs + `];
    ...
    `;
    

    , возможно, должно быть

    var fragmentShaderSrc = `
    precision highp float;
    
    const float WIDTH = ${width >> 0}.0;
    const float HEIGHT = ${height >> 0}.0;
    
    uniform vec3 metaballs[${numMetaballs}];
    ...
    `;
    

    Основная идея использования обратных кавычек для строк заключается в том, чтобы вы могли использовать функцию шаблонов ${code}

  4. Это жесткое кодирование ширина и высота

    const float WIDTH = ${width >> 0}.0;
    const float HEIGHT = ${height >> 0}.0;
    

    , возможно, должны быть

    uniform float WIDTH;
    uniform float HEIGHT;
    

    , чтобы их можно было установить

  5. Metaballs написано с ошибкой как Meatballs (возможно, что было намеренно)

Вот новая версия. Примечание: каждый раз, когда размер окна изменяется, метабалы занимают случайное положение. Если вы закомментируете вызов updateMetaballs после изменения размера холста, они не получат новые случайные позиции. Что лучше - решать вам. Лог c того, как они отскакивают, таков, что любые шары, которые вылетают за пределы экрана после изменения размера, остаются за пределами экрана. Вы можете исправить это, чтобы они направлялись к экрану и отскакивали только изнутри обратно внутрь. Текущий код таков, что снаружи они будут просто раскачиваться на месте.

var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var gl = canvas.getContext('webgl');

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

var numMetaballs = 30;
var metaballs = [];

function updateMetaballs() {
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  for (var i = 0; i < numMetaballs; i++) {
    var radius = Math.random() * 60 + 10;
    metaballs[i] = {
      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
    };
  }
}
updateMetaballs();

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;

uniform float WIDTH;
uniform float HEIGHT;

#define NUM_METABALLS ${numMetaballs}
uniform vec3 metaballs[NUM_METABALLS];

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

  float sum = 0.0;
  for (int i = 0; i < NUM_METABALLS; 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');
var widthHandle = getUniformLocation(program, 'WIDTH');
var heightHandle = getUniformLocation(program, 'HEIGHT');

function resizeCanvasToDisplaySize(canvas) {
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    canvas.width = width;
    canvas.height = height;
  }
  return needResize;
}

loop();
function loop() {
  if (resizeCanvasToDisplaySize(canvas)) {
    updateMetaballs();
  }
  const {width, height} = canvas;
  gl.viewport(0, 0, canvas.width, canvas.height);
  
  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);
  gl.uniform1f(widthHandle, canvas.clientWidth);
  gl.uniform1f(heightHandle, canvas.clientHeight);
  
  //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;
}

canvas.onmousemove = function(e) {
  mouse.x = e.clientX;
  mouse.y = e.clientY;
}
html, body {
  font-family: 'Alatsi', sans-serif;
  margin: 0;
  background: black;
  height: 100%;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

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

canvas {
  width: 100%;
  height: 100%;
  display: block;
}
<div class="container">
    <span class="title">METABALLS</span>
</div>

Если вы хотите изучить WebGL, обратите внимание на эти руководства

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...