Особенности рендеринга 8-битных текстур без знака - PullRequest
0 голосов
/ 24 января 2019

Я пытался использовать целочисленные текстуры (см. Этот вопрос для контекста), но мне не удалось осуществить переход от текстур на основе чисел с плавающей запятой gl.RGBA/gl.RGBA к gl.RGBA8UI/gl.RGBA_INTEGER.

Я заменил упоминания sampler2D на usampler2D, vec4 на uvec4 (для fragColor), переписал форматы текстур, но ничего не нарисовано. Я также не мог использовать glClear, показывая с ошибкой: glClear: can't be called on integer buffers. Есть ли какие-то особенности при использовании целочисленных текстур?

Редактировать: Кажется, он работает в Google Chrome, а не в Firefox?

const baseImage = new Image();
baseImage.src = 'https://i.imgur.com/O6aW2Tg.png';
baseImage.crossOrigin = 'anonymous';
baseImage.onload = function() {
  render(baseImage);
};

const vertexShaderSource = `#version 300 es
precision mediump float;

in vec2 position;
out vec2 textureCoordinate;

void main() {
  textureCoordinate = vec2(1.0 - position.x, 1.0 - position.y);
  gl_Position = vec4((1.0 - 2.0 * position), 0, 1);
}`;

const fragmentShaderSource = `#version 300 es
precision mediump float;
precision highp usampler2D;

uniform usampler2D inputTexture;
in vec2 textureCoordinate;
out uvec4 fragColor;

void main() {
    fragColor = texture(inputTexture, textureCoordinate);
}`;

function render(image) {
  const canvas = document.getElementById('canvas');
  const gl = canvas.getContext('webgl2');
  if (!gl) {
    return;
  }

  const positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
    gl.STATIC_DRAW
  );
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  const program = webglUtils.createProgramFromSources(gl, [
    vertexShaderSource,
    fragmentShaderSource,
  ]);
  const positionAttributeLocation = gl.getAttribLocation(
    program,
    'position'
  );
  const inputTextureUniformLocation = gl.getUniformLocation(
    program,
    'inputTexture'
  );
  const vao = gl.createVertexArray();
  gl.bindVertexArray(vao);
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.vertexAttribPointer(
    positionAttributeLocation,
    2,
    gl.FLOAT,
    false,
    0,
    0
  );
  gl.bindVertexArray(null);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  const rawTexture = gl.createTexture();
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, rawTexture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, gl.RGBA_INTEGER, 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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 0);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);

  const outputTexture = gl.createTexture();
  gl.activeTexture(gl.TEXTURE1);
  gl.bindTexture(gl.TEXTURE_2D, outputTexture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, image.width,
    image.height,
    0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, null);
  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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 0);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);

  const framebuffer = gl.createFramebuffer();

  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  gl.framebufferTexture2D(
    gl.FRAMEBUFFER,
    gl.COLOR_ATTACHMENT0,
    gl.TEXTURE_2D,
    outputTexture,
    0
  );
  gl.viewport(0, 0, image.width, image.height);
  gl.clearColor(0, 0, 0, 1.0);
  // gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.useProgram(program);
  gl.uniform1i(inputTextureUniformLocation, 0);
  gl.bindVertexArray(vao);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, rawTexture);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
  gl.bindVertexArray(null);

  const pixels = new Uint8Array(4 * image.width * image.height);
  gl.readPixels(
    0,
    0,
    image.width,
    image.height,
    gl.RGBA_INTEGER,
    gl.UNSIGNED_BYTE,
    pixels
  );
  console.log(pixels);
}
<canvas id="canvas"></canvas>
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script>

1 Ответ

0 голосов
/ 25 января 2019

Ваш код отображается очень хорошо. Это не удалось на readPixels, которые мы видим в консоли JavaScript, Firefox напечатал ошибку

Error: WebGL warning: readPixels: Incompatible format or type.

Это печальная часть спецификации .

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

Это то, что написано в разделе 4.3.2

.

В большинстве случаев принимаются только две комбинации формата и типа. Первый изменяется в зависимости от формата текущей привязанной поверхности рендеринга. Для нормализованных поверхностей рендеринга с фиксированной точкой формат комбинации RGBA и тип UNSIGNED_BYTE принято. Для целочисленных поверхностей рендеринга со знаком допускается комбинация формата RGBA_INTEGER и типа INT. Для целочисленных поверхностей рендеринга без знака принимается комбинация формата RGBA_INTEGER и типа UNSIGNED_INT.

Второй формат выбран из реализации из числа определенных в таблице 3.2, исключая форматы DEPTH_COMPONENT и DEPTH_STENCIL. значения формата и типа для этого формата могут быть определены путем вызова GetIntegerv с символическими константами IMPLEMENTATION_COLOR_READ_FORMAT и IMPLEMENTATION_COLOR_READ_TYPE соответственно. ... Формат, выбранный реализацией, может варьироваться в зависимости от формата выбранного буфера чтения текущего ограниченного буфера чтения.

Кроме того, когда внутренний формат поверхности рендеринга - RGB10_A2, третья комбинация формата RGBA и типа UNSIGNED_INT_2_10_10_10_REV принято.

Таблица 3.2, на которой вы можете увидеть версию на этой странице 4-я таблица на странице, содержит список комбинаций форматов / типов, и важно отметить, что спецификация не определяет, какие комбинации форматов / типов действительны. Другими словами, он не говорит, что выбирает комбинацию формат / тип из таблицы 3.2, которая соответствует текущему внутреннему формату. Вместо этого он просто говорит, что любой формат / тип в этой таблице действителен. Да, вы правильно прочитали. В соответствии со спецификацией вы можете загружать текстуры RGBA / INT, и реализация может решить, что ваш второй формат - это R / FLOAT ¯ \ _ (ツ) _ / ¯

Вот некоторый код для печати 2-й разрешенной комбинации формат / тип readPixels для текстуры RGBA8UI

function main() {
  const canvas = document.getElementById('canvas');
  const gl = canvas.getContext('webgl2');
  if (!gl) {
    return alert('need webgl2');
  }

  const outputTexture = gl.createTexture();
  gl.activeTexture(gl.TEXTURE1);
  gl.bindTexture(gl.TEXTURE_2D, outputTexture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, 4, 4,
    0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, null);
  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);

  const framebuffer = gl.createFramebuffer();

  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  gl.framebufferTexture2D(
    gl.FRAMEBUFFER,
    gl.COLOR_ATTACHMENT0,
    gl.TEXTURE_2D,
    outputTexture,
    0
  );

  console.log(
    `format/type: ${
      glEnumToString(gl, gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT))
    }/${
      glEnumToString(gl, gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE))
    }`);
  
}
main();

function glEnumToString(gl, value) {
  for (const key in gl) {
    if (gl[key] === value) {
      return key;
    }
  }
  return `0x${value.toFixed(16)}`;
}
<canvas id="canvas"></canvas>

Если я запускаю код выше, Chrome говорит

format/type: RGBA_INTEGER/UNSIGNED_BYTE

Но Firefox говорит

format/type: RGBA_INTEGER/UNSIGNED_INT

Оба из них действительны в соответствии со спецификацией.

Если вы хотите, чтобы он работал везде, вам нужно прочитать данные как RGBA_INTEGER/UNSIGNED_INT, поскольку в первой части спецификации выше сказано, что формат всегда поддерживается для целочисленных форматов без знака.

Изменение кода, чтобы он работал в обоих браузерах

const baseImage = new Image();
baseImage.src = 'https://i.imgur.com/O6aW2Tg.png';
baseImage.crossOrigin = 'anonymous';
baseImage.onload = function() {
  render(baseImage);
};

const vertexShaderSource = `#version 300 es
precision mediump float;

in vec2 position;
out vec2 textureCoordinate;

void main() {
  textureCoordinate = vec2(1.0 - position.x, 1.0 - position.y);
  gl_Position = vec4((1.0 - 2.0 * position), 0, 1);
}`;

const fragmentShaderSource = `#version 300 es
precision mediump float;
precision highp usampler2D;

uniform usampler2D inputTexture;
in vec2 textureCoordinate;
out uvec4 fragColor;

void main() {
    fragColor = texture(inputTexture, textureCoordinate);
}`;

function render(image) {
  const canvas = document.getElementById('canvas');
  const gl = canvas.getContext('webgl2');
  if (!gl) {
    return;
  }

  const positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
    gl.STATIC_DRAW
  );
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  const program = webglUtils.createProgramFromSources(gl, [
    vertexShaderSource,
    fragmentShaderSource,
  ]);
  const positionAttributeLocation = gl.getAttribLocation(
    program,
    'position'
  );
  const inputTextureUniformLocation = gl.getUniformLocation(
    program,
    'inputTexture'
  );
  const vao = gl.createVertexArray();
  gl.bindVertexArray(vao);
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.vertexAttribPointer(
    positionAttributeLocation,
    2,
    gl.FLOAT,
    false,
    0,
    0
  );
  gl.bindVertexArray(null);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  const rawTexture = gl.createTexture();
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, rawTexture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, gl.RGBA_INTEGER, 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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 0);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);

  const outputTexture = gl.createTexture();
  gl.activeTexture(gl.TEXTURE1);
  gl.bindTexture(gl.TEXTURE_2D, outputTexture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, image.width,
    image.height,
    0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, null);
  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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 0);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);

  const framebuffer = gl.createFramebuffer();

  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  gl.framebufferTexture2D(
    gl.FRAMEBUFFER,
    gl.COLOR_ATTACHMENT0,
    gl.TEXTURE_2D,
    outputTexture,
    0
  );
  gl.viewport(0, 0, image.width, image.height);
  gl.clearColor(0, 0, 0, 1.0);
  // gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.useProgram(program);
  gl.uniform1i(inputTextureUniformLocation, 0);
  gl.bindVertexArray(vao);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, rawTexture);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
  gl.bindVertexArray(null);

  const pixels = new Uint32Array(4 * image.width * image.height);
  gl.readPixels(
    0,
    0,
    image.width,
    image.height,
    gl.RGBA_INTEGER,
    gl.UNSIGNED_INT,
    pixels
  );
  console.log(pixels.slice(0, 40));
}
<canvas id="canvas"></canvas>
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script>
...