WebGL:如何正确混合 alpha 通道 png
WebGL: How to correctly blend alpha channel png
我是 OpenGL 的新手,甚至是 WebGL 的新手。我正在尝试绘制带有 alpha 通道的纹理四边形。但是我就是无法正确混合。
这就是我要找的结果
这就是 WebGL 结果的样子
如您所见,骰子边缘有一种白色轮廓,而在原始图像中则没有。
这就是我在 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;
}
知道为什么会这样吗?顺便说一句,我不是在创建 mipmap。
这已在 SO 的其他地方得到回答,但是...
WebGL 画布默认需要预乘 alpha。 WebGL canvases 合成在网页上(混合到页面上)。所以...
您是否希望您的 WebGL 图像与网页混合?
如果否,您不想与网页混合,请执行以下操作之一
关闭 canvas
中的 alpha
var gl = someCanvas.getContext("webgl", { alpha: false });
确保您的 alpha 保持在 1.0
最简单的方法是在使用
渲染后清除它
gl.colorMask(false, false, false, true);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
如果是,您确实希望与网页融合,然后执行以下操作之一
确保您写入 canvas 的值是预乘的 alpha 值。
告诉浏览器canvas中的值没有预乘
var gl = someCanvas.getContext("webgl", {premultipliedAlpha: false});
除此之外,默认情况下,加载到 WebGL 中的图像使用 未预乘的 alpha 这意味着您要么需要
设置你的canvas不预乘
自己在着色器中进行乘法运算
gl_FragColor.rgb *= gl_FragColor.a;
告诉 WebGL 在加载到 WebGL 时预乘纹理
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
假设你的 canvas 是预乘的,你希望你的混合函数是
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
那是因为您不需要将源乘以 alpha,因为它已经 premultiplied
下面是一个通过在着色器中乘以 alpha 与页面混合的示例。紫色条纹是 CSS 背景。图像被绘制两次,一次填充 canvas,一次在 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 设置为未预乘的示例。唯一的区别是我将 {premultipliedAlpha: false}
传递给了 getContext
。我删除了 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>
我是 OpenGL 的新手,甚至是 WebGL 的新手。我正在尝试绘制带有 alpha 通道的纹理四边形。但是我就是无法正确混合。
这就是我要找的结果
这就是 WebGL 结果的样子
如您所见,骰子边缘有一种白色轮廓,而在原始图像中则没有。
这就是我在 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;
}
知道为什么会这样吗?顺便说一句,我不是在创建 mipmap。
这已在 SO 的其他地方得到回答,但是...
WebGL 画布默认需要预乘 alpha。 WebGL canvases 合成在网页上(混合到页面上)。所以...
您是否希望您的 WebGL 图像与网页混合?
如果否,您不想与网页混合,请执行以下操作之一
关闭 canvas
中的 alphavar gl = someCanvas.getContext("webgl", { alpha: false });
确保您的 alpha 保持在 1.0
最简单的方法是在使用
渲染后清除它gl.colorMask(false, false, false, true); gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT);
如果是,您确实希望与网页融合,然后执行以下操作之一
确保您写入 canvas 的值是预乘的 alpha 值。
告诉浏览器canvas中的值没有预乘
var gl = someCanvas.getContext("webgl", {premultipliedAlpha: false});
除此之外,默认情况下,加载到 WebGL 中的图像使用 未预乘的 alpha 这意味着您要么需要
设置你的canvas不预乘
自己在着色器中进行乘法运算
gl_FragColor.rgb *= gl_FragColor.a;
告诉 WebGL 在加载到 WebGL 时预乘纹理
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
假设你的 canvas 是预乘的,你希望你的混合函数是
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
那是因为您不需要将源乘以 alpha,因为它已经 premultiplied
下面是一个通过在着色器中乘以 alpha 与页面混合的示例。紫色条纹是 CSS 背景。图像被绘制两次,一次填充 canvas,一次在 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 设置为未预乘的示例。唯一的区别是我将 {premultipliedAlpha: false}
传递给了 getContext
。我删除了 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>