Reputation: 5233
I am trying to output more than one buffer from a shader - the general goal is to use it for GPGPU purposes. I've looked at this answer and got closer to the goal with this:
document.addEventListener("DOMContentLoaded", function() {
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert("need WebGL2");
}
gl.canvas.width = 2;
gl.canvas.height = 2;
const vs = `
#version 300 es
in vec2 position;
void main(void) {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
const fs = `
#version 300 es
precision mediump float;
layout(location = 0) out vec4 outColor0;
layout(location = 1) out vec4 outColor1;
layout(location = 2) out vec4 outColor2;
layout(location = 3) out vec4 outColor3;
layout(location = 4) out vec4 outColor4;
layout(location = 5) out vec4 outColor5;
void main() {
// simplified for question purposes
outColor0 = vec4(1, 0, 0, 1);
outColor1 = vec4(0, 1, 0, 1);
outColor2 = vec4(0, 0, 1, 1);
outColor3 = vec4(1, 1, 0, 1);
outColor4 = vec4(1, 0, 1, 1);
outColor5 = vec4(0, 1, 1, 1);
}
`
const program = twgl.createProgram(gl, [vs, fs]);
const textures = [];
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
for (let i = 0; i < 6; ++i) {
const tex = gl.createTexture();
textures.push(tex);
gl.bindTexture(gl.TEXTURE_2D, tex);
const width = 2;
const height = 2;
const level = 0;
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// attach texture to framebuffer
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, level);
}
gl.viewport(0, 0, 2, 2);
// tell it we want to draw to all 4 attachments
gl.drawBuffers([
gl.COLOR_ATTACHMENT0,
gl.COLOR_ATTACHMENT1,
gl.COLOR_ATTACHMENT2,
gl.COLOR_ATTACHMENT3,
gl.COLOR_ATTACHMENT4,
gl.COLOR_ATTACHMENT5,
]);
// draw a single point
gl.useProgram(program);
{
const offset = 0;
const count = 1
gl.drawArrays(gl.TRIANGLE, 0, 4);
}
for (var l = 0; l < 6; l++) {
var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
console.log(pixels.join(' '));
}
}
main();
})
However, the result is that only one pixel in each buffer gets set, so the output is:
0 0 0 0 255 0 0 255 0 0 0 0 0 0 0 0
0 0 0 0 0 255 0 255 0 0 0 0 0 0 0 0
0 0 0 0 0 0 255 255 0 0 0 0 0 0 0 0
0 0 0 0 255 255 0 255 0 0 0 0 0 0 0 0
0 0 0 0 255 0 255 255 0 0 0 0 0 0 0 0
0 0 0 0 0 255 255 255 0 0 0 0 0 0 0 0
rather than what I was hoping/expecting:
255 0 0 255 255 0 0 255 255 0 0 255 255 0 0 255
etc.
I was expecting that
outColor0 = vec4(1, 0, 0, 1);
is the equivalent to
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
but clearly I am wrong.
So how do I get to the desired outcome - to be able to set each pixel on each of the buffers?
Upvotes: 0
Views: 93
Reputation:
The code does not provide any vertex data even though it's asking it to draw 4 vertices. Further it's passing in gl.TRIANGLE
which doesn't exist. It's gl.TRIANGLES
with an S
at the end. gl.TRIANGLE
will be undefined which gets coerced into 0 which matches gl.POINTS
In the JavaScript console
> const gl = document.createElement('canvas').getContext('webgl2');
< undefined
> gl.TRIANGLE
< undefined
> gl.TRIANGLES
< 4
> gl.POINTS
< 0
To put it another way all the gl.CONSTANTS are just integer values. Instead of
gl.drawArrays(gl.TRIANGLES, offset, count)
you can just do this
gl.drawArrays(4, offset, count)
because gl.TRIANGLES
= 4.
But you you didn't use gl.TRIANGLES
you used gl.TRIANGLE
(no S
) so you effectively did this
gl.drawArrays(undefined, offset, count)
that was interpreted as
gl.drawArrays(0, offset, count)
0 = gl.POINTS so that's the same as
gl.drawArrays(gl.POINTS, offset, count)
The code then draws a single 1 pixel point 4 times at the same location because you called it with a count of 4
gl.drawArrays(gl.POINTS, 0, 4)
Nothing in your vertex shader changes each iteration so every iteration is going to do exactly the same thing. In this case it's going to draw a 1x1 pixel POINT at clip space position 0,0,0,1 which will end up being the bottom left pixel of the 2x2 pixels.
In any case you probably want to provide vertices but as a simple test if I add
gl_PointSize = 2.0;
to the vertex shader and change the draw call to
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
Then it produces the results you expect. It draws a single 2x2 pixel POINT at clip space position 0,0,0,1
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert("need WebGL2");
}
gl.canvas.width = 2;
gl.canvas.height = 2;
const vs = `
#version 300 es
in vec2 position;
void main(void) {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 2.0;
}
`;
const fs = `
#version 300 es
precision mediump float;
layout(location = 0) out vec4 outColor0;
layout(location = 1) out vec4 outColor1;
layout(location = 2) out vec4 outColor2;
layout(location = 3) out vec4 outColor3;
layout(location = 4) out vec4 outColor4;
layout(location = 5) out vec4 outColor5;
void main() {
// simplified for question purposes
outColor0 = vec4(1, 0, 0, 1);
outColor1 = vec4(0, 1, 0, 1);
outColor2 = vec4(0, 0, 1, 1);
outColor3 = vec4(1, 1, 0, 1);
outColor4 = vec4(1, 0, 1, 1);
outColor5 = vec4(0, 1, 1, 1);
}
`
const program = twgl.createProgram(gl, [vs, fs]);
const textures = [];
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
for (let i = 0; i < 6; ++i) {
const tex = gl.createTexture();
textures.push(tex);
gl.bindTexture(gl.TEXTURE_2D, tex);
const width = 2;
const height = 2;
const level = 0;
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// attach texture to framebuffer
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, level);
}
gl.viewport(0, 0, 2, 2);
// tell it we want to draw to all 4 attachments
gl.drawBuffers([
gl.COLOR_ATTACHMENT0,
gl.COLOR_ATTACHMENT1,
gl.COLOR_ATTACHMENT2,
gl.COLOR_ATTACHMENT3,
gl.COLOR_ATTACHMENT4,
gl.COLOR_ATTACHMENT5,
]);
// draw a single point
gl.useProgram(program); {
const offset = 0;
const count = 1
gl.drawArrays(gl.POINTS, 0, 1);
}
for (var l = 0; l < 6; l++) {
var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
console.log(pixels.join(' '));
}
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
You can try using webgl-lint which if I run with your original code will at least complain
Uncaught Error: https://greggman.github.io/webgl-lint/webgl-lint.js:2942: error in drawArrays(/UNKNOWN WebGL ENUM/ undefined, 0, 4): argument 0 is undefined with WebGLProgram("unnamed") as current program with the default vertex array bound
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert("need WebGL2");
}
gl.canvas.width = 2;
gl.canvas.height = 2;
const vs = `
#version 300 es
in vec2 position;
void main(void) {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
const fs = `
#version 300 es
precision mediump float;
layout(location = 0) out vec4 outColor0;
layout(location = 1) out vec4 outColor1;
layout(location = 2) out vec4 outColor2;
layout(location = 3) out vec4 outColor3;
layout(location = 4) out vec4 outColor4;
layout(location = 5) out vec4 outColor5;
void main() {
// simplified for question purposes
outColor0 = vec4(1, 0, 0, 1);
outColor1 = vec4(0, 1, 0, 1);
outColor2 = vec4(0, 0, 1, 1);
outColor3 = vec4(1, 1, 0, 1);
outColor4 = vec4(1, 0, 1, 1);
outColor5 = vec4(0, 1, 1, 1);
}
`
const program = twgl.createProgram(gl, [vs, fs]);
const textures = [];
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
for (let i = 0; i < 6; ++i) {
const tex = gl.createTexture();
textures.push(tex);
gl.bindTexture(gl.TEXTURE_2D, tex);
const width = 2;
const height = 2;
const level = 0;
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// attach texture to framebuffer
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, level);
}
gl.viewport(0, 0, 2, 2);
// tell it we want to draw to all 4 attachments
gl.drawBuffers([
gl.COLOR_ATTACHMENT0,
gl.COLOR_ATTACHMENT1,
gl.COLOR_ATTACHMENT2,
gl.COLOR_ATTACHMENT3,
gl.COLOR_ATTACHMENT4,
gl.COLOR_ATTACHMENT5,
]);
// draw a single point
gl.useProgram(program); {
const offset = 0;
const count = 1
gl.drawArrays(gl.TRIANGLE, 0, 4);
}
for (var l = 0; l < 6; l++) {
var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
console.log(pixels.join(' '));
}
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js" crossorigin="anonymous"></script>
Upvotes: 1