I'm trying to create 2D texture from an ArrayBuffer
containing an image with 1 component. And I would like to do it without copying it into a 4 components images.
I would to know if it's possible with both webgl
and webgl2
(have more hope with webgl2).
I tried this one
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, pixelData);
I tried with both gl.LUMINANCE
ans gl.LUMINANCE_ALPHA
as the docs says
Each color component is a ...
as opposed of gl.ALPHA
for example where it says :
Discards the red, green and blue components and reads the alpha component
From that it seems clear that gl.ALPHA
will expect a 4 component image while gl.LUMINANCE
and gl.LUMINANCE_ALPHA
will only expect 1 component.
But when I do the following I get an error telling me the buffer is too small. If I pass in a buffer with 4 times the size there is no error so it seems it's also expecting a 4 component image after all.
I also tried with a width and height divided by 4 thinking I could outsmart the texture coord on my shader but came to the conclusion this was probably a bad idea for interpolation, or I would had to do it myself.
So I didn't had much expectation with webgl 1 but I was pretty sure this would be easy with webgl2. But unfortunately the following didn't work :
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, this.image.width, this.image.height, 0, gl.RED, gl.UNSIGNED_BYTE, pixelData);
It's doing the exact same complain about buffer being to small and not throwing any error with a buffer 4 times bigger.
So is it possible with any (or both) version or am I doomed creating a temporary image with 4 components ?
What kind of doc are you reading? None of the quotes you provided are true for ArrayBuffers
and none of them can be found in the WebGL(1) specification.
ALPHA
and LUMINANCE
are both single channel formats only expecting a single value per pixel, the only effective difference between them is through what components you're able to access them in the shader. LUMINANCE_ALHPA
however is a two channel format(first channel is mapped to rgb
and second to a
).
Now to your actual question, both WebGL 1 and 2 allow for single component data submission through texImage2D
, however both are defaulting to an UNPACK_ALIGNMENT
of 4(bytes).
Specifies the alignment requirements for the start of each pixel row in memory. The allowable values are 1 (byte-alignment), 2 (rows aligned to even-numbered bytes), 4 (word-alignment), and 8 (rows start on double-word boundaries).
Setting the UNPACK_ALIGNMENT
to 1(byte) via the pixelStorei
method resolves the issue.
ctx.pixelStorei(ctx.UNPACK_ALIGNMENT,1);
Attention though: this is a global setting(not bound to the active texture) and may lead to a performance penalty for texture data that could be unpacked with a better alignment. So you should determine the optimal unpack alignment on a per texture case(considering the format and data type) and set the alignment accordingly.
var ctx=canvas.getContext('webgl');
// Create alpha texture
var tex = ctx.createTexture();
ctx.bindTexture(ctx.TEXTURE_2D, tex);
ctx.pixelStorei(ctx.UNPACK_ALIGNMENT,1);
ctx.texImage2D(ctx.TEXTURE_2D,0,ctx.ALPHA,2,2,0,ctx.ALPHA,ctx.UNSIGNED_BYTE,new Uint8Array([0,128,128,0]));
ctx.texParameteri(ctx.TEXTURE_2D,ctx.TEXTURE_MIN_FILTER,ctx.NEAREST);
ctx.texParameteri(ctx.TEXTURE_2D,ctx.TEXTURE_MAG_FILTER,ctx.NEAREST);
// Create screenspace shader program
var
vshader=ctx.createShader(ctx.VERTEX_SHADER),
fshader=ctx.createShader(ctx.FRAGMENT_SHADER),
program=ctx.createProgram()
;
ctx.shaderSource(vshader,`
attribute vec2 vPosition;
void main() {
gl_Position=vec4(vPosition,0,1);
}
`);
ctx.compileShader(vshader);
ctx.attachShader(program,vshader);
ctx.shaderSource(fshader,`
precision mediump float;
uniform sampler2D tex;
void main(){
gl_FragColor=vec4(texture2D(tex,gl_FragCoord.xy*.05));
}
`);
ctx.compileShader(fshader);
ctx.attachShader(program,fshader);
ctx.linkProgram(program);
// Create and bind screenspace geometry
ctx.bindBuffer(ctx.ARRAY_BUFFER, ctx.createBuffer());
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array([
-1,1, 1,1, 1,-1, -1,-1,
-1,1, -1,-1, 1,-1, 1,1
]), ctx.STATIC_DRAW);
ctx.enableVertexAttribArray(0);
ctx.vertexAttribPointer(0,2,ctx.FLOAT,false,0,0);
// Render
ctx.useProgram(program);
ctx.drawArrays(ctx.TRIANGLE_FAN,0,4);
canvas {outline:1px solid red;position:absolute;left:50%;transform:translateX(-50%);}
html{background:#aaa;}
<canvas id="canvas"></canvas>