Эффект WebGL Droste - PullRequest
       10

Эффект WebGL Droste

0 голосов
/ 01 июля 2019

Я пытаюсь использовать WebGL для достижения эффекта Droste на гранях куба. В области просмотра есть одна сетка, куб, и все его грани имеют одинаковую текстуру. Чтобы добиться эффекта Droste, я обновляю текстуру в каждом кадре и на самом деле просто делаю снимок canvas, контекст WebGL которого я рисую, что со временем приводит к эффекту Droste, так как снимок все больше и больше содержит вложенное прошлое. кадры.

Вот демонстрация того, что я сейчас имею в действии:

https://tomashubelbauer.github.io/webgl-op-1/?cubeTextured

Код, о котором идет речь, следует:

// Set up fragment and vertex shader and attach them to a program, link the program
// Create a vertex buffer, an index buffer and a texture coordinate buffer
// Tesselate the cube's vertices and fill in the index and texture coordinate buffers
const textureCanvas = document.createElement('canvas');
textureCanvas.width = 256;
textureCanvas.height = 256;
const textureContext = textureCanvas.getContext('2d');

// In every `requestAnimationFrame`:
textureContext.drawImage(context.canvas, 0, 0);
const texture = context.createTexture();
context.bindTexture(context.TEXTURE_2D, texture);
context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, textureCanvas);
context.generateMipmap(context.TEXTURE_2D);
// Clear the viewport completely (depth and color buffers)
// Set up attribute and uniform values, the projection and model view matrices
context.activeTexture(context.TEXTURE0);
context.bindTexture(context.TEXTURE_2D, texture);
context.uniform1i(fragmentShaderTextureSamplerUniformLocation, 0);
context.drawElements(context.TRIANGLES, 36, context.UNSIGNED_SHORT, 0)

Вышесказанное является основой всего этого, есть отдельный холст от WebGL, и он получает холст WebGL, нарисованный на нем перед каждым фреймом WebGL, и этот холст затем используется для создания текстуры для данного фрейма и текстура применяется к граням куба в соответствии с буфером координат текстуры и формой сэмплера текстуры, предоставленной фрагментному шейдеру, который просто использует gl_FragColor = texture2D(textureSampler, textureCoordinate), как и следовало ожидать.

Но это очень медленно (на 30 простых кадров в секунду в этой простой демонстрации с одной кубической сеткой, где все мои другие демонстрации, на три порядка больше, все еще ограничивают 60 кадров в секунду requestAnimationFrame ограничение).

Также кажется странным делать это «за пределами» WebGL, используя внешний холст, когда я чувствую, что этого можно достичь, используя только WebGL.

Я знаю, что в WebGL хранятся два буфера: один для активного кадра и задний буфер для недавно нарисованного кадра, и эти два меняются местами с каждым кадром для немедленного обновления экрана. Можно ли нажать на этот задний буфер и использовать его в качестве текстуры? Можете ли вы предоставить пример кода того, как это будет сделано?

1 Ответ

1 голос
/ 02 июля 2019

Из этой статьи

Обычный способ сделать это - рендерить текстуру, прикрепив эту текстуру к кадровому буферу.

const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(
    gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0 /* level */) 

Теперь для рендерингана текстуру

gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.viewport(0, 0, textureWidth, textureHeight);

Для рендеринга на холст

gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

Чтобы сделать то, что вы хотите, вам нужно 2 текстуры, так как вы не можете читать и писать на одну и ту же текстуру одновременновремя, когда вы рисуете, скажем

  • Рисование изображения в текстуреA
  • Рисование предыдущего кадра (TextureB) в текстуреA
  • Рисование куба с TextureA в текстуруB
  • Рисуем текстуру B на холсте

"use strict";

function main() {
  const m4 = twgl.m4;
  const gl = document.querySelector('canvas').getContext('webgl')

  const vs = `
  attribute vec4 position;
  attribute vec2 texcoord;
  uniform mat4 u_matrix;
  varying vec2 v_texcoord;
  void main() {
    gl_Position = u_matrix * position;
    v_texcoord = texcoord;
  }
  `;
  
  const fs = `
  precision mediump float;
  varying vec2 v_texcoord;
  uniform sampler2D u_tex;
  void main() {
    gl_FragColor = texture2D(u_tex, v_texcoord);
  }
  `;
  
  // compile shaders, link program, look up locations
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

  // gl.createBuffer, gl.bufferData for positions and texcoords of a cube
  const cubeBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
  // gl.createBuffer, gl.bufferData for positions and texcoords of a quad
  const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl, 2);

  // all the normal stuff for setting up a texture
  const imageTexture = twgl.createTexture(gl, {
    src: 'https://i.imgur.com/ZKMnXce.png',
  });

  function makeFramebufferAndTexture(gl, width, height) {
    const framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
    
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D,
       0,       // level
       gl.RGBA, // internal format
       width,
       height,
       0,       // border
       gl.RGBA, // format
       gl.UNSIGNED_BYTE, // type
       null,    // data (no data needed)
    );
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    
    gl.framebufferTexture2D(
       gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
       gl.TEXTURE_2D, texture, 0 /* level */);
  
    // note: depending on what you're rendering you might want to atttach
    // a depth renderbuffer or depth texture. See linked article
    
    return {
      framebuffer,
      texture,
      width,
      height,
    };
  }
  
  function bindFramebufferAndSetViewport(gl, fbi) {
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbi ? fbi.framebuffer : null);
    const {width, height} = fbi || gl.canvas;
    gl.viewport(0, 0, width, height);
  }

  let fbiA = makeFramebufferAndTexture(gl, 512, 512);
  let fbiB = makeFramebufferAndTexture(gl, 512, 512);
  
  function drawImageAndPreviousFrameToTextureB() {
    bindFramebufferAndSetViewport(gl, fbiB);
    
    // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    // for each attribute
    twgl.setBuffersAndAttributes(gl, programInfo, quadBufferInfo);

    // calls gl.activeTexture, gl.bindTexture, gl.uniform 
    twgl.setUniforms(programInfo, {
      u_tex: imageTexture,
      u_matrix: m4.identity(),
    });

    // calls gl.drawArrays or gl.drawElements
    twgl.drawBufferInfo(gl, quadBufferInfo);
    
    // ---------
    
    // draw previous cube texture into current cube texture
    {
      twgl.setUniforms(programInfo, {
        u_tex: fbiA.texture,
        u_matrix: m4.scaling([0.8, 0.8, 1]),
      });
      twgl.drawBufferInfo(gl, quadBufferInfo);
    }
  }    
    
  function drawTexturedCubeToTextureA(time) {
    // ---------   
    // draw cube to "new" dstFB using srcFB.texture on cube
    bindFramebufferAndSetViewport(gl, fbiA);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    twgl.setBuffersAndAttributes(gl, programInfo, cubeBufferInfo);
    
    {
      const fov = 60 * Math.PI / 180;
      const aspect = fbiA.width / fbiA.height;
      const near = 0.1;
      const far = 100;
      let mat = m4.perspective(fov, aspect, near, far); 
      mat = m4.translate(mat, [0, 0, -2]);
      mat = m4.rotateX(mat, time);
      mat = m4.rotateY(mat, time * 0.7);

      twgl.setUniforms(programInfo, {
        u_tex: fbiB.texture,
        u_matrix: mat,
      });
    }
    
    twgl.drawBufferInfo(gl, cubeBufferInfo);
  }
  
  function drawTextureAToCanvas() {
    // --------
    // draw dstFB.texture to canvas
    bindFramebufferAndSetViewport(gl, null);
    
    twgl.setBuffersAndAttributes(gl, programInfo, quadBufferInfo);
    
    {
      const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
      const near = -1;
      const far = 1;
      let mat = m4.ortho(-aspect, aspect, -1, 1, near, far);

      twgl.setUniforms(programInfo, {
        u_tex: fbiA.texture,
        u_matrix: mat,
      });
    }
    
    twgl.drawBufferInfo(gl, quadBufferInfo);
  }  
  
  function render(time) {
    time *= 0.001; // convert to seconds;
    
    twgl.resizeCanvasToDisplaySize(gl.canvas);
    
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.CULL_FACE);
    
    // there's only one shader program so let's set it here
    gl.useProgram(programInfo.program);
  
    drawImageAndPreviousFrameToTextureB();
    drawTexturedCubeToTextureA(time);
    drawTextureAToCanvas();
  
    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

main();
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

Что касается холста и его 2 буферов, нет возможности напрямую использовать их в качестве текстур.Вы можете вызвать gl.copyTexImage2D или gl.copyTexSubImage2D top, чтобы скопировать часть холста в текстуру, хотя это еще одно решение.Он менее гибкий, и я считаю, что он медленнее, чем метод кадрового буфера

...