通过生成MipMap对Webgl2 R32F Texture中的值求和

时间:2018-06-22 01:47:10

标签: webgl webgl2

如果我已经将数据渲染到R32F纹理(2 ^ 18(〜250,000)个纹理像素)中,并且我想计算这些值的总和,是否可以通过要求GPU生成一个mipmap来做到这一点?

(这种想法是,最小的mipmap级别将具有一个包含所有原始纹理像素平均值的单个纹理像素)

我将使用哪些mipmap设置(钳位等)来生成正确的平均值?

我对webgl体操不太满意,并且希望了解如何将1到2 ^ 18的数字渲染到R32F纹理中然后在该纹理上求和的摘要。

对于这种数量的纹理像素,这种方法是否比尝试将纹理像素转移回cpu并在javascript中执行求和更快?

谢谢!

1 个答案:

答案 0 :(得分:2)

没有用于定义用于生成Mipmap的算法的设置。钳位设置,过滤器设置无效。您可以使用gl.hint来设置是否要优先考虑质量而不是性能的提示,但是驾驶员没有义务甚至注意该标志。此外,每个驱动程序都不同。生成mipmap的结果是用于对WebGL进行指纹识别的差异之一。

无论如何,如果您不关心所使用的算法,而只想读取生成mipmap的结果,则只需将最后一个mip附加到帧缓冲区并在调用gl.generateMipmap之后读取像素

您可能不会将所有1到2 ^ 18的数字渲染为纹理,但这并不难。您只需画一个512x512的四边形即可。片段着色器可能看起来像这样

#version 300 es
precision highp float;
out vec4 fragColor;
void main() {
  float i = 1. + gl_FragCoord.x + gl_FragCoord.y * 512.0;
  fragColor = vec4(i, 0, 0, 0);
}

如果您要使用其他尺寸,当然可以将512.0作为制服传递。

渲染为浮点纹理是WebGL2的可选功能。台式机支持该功能,但截至2018年,大多数移动设备均不支持。同样,能够过滤浮点纹理也是一项可选功能,自2018年起,大多数移动设备通常不支持此功能,而台式机通常不支持该功能。

function main() {
  const gl = document.createElement("canvas").getContext("webgl2");
  if (!gl) {
    alert("need webgl2");
    return;
  }
  {
    const ext = gl.getExtension("EXT_color_buffer_float");
    if (!ext) {
      alert("can not render to floating point textures");
      return;
    }
  }
  {
    const ext = gl.getExtension("OES_texture_float_linear");
    if (!ext) {
       alert("can not filter floating point textures");
       return;
    }
  }
  
  // create a framebuffer and attach an R32F 512x512 texture
  const numbersFBI = twgl.createFramebufferInfo(gl, [
    { internalFormat: gl.R32F, minMag: gl.NEAREST },
  ], 512, 512);
  
  const vs = `
  #version 300 es
  in vec4 position;
  void main() {
    gl_Position = position;
  }
  `;
  const fillFS = `
  #version 300 es
  precision highp float;
  out vec4 fragColor;
  void main() {
    float i = 1. + gl_FragCoord.x + gl_FragCoord.y * 512.0;
    fragColor = vec4(i, 0, 0, 0);
  }
  `
  
  // creates a buffer with a single quad that goes from -1 to +1 in the XY plane
  // calls gl.createBuffer, gl.bindBuffer, gl.bufferData
  const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
  
  const fillProgramInfo = twgl.createProgramInfo(gl, [vs, fillFS]);
  gl.useProgram(fillProgramInfo.program);

  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, fillProgramInfo, quadBufferInfo);
  
  // tell webgl to render to our texture 512x512 texture
  // calls gl.bindBuffer and gl.viewport
  twgl.bindFramebufferInfo(gl, numbersFBI);
  
  // draw 2 triangles (6 vertices)
  gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
  
  // compute the last mip level
  const miplevel = Math.log2(512);

  // get the texture twgl created above
  const texture = numbersFBI.attachments[0];

  // create a framebuffer with the last mip from
  // the texture
  const readFBI = twgl.createFramebufferInfo(gl, [
    { attachment: texture, level: miplevel },
  ]);
  
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // try each hint to see if there is a difference      
  ['DONT_CARE', 'NICEST', 'FASTEST'].forEach((hint) => {
    gl.hint(gl.GENERATE_MIPMAP_HINT, gl[hint]);
    gl.generateMipmap(gl.TEXTURE_2D);

    // read the result.
    const result = new Float32Array(4);
    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, result);

    log('mip generation hint:', hint);
    log('average:', result[0]);
    log('average * count:', result[0] * 512 * 512);
    log(' ');
  });
  
  function log(...args) {
    const elem = document.createElement('pre');
    elem.textContent = [...args].join(' ');
    document.body.appendChild(elem);
  }
}
main();
pre {margin: 0}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

请注意,我使用twgl.js来简化代码。如果您不知道如何制作帧缓冲区和附加纹理,或者不知道如何设置缓冲区和属性,编译着色器以及设置制服,那么您会问的问题范围太广,建议您阅读some tutorials

让我指出一下,如何不能保证此方法比其他方法更快。首先,取决于驾驶员。驱动程序可能会通过软件执行此操作(尽管不太可能)。

一个明显的提高速度是使用RGBAF32,让代码一次执行4个值,然后最后读取所有4个通道(R,G,B,A)并将它们相加。

此外,由于您只关心最后的1x1像素,因此您要求代码比更直接的方法渲染更多的像素。真的,您只需要渲染1个像素即可。但是对于2 ^ 18值的示例来说,这是512x512的纹理,这意味着256x526、128x128、64x64、32x32、16x16、8x8、4x4和2x2的mip都已分配和计算,这无疑是浪费的时间。实际上,规范说所有mips都是从第一个mip生成的。当然,驱动程序可以随意使用快捷方式,并且很可能会从Mip N-1生成Mip N,因为结果类似,但这不是规范的定义方式。但是,即使从以前的一个生成一个mip,也不会计算出您所关心的87380个值。

我只是猜测生成比2x2大的卡盘会更快。同时,还有纹理缓存,如果我理解正确的话,它们通常会缓存纹理的矩形部分,以便从mip读取4个值很快。当您有纹理缓存未命中时,它确实会降低性能。因此,如果您的块太大,则可能会导致许多高速缓存未命中。您基本上必须进行测试,每个GPU可能会显示不同的性能特征。

另一种加速方法是考虑使用多个绘图缓冲区,然后您可以为每个片段着色器迭代写入16到32个值,而不仅仅是4个。