Поддержка буфера трафарета с WebGL - PullRequest
0 голосов
/ 07 августа 2020

Инициализация webgl с помощью canvas.getContext("webgl", {stencil : true}) запрашивает буфер трафарета, но не все браузеры фактически предоставят вам его (для меня Firefox 79.0 в Ubuntu 20.04 LTS не работает, а Chrome 84.0.4147.89 работает. Моя графика карта NVIDIA RTX 2060, я использую драйвер nvidia-driver-440-server).

Я хотел бы знать, насколько широко поддерживаются буферы трафаретов, но я не могу найти информацию о том, какие браузеры поддерживается. Такие функции, как glStencilOp, единственное, для чего я могу найти информацию для поддержки, все еще можно использовать, они просто ничего не делают с 0 битами трафарета.

Есть ли список браузеров, поддерживающих эта функция?

1 Ответ

1 голос
/ 07 августа 2020

Честно говоря, это звучит как ошибка в firefox, хотя с учетом SP c реализация не может предоставить буфер трафарета на холсте по любой причине, хотя технически это не является ошибкой. Я бы подумал о наполнении одного. Протестируйте с помощью браузера Chromium, чтобы убедиться, что это Firefox, выбирающее не предоставлять буфер трафарета, а не проблему с драйвером или что-то еще.

У вас всегда должна быть возможность создать DEPTH_STENCIL буфер рендеринга. Нет версии WebGL, в которой реализация не поддерживала бы это. Таким образом, вы можете обойти эту ошибку, отрендерив в буфер рендеринга трафарета текстура + глубина, прикрепленный к фреймбуферу, а затем отрендерив цветную текстуру фреймбуфера на холст.

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

Синий квадрат показывает размеры текстуры фреймбуфера. Если бы зеленый квадрат не был замаскирован буфером трафарета, он бы растекся под синий.

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

"use strict";

function main() {
  const gl = document.querySelector("canvas").getContext("webgl");
  
  const vs = `
  attribute vec4 position;

  void main() {
    gl_Position = position;
    gl_PointSize = 64.0;
  }
  `;

  const fs = `
  precision mediump float;

  uniform sampler2D tex;

  void main() {
     gl_FragColor = texture2D(tex, gl_PointCoord.xy);
  }
  `;

  const program = twgl.createProgram(gl, [vs, fs]);
  const posLoc = gl.getAttribLocation(program, "position");

  // Create a texture to render to
  const targetTextureWidth = 128;
  const targetTextureHeight = 128;
  const targetTexture = createTexture(gl);

  {
    // define size and format of level 0
    const level = 0;
    const internalFormat = gl.RGBA;
    const border = 0;
    const format = gl.RGBA;
    const type = gl.UNSIGNED_BYTE;
    const data = null;
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
                  targetTextureWidth, targetTextureHeight, border,
                  format, type, data);

  }

  // Create and bind the framebuffer
  const fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);

  // attach the texture as the first color attachment
  const attachmentPoint = gl.COLOR_ATTACHMENT0;
  const level = 0;
  gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);

  // create a depth-stencil renderbuffer
  const depthStencilBuffer = gl.createRenderbuffer();
  gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);

  // make a depth-stencil buffer and the same size as the targetTexture
  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, targetTextureWidth, targetTextureHeight);
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
  
  function createTexture(gl, color) {
    const tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    // set the filtering so we don't need mips
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    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);
    if (color) {
      gl.texImage2D(
          gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
          gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color));
    }
    return tex;
  }
  
  // create a red texture and a green texture
  const redTex = createTexture(gl, [255, 0, 0, 255]);
  const greenTex = createTexture(gl, [0, 255, 0, 255]);

  gl.enable(gl.STENCIL_TEST);

  gl.useProgram(program);
  gl.clearColor(0, 0, 1, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  gl.bindTexture(gl.TEXTURE_2D, redTex);
  gl.stencilFunc(
       gl.ALWAYS,    // the test
       1,            // reference value
       0xFF,         // mask
    );  
  gl.stencilOp(
     gl.KEEP,     // what to do if the stencil test fails
     gl.KEEP,     // what to do if the depth test fails
     gl.REPLACE,  // what to do if both tests pass
  );
  
  // draw a 64x64 pixel red rect in middle
  gl.drawArrays(gl.POINTS, 0, 1);
  

  gl.stencilFunc(
       gl.EQUAL,     // the test
       1,            // reference value
       0xFF,         // mask
    );  
  gl.stencilOp(
     gl.KEEP,     // what to do if the stencil test fails
     gl.KEEP,     // what to do if the depth test fails
     gl.KEEP,  // what to do if both tests pass
  );


  // draw a green 64x64 pixel square in the
  // upper right corner. The stencil will make
  // it not go outside the red square
  gl.vertexAttrib2f(posLoc, 0.5, 0.5);
  gl.bindTexture(gl.TEXTURE_2D, greenTex);
  gl.drawArrays(gl.POINTS, 0, 1);

  // draw the framebuffer's texture to
  // the canvas. we should see a 32x32
  // red square with the bottom right corner
  // green showing the stencil worked. That will
  // be surrounded by blue to show the texture
  // we were rendering to is larger than the
  // red square. And that will be surrounded
  // by purple since we're drawing a 64x64
  // point on a 128x128 canvas which we clear 
  // purple.
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.clearColor(1, 0, 1, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  gl.vertexAttrib2f(posLoc, 0.0, 0.0);
  gl.bindTexture(gl.TEXTURE_2D, targetTexture);
  gl.drawArrays(gl.POINTS, 0, 1);  

}

main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas width="128" height="128"></canvas>

Если вы измените формат буфера рендеринга на DEPTH_COMPONENT16 и точку присоединения на DEPTH_ATTACHMENT, тогда вы увидите, что зеленый квадрат больше не маскируется трафаретом

"use strict";

function main() {
  const gl = document.querySelector("canvas").getContext("webgl");
  
  const vs = `
  attribute vec4 position;

  void main() {
    gl_Position = position;
    gl_PointSize = 64.0;
  }
  `;

  const fs = `
  precision mediump float;

  uniform sampler2D tex;

  void main() {
     gl_FragColor = texture2D(tex, gl_PointCoord.xy);
  }
  `;

  const program = twgl.createProgram(gl, [vs, fs]);
  const posLoc = gl.getAttribLocation(program, "position");

  // Create a texture to render to
  const targetTextureWidth = 128;
  const targetTextureHeight = 128;
  const targetTexture = createTexture(gl);

  {
    // define size and format of level 0
    const level = 0;
    const internalFormat = gl.RGBA;
    const border = 0;
    const format = gl.RGBA;
    const type = gl.UNSIGNED_BYTE;
    const data = null;
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
                  targetTextureWidth, targetTextureHeight, border,
                  format, type, data);

  }

  // Create and bind the framebuffer
  const fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);

  // attach the texture as the first color attachment
  const attachmentPoint = gl.COLOR_ATTACHMENT0;
  const level = 0;
  gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);

  // create a depth-stencil renderbuffer
  const depthStencilBuffer = gl.createRenderbuffer();
  gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);

  // make a depth-stencil buffer and the same size as the targetTexture
  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, targetTextureWidth, targetTextureHeight);
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
  
  function createTexture(gl, color) {
    const tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    // set the filtering so we don't need mips
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    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);
    if (color) {
      gl.texImage2D(
          gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
          gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color));
    }
    return tex;
  }
  
  // create a red texture and a green texture
  const redTex = createTexture(gl, [255, 0, 0, 255]);
  const greenTex = createTexture(gl, [0, 255, 0, 255]);

  gl.enable(gl.STENCIL_TEST);

  gl.useProgram(program);
  gl.clearColor(0, 0, 1, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  gl.bindTexture(gl.TEXTURE_2D, redTex);
  gl.stencilFunc(
       gl.ALWAYS,    // the test
       1,            // reference value
       0xFF,         // mask
    );  
  gl.stencilOp(
     gl.KEEP,     // what to do if the stencil test fails
     gl.KEEP,     // what to do if the depth test fails
     gl.REPLACE,  // what to do if both tests pass
  );
  
  // draw a 64x64 pixel red rect in middle
  gl.drawArrays(gl.POINTS, 0, 1);
  

  gl.stencilFunc(
       gl.EQUAL,     // the test
       1,            // reference value
       0xFF,         // mask
    );  
  gl.stencilOp(
     gl.KEEP,     // what to do if the stencil test fails
     gl.KEEP,     // what to do if the depth test fails
     gl.KEEP,  // what to do if both tests pass
  );


  // draw a green 64x64 pixel square in the
  // upper right corner. The stencil will make
  // it not go outside the red square
  gl.vertexAttrib2f(posLoc, 0.5, 0.5);
  gl.bindTexture(gl.TEXTURE_2D, greenTex);
  gl.drawArrays(gl.POINTS, 0, 1);

  // draw the framebuffer's texture to
  // the canvas. we should see a 32x32
  // red square with the bottom right corner
  // green showing the stencil worked. That will
  // be surrounded by blue to show the texture
  // we were rendering to is larger than the
  // red square. And that will be surrounded
  // by purple since we're drawing a 64x64
  // point on a 128x128 canvas which we clear 
  // purple.
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.clearColor(1, 0, 1, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  gl.vertexAttrib2f(posLoc, 0.0, 0.0);
  gl.bindTexture(gl.TEXTURE_2D, targetTexture);
  gl.drawArrays(gl.POINTS, 0, 1);  

}

main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas width="128" height="128"></canvas>

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

...