WebGL:如何正确混合带有 Alpha 通道的 PNG?

18

我对OpenGL和WebGL都很陌生。 我试图绘制一个带有alpha通道的纹理四边形,但是我就是无法正确混合颜色。

这是我想要的结果

This is the result I'm looking for

而这是WebGL的结果

And this is what the WebGL result looks like

你可以看到在骰子边缘上有一种白色轮廓,在原始图像中则没有。

以下是我在WebGL中进行混合的方式

gl.clearColor(0.5, 0.5, 0.5, 1);
gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

这是我的片段着色器:

precision mediump float;

varying vec2 vUV;

uniform sampler2D texture;

void main(void) {
    vec4 frag = texture2D(texture, vUV);
    gl_FragColor = frag;
}

你有任何想法为什么会发生这种情况吗?顺便说一句,我没有创建mipmaps。

1个回答

41

这个问题在SO上已有答案,但是...

WebGL画布默认需要预乘alpha通道。WebGL画布在网页上进行合成(混合到页面上)。所以...

您是否希望将WebGL图像与网页混合?

如果不需要,请执行以下操作之一

  • Turn off alpha in the canvas

    var gl = someCanvas.getContext("webgl", { alpha: false });
    
  • Make sure your alpha stays at 1.0

    The easy way to do that is to just clear it after rendering with

    gl.colorMask(false, false, false, true);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    

如果您确实希望与网页混合,则可以执行以下操作之一

  • Make sure the values you write into the canvas are premultiplied alpha values.

  • Tell the browser the values in the canvas are not premultiplied

    var gl = someCanvas.getContext("webgl", {premultipliedAlpha: false});
    

默认情况下,加载到 WebGL 中的图像使用未预乘的 alpha。这意味着您需要执行以下操作:

  • Set your canvas to not be premultiplied

  • Do the multiplication yourself in your shader

    gl_FragColor.rgb *= gl_FragColor.a;
    
  • Tell WebGL to premultiply the texture when you load it into WebGL

    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
    

假设你的画布已经进行了预乘操作,你希望你的混合函数为:

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

这是因为您不需要将源乘以alpha,因为它已经进行了预乘

以下是一个通过着色器乘以alpha来与页面混合的示例。紫色条纹是CSS背景。图像被绘制两次,一次用于填充画布,一次在其上方1/2大小的位置。您可以看到所有元素都正确混合。

var vs = `
attribute vec4 position;
varying vec2 v_texcoord;
uniform mat4 u_matrix;
void main() {
  gl_Position = u_matrix * position;
  v_texcoord = position.xy * .5 + .5;
}
`;

var fs = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_tex;
void main() {
  gl_FragColor = texture2D(u_tex, v_texcoord);
  gl_FragColor.rgb *= gl_FragColor.a;
}
`;

var gl = document.querySelector("canvas").getContext("webgl");
var m4 = twgl.m4;
var programInfo = twgl.createProgramInfo(gl, [vs, fs]);
var bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);

var tex = twgl.createTexture(gl, { 
  src: "https://i.imgur.com/iFom4eT.png",
  crossOrigin: "",
}, render);

function render() {
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  
  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, {
    u_tex: tex,
    u_matrix: m4.identity(),
  });
  twgl.drawBufferInfo(gl, bufferInfo);

  twgl.setUniforms(programInfo, {
    u_tex: tex,
    u_matrix: m4.scaling([0.5, 0.5, 1]),
  });
  twgl.drawBufferInfo(gl, bufferInfo);
}
canvas {
  background-color: purple;
  background-image: linear-gradient(45deg, rgba(255, 255, 255, 1) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 1) 50%, rgba(255, 255, 255, 1) 75%, transparent 75%, transparent);
  background-size: 50px 50px;
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

下面是一个将canvas设置为未预乘的示例。唯一的区别是我向getContext传递了{premultipliedAlpha: false},同时移除了gl_FragColor.rgb *= gl_FragColor.a,并将混合函数更改为gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

var vs = `
attribute vec4 position;
varying vec2 v_texcoord;
uniform mat4 u_matrix;
void main() {
  gl_Position = u_matrix * position;
  v_texcoord = position.xy * .5 + .5;
}
`;

var fs = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_tex;
void main() {
  gl_FragColor = texture2D(u_tex, v_texcoord);
}
`;

var gl = document.querySelector("canvas").getContext("webgl", {
  premultipliedAlpha: false,
});
var m4 = twgl.m4;
var programInfo = twgl.createProgramInfo(gl, [vs, fs]);
var bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);

var tex = twgl.createTexture(gl, { 
  src: "https://i.imgur.com/iFom4eT.png",
  crossOrigin: "",
}, render);

function render() {
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  
  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, {
    u_tex: tex,
    u_matrix: m4.identity(),
  });
  twgl.drawBufferInfo(gl, bufferInfo);

  twgl.setUniforms(programInfo, {
    u_tex: tex,
    u_matrix: m4.scaling([0.5, 0.5, 1]),
  });
  twgl.drawBufferInfo(gl, bufferInfo);
}
canvas {
  background-color: purple;
  background-image: linear-gradient(45deg, rgba(255, 255, 255, 1) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 1) 50%, rgba(255, 255, 255, 1) 75%, transparent 75%, transparent);
  background-size: 50px 50px;
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>


非常好的答案和解释。设置 '{ alpha: false }' 正好达到了我想要的效果! - mode777
1
设置{premultipliedAlpha: false}正是我想要的!一个问题得到了多个答案! - jameshfisher
好的回答。如果您的纹理具有alpha通道并且使用线性插值,则还需要使用“gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL,true)”。否则,您可能会在纹理特征的边缘(即纹理的不透明和透明部分之间的边界)看到暗色边缘。 - Adam Gawne-Cain
1
不知道为什么,但当我使用 alpha: false 时,帧速率会下降约35%,所以我只能通过将画布的 background-color: #000 设置为避免这种情况并获得结果。(Chrome 90) - Reahreic
2
gl_FragColor.rgb *= gl_FragColor.a 救了我的一天,最近的 Chromium 100+ 在 ARM 上使用 WebGL2 premultipliedAlpha: false 时会严重掉帧。谢谢! - norbertas.gaulia
@norbertas.gaulia 请提交错误报告,尤其是如果您能提供一个小的MVCE存储库。 - gman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接