代码之家  ›  专栏  ›  技术社区  ›  Jeff Saremi

在frag shader中接收非规范化的输出纹理坐标

  •  0
  • Jeff Saremi  · 技术社区  · 6 年前

    更新

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


    使用 WebGL2 我可以通过一个非规范化的坐标访问一个texel(对不起,这不是正确的行话)。这意味着我不必像以前那样把它们缩小到0-1 texture2D() . 但是,片段明暗器的输入仍然是 vec2/3 在标准化值中。

    是否有一种方法可以在顶点和frag明暗器中声明in/out变量,这样我就不必缩放坐标了?

    顶点着色中的某个位置:

    ...
    out vec2 TextureCoordinates;
    

    在frag shader中:

    ...
    in vec2 TextureCoordinates;
    

    我想要 TextureCoordinates 成为 ivec2 而且已经有了规模。

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

    我们以几种方式将数据映射到纹理。我们遵循的最简单的方法是——假设我们可以以平面数组的形式访问数据——沿着纹理的宽度排列数据,并沿纹理的高度增加数据,直到我们完成。

    因为我们的思想、逻辑和计算都是基于张量/矩阵索引——在片段着色中——我们必须映射回/从x-y纹理坐标映射到索引。这里的中间步骤是计算一个给定的texel位置的偏移量。然后从这个偏移量,我们可以从矩阵的步幅计算出矩阵指数。

    对于非常大的纹理,在WebGL1中计算偏移似乎比使用整数坐标的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 coords时的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 回复  |  直到 6 年前
        1
  •  1
  •   gman    6 年前

    不清楚你为什么要做你想做的事。最好问一些类似“我正在尝试绘制图像/实现后处理辉光/do-ray跟踪/……为了做到这一点,我想使用非标准化的纹理坐标,因为“然后我们可以告诉你你的解决方案是否可行以及如何解决它。

    无论如何,传球 int unsigned int ivec2/3/4 uvec2/3/4 因为一个变量是支持的,但是 非插值 . 你必须把它们声明为 flat .

    但是,可以将未规范化的值作为 float vec2/3/4 以及转化为 int , IVEC2/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>

    因此,对于您更新后的问题,仍然不清楚您想要做什么。为什么要将varying传递到片段明暗器?你不能在片段着色程序中做你想要的任何数学吗?

    例子:

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

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