Я хочу использовать пиксельные координаты в моем шейдере - PullRequest
0 голосов
/ 09 января 2019

С https://webgl2fundamentals.org/webgl/lessons/webgl-image-processing.html

WebGL2 добавляет возможность читать текстуру, используя также пиксельные координаты. Какой путь лучше, зависит от вас. Мне кажется, что чаще использовать текстурные координаты, чем пиксельные.

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

1 Ответ

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

Вы можете читать отдельные пиксели / тексели из текстуры в WebGL2 с помощью texelFetch

vec4 color = texelFetch(someUniformSampler, ivec2(pixelX, pixelY), intMipLevel);

Например, вычислить средний цвет текстуры, читая каждый пиксель

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

const fs = `#version 300 es
precision highp float;
uniform sampler2D tex;
out vec4 outColor;
void main() {
  int level = 0;
  ivec2 size = textureSize(tex, level);
  vec4 color = vec4(0);
  for (int y = 0; y < size.y; ++y) {
    for (int x = 0; x < size.x; ++x) {
      color += texelFetch(tex, ivec2(x, y), level);
    }
  }
  outColor = color / float(size.x * size.y);
}
`;

function main() {
  const gl = document.createElement('canvas').getContext('webgl2');
  if (!gl) {
    return alert('need webgl2');
  }
  const prg = twgl.createProgram(gl, [vs, fs]);
  
  gl.canvas.width = 1;
  gl.canvas.height = 1;
  gl.viewport(0, 0, 1, 1);

  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  // so we don't need mips
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  // so we can pass a non multiple of 4 bytes
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
  
  const values = new Uint8Array([10, 255, 13, 70, 56, 45, 89]);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, values.length, 1, 0, gl.RED, gl.UNSIGNED_BYTE, values);
  
  gl.useProgram(prg);
  gl.drawArrays(gl.POINTS, 0, 1);
  
  const gpuAverage = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, gpuAverage);
  
  const jsAverage = values.reduce((s, v) => s + v) / values.length;
  
  console.log('gpuAverage:', gpuAverage[0]);
  console.log('jsAverage:', jsAverage);
}

main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

примечания: так как холст RGBA8 может получить только целочисленный результат. Может измениться на некоторый формат с плавающей точкой, но это усложнит пример, который не о рендеринге, а о texelFetch.

Конечно, просто изменив данные с R8 на RGBA8, мы можем сделать 4 массива как один раз, если мы чередуем значения

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

const fs = `#version 300 es
precision highp float;
uniform sampler2D tex;
out vec4 outColor;
void main() {
  int level = 0;
  ivec2 size = textureSize(tex, level);
  vec4 color = vec4(0);
  for (int y = 0; y < size.y; ++y) {
    for (int x = 0; x < size.x; ++x) {
      color += texelFetch(tex, ivec2(x, y), level);
    }
  }
  outColor = color / float(size.x * size.y);
}
`;

function main() {
  const gl = document.createElement('canvas').getContext('webgl2');
  if (!gl) {
    return alert('need webgl2');
  }
  const prg = twgl.createProgram(gl, [vs, fs]);
  
  gl.canvas.width = 1;
  gl.canvas.height = 1;
  gl.viewport(0, 0, 1, 1);

  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  // so we don't need mips
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  // so we can pass a non multiple of 4 bytes
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
  
  const values = new Uint8Array([
     10, 100, 200, 1,
     12, 150, 231, 9,
     50, 129, 290, 3,
     45, 141, 300, 2,
     12, 123, 212, 4,
  ]);
  const width = 1;
  const height = values.length / 4;
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, values);
  
  gl.useProgram(prg);
  gl.drawArrays(gl.POINTS, 0, 1);
  
  const gpuAverages = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, gpuAverages);
  
  let jsAverages = [0, 0, 0, 0];
  for (let i = 0; i < height; ++i) {
    for (let j = 0; j < 4; ++j) {
      jsAverages[j] += values[i * 4 + j];
    }
  }
  jsAverages = jsAverages.map(v => v / height);
  
  console.log('gpuAverage:', gpuAverages.join(', '));
  console.log('jsAverage:', jsAverages.join(', '));
}

main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

Чтобы сделать больше, нужно найти способ упорядочить данные и использовать вход для фрагментного шейдера, чтобы выяснить, где находятся данные. Например, мы снова чередуем данные, 5 массивов, так что данные идут 0,1,2,3,4,0,1,2,3,4,0,1,2,3,4.

Давайте вернемся к R8 и сделаем 5 отдельных массивов. Нам нужно нарисовать 5 пикселей. Мы можем сказать, какой пиксель рисуется, посмотрев на gl_FragCoord. Мы можем использовать это, чтобы сместить, на какие пиксели мы смотрим и сколько пропустить.

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

const fs = `#version 300 es
precision highp float;
uniform sampler2D tex;
uniform int numArrays;
out vec4 outColor;
void main() {
  int level = 0;
  int start = int(gl_FragCoord.x);
  ivec2 size = textureSize(tex, level);
  vec4 color = vec4(0);
  for (int y = 0; y < size.y; ++y) {
    for (int x = start; x < size.x; x += numArrays) {
      color += texelFetch(tex, ivec2(x, y), level);
    }
  }
  outColor = color / float(size.x / numArrays * size.y);
}
`;

function main() {
  const gl = document.createElement('canvas').getContext('webgl2');
  if (!gl) {
    return alert('need webgl2');
  }
  const prg = twgl.createProgram(gl, [vs, fs]);
  const numArraysLoc = gl.getUniformLocation(prg, "numArrays");
  
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  // so we don't need mips
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  // so we can pass a non multiple of 4 bytes
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
  
  const numArrays = 5;
  const values = new Uint8Array([
     10, 100, 200, 1, 70,
     12, 150, 231, 9, 71,
     50, 129, 290, 3, 90,
     45, 141, 300, 2, 80,
     12, 123, 212, 4, 75,
  ]);
  const width = values.length;
  const height = 1;
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, width, height, 0, gl.RED, gl.UNSIGNED_BYTE, values);
  
  gl.canvas.width = numArrays;
  gl.canvas.height = 1;
  gl.viewport(0, 0, numArrays, 1);

  gl.useProgram(prg);
  gl.uniform1i(numArraysLoc, numArrays);
  gl.drawArrays(gl.POINTS, 0, 1);
  
  const gpuData = new Uint8Array(4 * numArrays);
  gl.readPixels(0, 0, numArrays, 1, gl.RGBA, gl.UNSIGNED_BYTE, gpuData);
  const gpuAverages = [];
  for (let i = 0; i < numArrays; ++i) {
    gpuAverages.push(gpuData[i * 4]); // because we're only using the RED channel
  }
  const jsAverages = (new Array(numArrays)).fill(0);
  const numValues = (width / numArrays) * height;
  for (let i = 0; i < width / numArrays; ++i) {
    for (let j = 0; j < numArrays; ++j) {
      jsAverages[j] += values[i * numArrays + j] / numValues;
    }
  }
  
  console.log('gpuAverage:', gpuAverages.join(', '));
  console.log('jsAverage:', jsAverages.join(', '));
}

main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
...