WebGL:如何正确混合Alpha通道png

WebGL: How to correctly blend alpha channel png

我是OpenGL的新手,甚至是WebGL的新手。 我正在尝试绘制带有alpha通道的纹理四边形。 但是我只是不能正确混合。

这是我正在寻找的结果

This is the result Im looking for

这就是WebGL结果的样子

And this is what the WebGL result looks like

如您所见,骰子边缘上有一个白色的轮廓,在原始图像中没有。

这就是我在WebGL中进行混合的方式

1
2
3
4
        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);

这是我的片段着色器:

1
2
3
4
5
6
7
8
9
10
precision mediump float;

varying vec2 vUV;

uniform sampler2D texture;

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

知道为什么会这样吗? 我不会创建Mipmap,顺便说一句。


这已经在其他地方得到了解答,但是...

WebGL画布默认为要求预乘alpha。 WebGL画布在网页上混合(混合到页面上)。所以...

您是否不想将WebGL图像与网页混合?

如果不是,则您不想与网页融合,然后执行以下一项操作

  • 关闭画布中的Alpha

    1
    var gl = someCanvas.getContext("webgl", { alpha: false });
  • 确保您的Alpha值保持在1.0

    最简单的方法是在渲染后清除它

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

如果是,您确实要与网页融合,然后执行以下一项操作

  • 确保您写入画布的值是预乘的alpha值。

  • 告诉浏览器画布中的值未预乘

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

最重要的是,默认情况下,加载到WebGL中的图像使用未预乘的Alpha,这意味着您要么需要

  • 将画布设置为不预乘

  • 在着色器中自己进行乘法

    1
    gl_FragColor.rgb *= gl_FragColor.a;
  • 告诉WebGL将纹理加载到WebGL中时预乘纹理

    1
    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

假设您的画布是预乘的,那么您希望混合功能为

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

那是因为您不需要将源乘以alpha,因为它已经被预乘了

这是通过在着色器中乘以alpha与页面混合的示例。紫色条纹是CSS背景。绘制图像两次,一次填充画布,一次绘制在1/2尺寸的顶部。您会看到它们全部正确混合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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);
}
1
2
3
4
5
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;
}
1
2
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js">
<canvas></canvas>

这是将画布设置为不预乘的示例。唯一的区别是我将{premultipliedAlpha: false}传递给了getContext。我删除了gl_FragColor.rgb *= gl_FragColor.a。而且,我将混合功能更改为gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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);
}
1
2
3
4
5
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;
}
1
2
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js">
<canvas></canvas>


基本问题是OpenGL使用后乘Alpha混合通道,这会导致各种有趣的伪像。

您需要:

  • 修改纹理,以使褪色中没有白色内容
    外区域(通常通过向外扩展不透明的彩色像素
    进入透明区域-各种纹理工具将执行此操作
    为了你)
  • 或切换为使用预乘Alpha混合,相应地调整纹理(作为警告可能会引入量化伪像)

大多数脱机工具,例如photoshop,GIMP等,默认情况下都使用预乘Alpha,因此,如果您在照片编辑套件中模拟场景,这是实时渲染的非常常见的"惊奇"。

这里的好博客:

https://developer.nvidia.com/content/alpha-blending-pre-or-not-pre