通过 webgl 加载 PNG 图像并不完美

Loading the PNG Image by webgl is not perfect

当使用canvas.getContext('2d')加载具有透明部分的png文件时,它看起来与png文件本身完全一样。但是用canvas.getContext('webgl')加载时,透明部分会显示为白色。然后如果你在着色器中添加 discard ,它会更好但仍然不如 png 文件完美。如何解决这个问题?


gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.img);

void main() {
      vec4 color = texture2D(u_image, v_texCoord);
      if(color.a < 0.5) {
         discard;
      }
      gl_FragColor = color;
    }

听起来您可能需要激活混合。

gl.enable(gl.BLEND);

然后将混合函数设置为使用预乘 alpha(默认)

gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

透明度其实有点复杂

#1 canvas 本身需要什么

默认 canvas 需要预乘 alpha。换句话说,它希望您提供 RGBA 值,其中 RGB 已乘以 A.

您可以设置 canvas,这样在通过传入 premultipledAlpha: false 创建 webgl 上下文时它不会期望预乘 alpha,如

const gl = someCanvas.getContext('webgl', {premultipliedAlpha: false});

注意:IIRC 这不适用于 iOS。

#2 你加载图片的格式

在 WebGL 中加载图像的默认值是未预乘的 alpha。换句话说,如果图像的像素是

255, 128, 64, 128   RGBA

它将完全像这样加载 (*)

您可以通过设置

告诉WebGL在加载图像时为您预乘
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

调用前 gl.texImage2D.

现在上面的相同像素将最终成为

128, 64, 16, 128    RGBA

每个RGB都乘以A(上面的A是128,其中128代表128/255或0.5019607843137255)

#3 你在着色器中写了什么

如果您加载了未预乘的数据,您可能会选择在着色器中进行预乘

gl_FragColor = vec4(someColor.rgb * someColor.a, someColor.a);

#4 你如何融合

如果您想将正在绘制的内容混合到已绘制的内容中,则需要打开混合

gl.enable(gl.BLEND);

但是您还需要设置混合的发生方式。有多种功能会影响混合。最常用的一种是 gl.blendFunc,它设置 src 像素(由着色器生成的像素)和 dst 像素(在 canvas 中绘制在顶部的像素)在被渲染之前如何受到影响合并。 2 个最常见的设置是

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);  // unpremultiplied alpha

gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);  // premultiplied alpha

第一个参数是如何乘以 src 像素。上面我们要么乘以 src 的 alpha (SRC_ALPHA),要么乘以 1 (ONE)。第二个参数是如何乘以 dst 像素。 ONE_MINUS_SRC_ALPHA 正是它所说的 (1 - alpha)

如何将所有这些组合在一起取决于您。

This article and This one 稍微涵盖了这些问题

(*) 图片可能有 color conversion applied