WebGL 细碎记录

这篇文字主要记录使用 WebGL 中遇到的问题和……其他琐碎的东西。

使用 WebGL 是为了解决图像处理等密集型任务原生 js 性能不足的问题。但 WebGL 好像也无法做到 16ms 内处理一张图片(1280 x 800),5×5 的高斯模糊卷积核大概需要 35ms(不是非常确定浏览器用的核显还是独显,并且整个时间包括)。

for 循环

在关于 WebGL 的介绍文章中,很少有提及循环的,以至于我在写卷积的时候,用 js 生成卷积核每一个像素的处理语句,这导致卷积核较大的时候,语句很长,然后报错——webgl ERROR: Expression too complex。

于是我就想,webgl 总得有循环吧,但网上搜到的几篇文章都没有提到循环,后来是以 glsl 作为关键字搜索到的。也是,webgl 是封装,具体的 glsl 语法还是要看 glsl 的。

int length = ${kernelHeight * kernelWidth};

for (int i = 0; i < length; i++) {
  int x = i % ${kernelWidth};
  int y = i / ${kernelHeight};
  colorSum += texture(u_image, v_texCoord + onePixel * vec2(x - ${halfWidth}, y - ${halfHeight})) * u_kernel[i];
}

${} 部分是 js 模板字符串里的表达式,不需要关心。

glsl 的 for 循环和 c 基本一样,和 js 也就关键字的差别。

WebGL 获取图片数据

gl.readPixels() 函数会返回纹理的像素数据。

const results = new ImageData(imgData.width, imgData.height)
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, results.data)

但是,这里获取的图片数据是上下颠倒的,因为 gl 默认原点在左下角。为了得到正确的图片数据,开发者需要自行处理数据。

function flipImageY (source: ImageData) {
  const half = source.height >> 1
  let temp = new Uint8ClampedArray(0)
  for (let y = 0; y < half; y++) {
    temp = source.data.slice(y * source.width * 4, (y + 1) * source.width * 4)
    source.data.set(
      source.data.slice(
        (source.height - y - 1) * source.width * 4,
        (source.height - y) * source.width * 4
      ),
      y * source.width * 4
    )
    source.data.set(temp, (source.height - y - 1) * source.width * 4)
  }
}

不能直接获取正确的数据吗?嗯……反正我没找到。