首页 > 解决方案 > 我想在我的着色器中使用像素坐标

问题描述

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

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

除了以像素为单位传递统一的纹理尺寸并从那里计算之外,没有提到这一点,有没有一种方法可以在没有计算的情况下访问这些像素坐标,因为它应该在这里?

标签: coordinatespixelwebgl2

解决方案


您可以使用 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>


推荐阅读