Как сравнить 2 текстуры в JavaScript / WebGL2? - PullRequest
1 голос
/ 25 мая 2019

Я пишу фрагментный шейдер для алгоритма обработки изображений. Шейдер будет запускаться несколько раз между двумя кадровыми буферами в цикле (пинг-понг). В какой-то момент мне нужно остановить цикл, когда входные и выходные текстуры идентичны.

Что я собираюсь сделать, так это последний шаг алгоритма детектора контуров Канни, «отслеживание контуров гистерезиса». Я хочу создать в режиме реального времени версию алгоритма Canny для GPU / WebGL2 и загрузить его на веб-сайт.

Этот последний шаг выглядит следующим образом:
Дано двойное пороговое изображение, содержащее «сильные» краевые пиксели (1.0) и «слабые» краевые пиксели (0.5)

  • найдите все цепочки слабых пикселей, связанных с сильным пикселем, и отметьте их как "сильные"

  • сохранить все "сильные" пиксели и отбросить все оставшиеся "слабые".

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

1 Ответ

0 голосов
/ 26 мая 2019

Я не на 100% уверен, что вы спрашиваете.Вы просите сравнить на процессоре.Вы можете прочитать содержимое текстуры, прикрепив ее к кадровому буферу и вызвав gl.readPixels.Затем вы можете сравнить все пиксели.Примечание: не все форматы текстур могут быть присоединены к кадровому буферу, но при условии, что вы используете формат, который может.Вы уже прикрепили текстуры к буферам кадров для своего пинг-понга, так что же еще нужно?

Как я уже писал в комментарии к графическому процессору, вы можете написать шейдер для сравнения 2 текстур

#version 300 es
precision highp float;

uniform sampler2D tex1;
uniform sampler2D tex2;

out vec4 outColor;

void main() {
  ivec2 size = textureSize(tex1, 0);  // size of mip 0
  float len = 0.0;
  for (int y = 0; y < size.y; ++y) {
    for (int x = 0; x < size.x; ++x) {
      vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
      vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
      vec4 diff = color1 - color2;
      len = length(diff);
      if (len > 0.0) break;
    }
    if (len > 0.0) break;
  }
  outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}

Теперь просто нарисуйте 1 пиксель и прочитайте его с помощью readPixels.если это 0, текстуры одинаковы.Если это не так, они разные.

Код предполагает, что текстуры имеют одинаковый размер, но, конечно, если они не одинакового размера, то мы уже знаем, что они не могут быть одинаковыми.

// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
  const canvas = document.createElement('canvas');
  canvas.width = 128;
  canvas.height = 128;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = 'blue';
  ctx.fillRect(0, 0, 128, 128);
  ctx.font = '80px monospace';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillStyle = 'yellow';
  ctx.fillText(msg, 64, 64);
  document.body.appendChild(canvas);
  return canvas;
});

const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }

const vs = `#version 300 es
void main() {
  gl_PointSize = 1.0;
  gl_Position = vec4(0, 0, 0, 1);
}
`;

const fs = `#version 300 es
precision highp float;

uniform sampler2D tex1;
uniform sampler2D tex2;

out vec4 outColor;

void main() {
  ivec2 size = textureSize(tex1, 0);  // size of mip 0
  float len = 0.0;
  for (int y = 0; y < size.y; ++y) {
    for (int x = 0; x < size.x; ++x) {
      vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
      vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
      vec4 diff = color1 - color2;
      len = length(diff);
      if (len > 0.0) break;
    }
    if (len > 0.0) break;
  }
  outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}
`;

// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

const textures = canvases.map((canvas) => {
  // gl.createTexture, gl.bindTexture, gl.texImage, etc.
  return twgl.createTexture(gl, {src: canvas});
});

compareTextures(0, 1);
compareTextures(1, 2);

function compareTextures(ndx1, ndx2) {
  gl.useProgram(programInfo.program);
  
  // gl.activeTexture, gl.bindTexture, gl.uniform
  twgl.setUniforms(programInfo, {
    tex1: textures[ndx1],
    tex2: textures[ndx2],
  });
  
  // draw the bottom right pixel
  gl.viewport(0, 0, 1, 1);
  
  gl.drawArrays(gl.POINTS, 0, 1);  // draw 1 point
  
  // read the pixel
  const result = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, result);
  
  console.log('textures', ndx1, 'and', ndx2, 'are', result[0] ? 'the same' : 'not the same'); 
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

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

// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
  const canvas = document.createElement('canvas');
  canvas.width = 128;
  canvas.height = 128;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = 'blue';
  ctx.fillRect(0, 0, 128, 128);
  ctx.font = '80px monospace';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillStyle = 'yellow';
  ctx.fillText(msg, 64, 64);
  document.body.appendChild(canvas);
  return canvas;
});

const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }

const vs = `#version 300 es
void main() {
  gl_PointSize = 1.0;
  gl_Position = vec4(0, 0, 0, 1);
}
`;

const fs = `#version 300 es
precision highp float;

uniform sampler2D tex1;
uniform sampler2D tex2;

out vec4 outColor;

void main() {
  ivec2 size = textureSize(tex1, 0);  // size of mip 0
  float len = 0.0;
  for (int y = 0; y < size.y; ++y) {
    for (int x = 0; x < size.x; ++x) {
      vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
      vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
      vec4 diff = color1 - color2;
      len = length(diff);
      if (len > 0.0) break;
    }
    if (len > 0.0) break;
  }
  if (len > 0.0) {
    discard;
  }
  outColor = vec4(1);
}
`;

// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

const textures = canvases.map((canvas) => {
  // gl.createTexture, gl.bindTexture, gl.texImage, etc.
  return twgl.createTexture(gl, {src: canvas});
});

function wait(ms = 0) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function test() {
  await compareTextures(0, 1);
  await compareTextures(1, 2);
}
test();

async function compareTextures(ndx1, ndx2) {
  gl.clear(gl.DEPTH_BUFFER_BIT);
  gl.enable(gl.DEPTH_TEST);
  gl.useProgram(programInfo.program);
  
  // gl.activeTexture, gl.bindTexture, gl.uniform
  twgl.setUniforms(programInfo, {
    tex1: textures[ndx1],
    tex2: textures[ndx2],
  });
  
  // draw the bottom right pixel
  gl.viewport(0, 0, 1, 1);
  
  const query = gl.createQuery();
  gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
  gl.drawArrays(gl.POINTS, 0, 1);  // draw 1 point
  gl.endQuery(gl.ANY_SAMPLES_PASSED);
  gl.flush();
  
  let ready = false;
  while(!ready) {
    await wait();
    ready = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
  }
  
  const same = gl.getQueryParameter(query, gl.QUERY_RESULT);
  
  console.log('textures', ndx1, 'and', ndx2, 'are', same ? 'the same' : 'not the same'); 
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
...