在片段着色器中接收非规范化输出纹理坐标

Receiving denormalized output texture coordinates in Frag shader

更新

请参阅下面我的问题末尾的基本原理


使用 WebGL2 我可以通过其非规范化坐标访问纹素(抱歉,这不是正确的行话)。这意味着我不必像在 texture2D() 中那样将它们缩小到 0-1。 然而,片段着色器的输入仍然是规范化值中的 vec2/3

有没有办法在顶点和片段着色器中声明 in/out 变量,这样我就不必缩放坐标了?

顶点着色器中的某处:

...
out vec2 TextureCoordinates;

片段着色器中的某处:

...
in vec2 TextureCoordinates;

我希望 TextureCoordinatesivec2 并且已经缩放。

这个问题和我关于 webgl 的所有其他问题都与使用 WebGL 的一般计算有关。我们正在尝试使用 WebGL 进行张量(多维矩阵)运算。

我们以几种方式将数据映射到纹理。我们遵循的最简单的方法是——假设我们可以将我们的数据作为一个平面数组来访问——沿着纹理的宽度布置它,并沿着纹理的高度向上布置,直到我们完成。

由于我们的思维、逻辑和计算都基于 tensor/matrix 索引——在片段着色器内部——我们必须将 X-Y 纹理坐标映射回 to/from 到索引。这里的中间步骤是计算纹素给定位置的偏移量。然后根据该偏移量,我们可以根据其步幅计算矩阵索引。

在 webgl 1 中为非常大的纹理计算偏移量似乎比使用整数坐标的 webgl2 花费的时间长得多。见下文:

WebGL 1 偏移计算

  int coordsToOffset(vec2 coords, int width, int height) {
    float s = coords.s * float(width);
    float t = coords.t * float(height);
    int offset = int(t) * width + int(s);
    return offset;
  }
  vec2 offsetToCoords(int offset, int width, int height) {
    int t = offset / width;
    int s = offset - t*width;
    vec2 coords = (vec2(s,t) + vec2(0.5,0.5)) / vec2(width, height);
    return coords;
  }

存在int坐标的WebGL 2偏移计算

int coordsToOffset(ivec2 coords, int width) {
    return coords.t * width + coords.s;
}
ivec2 offsetToCoords(int offset, int width) {
  int t = offset / width;
  int s = offset - t*width;
  return ivec2(s,t);
}

很明显,对于一系列大型纹理操作,我们仅在 offset/coords 计算中就节省了数十万次操作。

不清楚你为什么要做你想做的事。最好问 "I'm trying to draw an image/implement post processing glow/do ray tracing/... and to do that I want to use un-normalized texture coordinates because " 这样的问题,然后我们可以告诉您您的解决方案是否有效以及如何解决。

在任何情况下,都支持传递 intunsigned intivec2/3/4uvec2/3/4 作为变量,但 不支持插值 .您必须将它们声明为 flat.

不过,您可以将未规范化的值作为 floatvec2/3/4 传递,并在片段着色器中转换为 intivec2/3/4

另一个问题是您将无法使用 texelFetch 进行采样,该函数采用纹素坐标而不是归一化纹理坐标。它只是 returns 单个像素的精确值。它不像普通的 texture 函数那样支持过滤。

示例:

function main() {
  const gl = document.querySelector('canvas').getContext('webgl2');
  if (!gl) {
    return alert("need webgl2");
  }
  
  const vs = `
   #version 300 es
   in vec4 position;
   in ivec2 texelcoord;
   
   out vec2 v_texcoord;
   
   void main() {
      v_texcoord = vec2(texelcoord);
      gl_Position = position;
   }
  `;
  
  const fs = `
  #version 300 es
  precision mediump float;
  in vec2 v_texcoord;
  out vec4 outColor;
  uniform sampler2D tex;
  
  void main() {
    outColor = texelFetch(tex, ivec2(v_texcoord), 0);
  }
  `;
  
  // compile shaders, link program, look up locations
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
  
  // create buffers via gl.createBuffer, gl.bindBuffer, gl.bufferData)
  const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
    position: {
      numComponents: 2,
      data: [
      -.5, -.5,
       .5, -.5,
        0,  .5,
      ],
    },
    texelcoord: {
      numComponents: 2,
      data: new Int32Array([
        0,  0,
       15,  0,
        8, 15,
      ]),
    }
  });
  
  // make a 16x16 texture
  const ctx = document.createElement('canvas').getContext('2d');
  ctx.canvas.width = 16;
  ctx.canvas.height = 16;
  for (let i = 23; i > 0; --i) {
    ctx.fillStyle = `hsl(${i / 23 * 360 | 0}, 100%, ${i % 2 ? 25 : 75}%)`;
    ctx.beginPath();
    ctx.arc(8, 15, i, 0, Math.PI * 2, false);
    ctx.fill();
  }
  const tex = twgl.createTexture(gl, { src: ctx.canvas });
  
  gl.useProgram(programInfo.program);
  
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  // no need to set uniforms since they default to 0
  // and only one texture which is already on texture unit 0
  gl.drawArrays(gl.TRIANGLES, 0, 3);
}
main();
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

所以在回答你更新的问题时,你还不清楚你想做什么。为什么要将变量传递给片段着色器?你不能在片段着色器本身中做任何你想做的数学吗?

示例:

uniform sampler2D tex;
out float result;

// some all the values in the texture
vec4 sum4 = vec4(0);
ivec2 texDim = textureSize(tex, 0);
for (int y = 0; y < texDim.y; ++y) {
  for (int x = 0; x < texDim.x; ++x) {
    sum4 += texelFetch(tex, ivec2(x, y), 0);
  }
}
result = sum4.x + sum4.y + sum4.z + sum4.w;

示例 2

uniform isampler2D indices;
uniform sampler2D data;

out float result;

// some only values in data pointed to by indices
vec4 sum4 = vec4(0);
ivec2 texDim = textureSize(indices, 0);
for (int y = 0; y < texDim.y; ++y) {
  for (int x = 0; x < texDim.x; ++x) {
    ivec2 index = texelFetch(indices, ivec2(x, y), 0).xy;
    sum4 += texelFetch(tex, index, 0);
  }
}
result = sum4.x + sum4.y + sum4.z + sum4.w;

请注意,我也不是 GPGPU 方面的专家,但我预感上面的代码不是最快的方法,因为我相信并行化是基于输出发生的。上面的代码只有 1 个输出,所以没有并行化?更改起来很容易,因此它将块 ID、图块 ID、区域 ID 作为输入并仅计算该区域的总和。然后你会用每个块的总和写出一个更大的纹理,最后对块总和求和。

此外,依赖和不均匀的纹理读取是一个已知的性能问题。第一个示例按顺序读取纹理。这是缓存友好的。第二个示例以随机顺序(由索引指定)读取纹理,这对缓存不友好。