我想在我的着色器中使用像素坐标

时间:2019-01-08 23:00:17

标签: coordinates pixel webgl2

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

  

WebGL2还增加了使用像素坐标读取纹理的功能。哪种方法最好取决于您。我觉得使用纹理坐标比像素坐标更常见。

在其他地方没有提及,然后以像素为单位均匀地传递纹理尺寸并从那里进行计算,是否有一种方法可以访问此处的像素坐标而无需计算?

1 个答案:

答案 0 :(得分:0)

您可以使用texelFetch

从WebGL2中的纹理读取单个像素/纹理。
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>