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

时间:2018-09-10 17:54:21

标签: webgl2

更新

在下面我的问题结尾处查看基本原理


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

是否可以在Vertex和Frag着色器中声明输入/输出变量,从而不必缩放坐标?

顶点着色器中的某处:

...
out vec2 TextureCoordinates;

在片段着色器中的某处:

...
in vec2 TextureCoordinates;

我希望TextureCoordinates成为ivec2并已经缩放。

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

我们以几种方式将数据映射到纹理。我们遵循的最简单的方法是-假设我们可以将数据作为平面数组访问-沿纹理的宽度将其布局并提高纹理的高度,直到完成。

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

使用整数坐标计算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);
}

很明显,对于一系列大型纹理操作,仅在偏移/坐标计算上,我们就节省了数十万次操作。

1 个答案:

答案 0 :(得分:1)

尚不清楚您为什么想要做自己想做的事情。最好问类似“我正在尝试绘制图像/实现后处理发光/做射线追踪/ ...,并希望使用未归一化的纹理坐标的事情,因为”,然后我们可以告诉您是否可以使用您的解决方案以及解决方法。

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

仍然,您可以将未归一化的值传递为floatvec2/3/4,并在片段着色器中将其转换为intivec2/3/4

另一个问题是,您将无法使用texelFetch进行采样,该函数采用texel坐标而不是归一化纹理坐标。它仅返回单个像素的精确值。它不像普通的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;

Example2

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作为输入,并仅计算该区域的总和。然后,用每个块的总和写出较大的纹理,最后将块的总和求和。

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