I'm trying to pass some non-texture values to a pixel shader in a texture and i'm running into a weird problem where sampler2d
returns vec4(0.0)
when the texture's alpha value is zero, regardless of the value of the other 3 bytes.
this isn't a premultiplied alpha thing, or a blending thing, it doesn't happen when the alpha's byte value is 1 through 255, just zero.
if you run the code below you'll see a 2x2 texture in the small 2d canvas being rendered into the large 3d canvas. all 4 pixels have r,g,b = 255. and all 4 pixels have different a values. the top-left pixel of the texture has an alpha value of zero.
the pixel shader sets gl_FragColor.a = 1.0
always.
the reason i don't believe this is a premultiplied alpha thing is that if it were, then surely all 3 pixels would be different shades of grey?
can anyone tell me why this happens?
const cvs = document.getElementById("cvs"),
{
width: W,
height: H
} = cvs.getBoundingClientRect();
cvs.width = W;
cvs.height = H;
const gl = cvs.getContext("experimental-webgl", {
premultipliedAlpha: false
}),
VERTEX_SHADER = `attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}`,
FRAGMENT_SHADER = `precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
gl_FragColor.rgb = texture2D(u_Sampler, v_TexCoord).rgb;
gl_FragColor.a = 1.0;
}`,
vshader = gl.createShader(gl.VERTEX_SHADER),
fshader = gl.createShader(gl.FRAGMENT_SHADER),
program = gl.createProgram();
gl.shaderSource(vshader, VERTEX_SHADER);
gl.shaderSource(fshader, FRAGMENT_SHADER);
gl.compileShader(vshader);
gl.compileShader(fshader);
gl.attachShader(program, vshader);
gl.attachShader(program, fshader);
gl.linkProgram(program);
gl.useProgram(program);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
const farr = new Float32Array([-1, 1, 0, 1, -1, -1, 0, 0,
1, 1, 1, 1,
1, -1, 1, 0
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, farr, gl.STATIC_DRAW);
const a_Position = gl.getAttribLocation(program, "a_Position"),
a_TexCoord = gl.getAttribLocation(program, "a_TexCoord"),
fsize = farr.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 4 * fsize, 0);
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 4 * fsize, 2 * fsize);
gl.enableVertexAttribArray(a_Position);
gl.enableVertexAttribArray(a_TexCoord);
var image = document.getElementById("img"),
context = image.getContext("2d"),
imageData = context.getImageData(0, 0, 2, 2),
pixels = imageData.data;
for (var i = 0; i < pixels.length; i++) {
pixels[i] = 255;
}
pixels[0 * 4 + 3] = 0;
pixels[1 * 4 + 3] = 1;
pixels[2 * 4 + 3] = 128;
context.putImageData(imageData, 0, 0, 0, 0, 2, 2);
const texture = gl.createTexture(),
u_Sampler = gl.getUniformLocation(program, "u_Sampler");
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform1i(u_Sampler, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
html,
body,
canvas {
padding: 0;
margin: 0;
}
canvas {
border: 1px solid red;
display: block;
margin: 5px;
}
body {
background: black;
display: block;
position: absolute;
width: 100%;
height: 100%;
}
#cvs {
width: 100px;
height: 100px;
}
<canvas id="cvs"></canvas>
<canvas id="img" width="2" height="2"></canvas>
What @pleup said. Canvas 2d values are always written into the canvas premultiplied. That means the moment you called
putImageData
your data was multiplied by alpha and the data was lost.Using 255 for color partly hides the issue since what's in the 2d canvas after you call
putImageData
isUnpremultiplying (when uploading to WebGL) you get 255s back for all values except 0
If the color value was say 20 and different alphas
And then unpremultiplying gets
Just pointing out how lossy it is.
If you actually want to manually put data in a texture in WebGL you should just use a typedArray
If you want to read that data instead of going through a 2d canvas just call
gl.readPixels