twgl.js: How to use a packed vertex array via BufferInfo - javascript

I am using twgl.js (http://twgljs.org/) in a webgl project, it's very nice to use, but I want to optimize my code to use packed vertex arrays. However I can't seem to figure out how that works in twgl, I can see there are some options for the stride and offset in it, but no further information how exactly they will behave. I've read the code, but from that I get the feeling that it will not allow me to use a single call to createBufferInfoFromArrays, but require alot more by hand plumbing.
Is it possible to setup packed arrays with twgl.js? How?

The short answer is "Yes, it will require a lot more plumbing" or rather it's up to you to pack the data. You can easily make a your own BufferInfo
var packedBuffer = twgl.createBufferFromTypedArray(
gl, typedArrayWithPackedData);
var indexBuffer = twgl.createBufferFromTypedArray(
gl, typedArrayWithIndices, gl.ELEMENT_ARRAY_BUFFER);
var stride = 36; // position + normal + texcoord + rgba8
var handMadeBufferInfo = {
numElements: 123, // or whatever
indices: indexBuffer, // remove if no indices
elementType: gl.UNSIGNED_SHORT, // remove if no indices
attribs: {
position: { buffer: packedBuffer, numComponents: 3, type: gl.FLOAT, stride: stride, offset: 0, },
normal: { buffer: packedBuffer, numComponents: 3, type: gl.FLOAT, stride: stride, offset: 12, },
texcoord: { buffer: packedBuffer, numComponents: 2, type: gl.FLOAT, stride: stride, offset: 24, },
vcolor: { buffer: packedBuffer, numComponents: 4, type: gl.UNSIGNED_BYTE, stride: stride, offset: 32, normalize: true },
},
};
var gl = document.getElementById("c").getContext("webgl");
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
// there's no easy way to interleave the data unless we use pure binary so
// lets do it manually.
var position = [
-1, -1, 0,
1, -1, 0,
-1, 1, 0,
1, 1, 0,
];
var texcoord = [
0, 0,
1, 0,
0, 1,
1, 1,
]
var color = [
255, 0, 0, 255,
0, 255, 0, 255,
0, 0, 255, 255,
255, 255, 255, 255,
];
var stride = 24; // position + texcoord + rgba8
var typedArrayWithPackedData = new ArrayBuffer(stride * 4);
var floatView = new Float32Array(typedArrayWithPackedData);
var uint8View = new Uint8Array(typedArrayWithPackedData);
var floatAdd = 1;
var uint8Add = stride - 4;
var floatOffset = 0;
var uint8Offset = stride - 4;
var positionOffset = 0;
var texcoordOffset = 0;
var colorOffset = 0;
for (var ii = 0; ii < 4; ++ii) {
floatView[floatOffset++] = position[positionOffset++];
floatView[floatOffset++] = position[positionOffset++];
floatView[floatOffset++] = position[positionOffset++];
floatView[floatOffset++] = texcoord[texcoordOffset++];
floatView[floatOffset++] = texcoord[texcoordOffset++];
floatOffset += floatAdd;
uint8View[uint8Offset++] = color[colorOffset++];
uint8View[uint8Offset++] = color[colorOffset++];
uint8View[uint8Offset++] = color[colorOffset++];
uint8View[uint8Offset++] = color[colorOffset++];
uint8Offset += uint8Add;
}
var packedBuffer = twgl.createBufferFromTypedArray(
gl, typedArrayWithPackedData);
var typedArrayWithIndices = new Uint16Array([0, 1, 2, 2, 1, 3]);
var indexBuffer = twgl.createBufferFromTypedArray(
gl, typedArrayWithIndices, gl.ELEMENT_ARRAY_BUFFER);
var handMadeBufferInfo = {
numElements: 6, // or whatever
indices: indexBuffer, // remove if no indices
elementType: gl.UNSIGNED_SHORT, // remove if no indices
attribs: {
position: { buffer: packedBuffer, numComponents: 3, type: gl.FLOAT, stride: stride, offset: 0, },
texcoord: { buffer: packedBuffer, numComponents: 2, type: gl.FLOAT, stride: stride, offset: 12, },
color: { buffer: packedBuffer, numComponents: 4, type: gl.UNSIGNED_BYTE, stride: stride, offset: 20, normalize: true, },
},
};
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// make a texture with a circle in it
var canvas2d = document.createElement("canvas");
canvas2d.width = 64;
canvas2d.height = 64;
var ctx = canvas2d.getContext("2d");
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(32, 32, 30, 0, Math.PI * 2, false);
ctx.fill();
var uniforms = {
u_texture: twgl.createTexture(gl, { src: canvas2d }),
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, handMadeBufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, handMadeBufferInfo);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<script id="vs" type="notjs">
attribute vec4 position;
attribute vec2 texcoord;
attribute vec4 color;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
v_texcoord = texcoord;
v_color = color;
gl_Position = position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
gl_FragColor = texture2D(u_texture, v_texcoord) * v_color;
}
</script>
<canvas id="c"></canvas>
Note: twgl.createBufferFromTypedArray was only recently exposed in version 0.0.37
It's not really clear how it could be any easier. If you want twgl to pack the data for you it's not clear what the point is. Is it really that much faster to render? Maybe twgl should support Vertex Array Objects instead?
If you packed it yourself you'd have to give it all the same information above anyway just to have twgl transform it into a BufferInfo which doesn't seem like it would save you much time.

Related

Texture coordinate inaccuracy

I am getting what looks like a precision problem at 16 bit for texture uv. This might be common knowledge, and texture casting to normalized integer, but I could not find much¹ about that, nor would it make much sense to me (why degrade the coordinates at the last step?).
In real code (which is too long to show), this results in a texture bleed when zooming in too far, way before the expected issues for float32. The following demo has a lot of boilerplate, so here are the parts I think are important:
The test texture is a 512x512 checkerboard of 8x8 tiles, green and blue.
A square is rendered, with uv set to an 8x8 blue tile above the middle, sitting so it covers half the canvas.
const offset = -1 / (1 << bitsRequired); slightly shifts these uv on the y-axis.
const zoom = 2 ** (bitsRequired - 14); zooms in on the edge.
By construction, the uv offset is then set to a step that requires more than 16 bit. The zoom is chosen so exactly one green pixel should be rendered, without precision problems. However, at exactly 17 bit required precision, the pixel vanishes (at least for me, but it could be dependent on hardware/driver/whatever - if someone cannot reproduce, and it hasn't been mentioned already, please do).
At first, I thought I made a mistake. However, adding the rounding manually before the texture call, uncommenting the following, makes the green pixel line reappear:
vec2 texCoordRounded = vec2(
(floor(vTextureCoord.x * 512.) + 0.5) / 512.,
(floor(vTextureCoord.y * 512.) + 0.5) / 512.
);
Now I am confused. Did I miss something, or make a mistake? Does texture cast to some normalized integer? Why does this look like my precision runs out at 16 bit?
The following is copy&paste of the same code, with changed parameters (too cumbersome to parameterize this demo code):
With an offset requiring less than 16 bit, the green pixel line appears:
const assert = (condition, message) => {
if (!condition) throw new Error(message);
};
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl2');
assert(gl !== null, `WebGL2 was unexpectedly not supported.`);
const testImage = new Uint8Array(Array.from(
{ length: 512 * 512 },
(_, i) => (i % 16 > 7) !== (Math.floor(i / 512) % 16 > 7)
? [0, 0xff, 0, 0xff]
: [0, 0, 0xff, 0xff],
).flat());
const bitsRequired = 16;
const offset = -1 / (1 << bitsRequired);
const vData = new Float32Array([
-1, 0, 0, 0.5 + offset, 1, 0, 0.015625, 0.5 + offset,
1, 2, 0.015625, 0.515625 + offset, -1, 2, 0, 0.515625 + offset,
]);
const zoom = 2 ** (bitsRequired - 14);
const projection = new Float32Array([
zoom, 0, 0, 0,
0, zoom, 0, 0,
0, 0, -zoom, 0,
0, 0, 0, 1,
]);
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
const texture = gl.createTexture();
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, 512, 512, 0, gl.RGBA, gl.UNSIGNED_BYTE, testImage);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);
const vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 16, 8);
gl.bufferData(gl.ARRAY_BUFFER, vData, gl.STATIC_DRAW);
const iBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
const vertexShaderSrc = `#version 300 es
precision highp float;
uniform mat4 projection;
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTextureCoord;
out vec2 vTextureCoord;
void main(void) {
gl_Position = projection * vec4(aPosition, 0.0, 1.0);
vTextureCoord = aTextureCoord;
}`;
const fragmentShaderSrc = `#version 300 es
precision highp float;
uniform sampler2D sampler;
in vec2 vTextureCoord;
out vec4 fColor;
void main(void){
// vec2 texCoordRounded = vec2(
// (floor(vTextureCoord.x * 512.) + 0.5) / 512.,
// (floor(vTextureCoord.y * 512.) + 0.5) / 512.
// );
// vec4 color = texture(sampler, texCoordRounded);
vec4 color = texture(sampler, vTextureCoord);
fColor = color;
}`;
const program = gl.createProgram();
assert(program !== null, `Program was unexpectedly \`null\`.`);
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
assert(vertexShader !== null, `Vertex-shader was unexpectedly \`null\`.`);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);
assert(gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS), `Vertex-shader failed to compile:\n${gl.getShaderInfoLog(vertexShader)}`);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
assert(fragmentShader !== null, `Vertex-shader was unexpectedly \`null\`.`);
gl.shaderSource(fragmentShader, fragmentShaderSrc);
gl.compileShader(fragmentShader);
assert(gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS), `Fragment-shader failed to compile:\n${gl.getShaderInfoLog(fragmentShader)}`);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
assert(gl.getProgramParameter(program, gl.LINK_STATUS), `Program linking failed:\n${gl.getProgramInfoLog(program)}`);
gl.useProgram(program);
const uniformLocationSampler = gl.getUniformLocation(program, 'sampler');
gl.uniform1i(uniformLocationSampler, 0);
const uniformLocationProjection = gl.getUniformLocation(program, 'projection');
gl.uniformMatrix4fv(uniformLocationProjection, false, projection);
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
<canvas id='canvas' width='256' height='256'></canvas>
Changing bitsRequired to 17 (only change) causes the problem, the green pixel line disappears:
const assert = (condition, message) => {
if (!condition) throw new Error(message);
};
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl2');
assert(gl !== null, `WebGL2 was unexpectedly not supported.`);
const testImage = new Uint8Array(Array.from(
{ length: 512 * 512 },
(_, i) => (i % 16 > 7) !== (Math.floor(i / 512) % 16 > 7)
? [0, 0xff, 0, 0xff]
: [0, 0, 0xff, 0xff],
).flat());
const bitsRequired = 17;
const offset = -1 / (1 << bitsRequired);
const vData = new Float32Array([
-1, 0, 0, 0.5 + offset, 1, 0, 0.015625, 0.5 + offset,
1, 2, 0.015625, 0.515625 + offset, -1, 2, 0, 0.515625 + offset,
]);
const zoom = 2 ** (bitsRequired - 14);
const projection = new Float32Array([
zoom, 0, 0, 0,
0, zoom, 0, 0,
0, 0, -zoom, 0,
0, 0, 0, 1,
]);
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
const texture = gl.createTexture();
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, 512, 512, 0, gl.RGBA, gl.UNSIGNED_BYTE, testImage);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);
const vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 16, 8);
gl.bufferData(gl.ARRAY_BUFFER, vData, gl.STATIC_DRAW);
const iBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
const vertexShaderSrc = `#version 300 es
precision highp float;
uniform mat4 projection;
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTextureCoord;
out vec2 vTextureCoord;
void main(void) {
gl_Position = projection * vec4(aPosition, 0.0, 1.0);
vTextureCoord = aTextureCoord;
}`;
const fragmentShaderSrc = `#version 300 es
precision highp float;
uniform sampler2D sampler;
in vec2 vTextureCoord;
out vec4 fColor;
void main(void){
// vec2 texCoordRounded = vec2(
// (floor(vTextureCoord.x * 512.) + 0.5) / 512.,
// (floor(vTextureCoord.y * 512.) + 0.5) / 512.
// );
// vec4 color = texture(sampler, texCoordRounded);
vec4 color = texture(sampler, vTextureCoord);
fColor = color;
}`;
const program = gl.createProgram();
assert(program !== null, `Program was unexpectedly \`null\`.`);
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
assert(vertexShader !== null, `Vertex-shader was unexpectedly \`null\`.`);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);
assert(gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS), `Vertex-shader failed to compile:\n${gl.getShaderInfoLog(vertexShader)}`);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
assert(fragmentShader !== null, `Vertex-shader was unexpectedly \`null\`.`);
gl.shaderSource(fragmentShader, fragmentShaderSrc);
gl.compileShader(fragmentShader);
assert(gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS), `Fragment-shader failed to compile:\n${gl.getShaderInfoLog(fragmentShader)}`);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
assert(gl.getProgramParameter(program, gl.LINK_STATUS), `Program linking failed:\n${gl.getProgramInfoLog(program)}`);
gl.useProgram(program);
const uniformLocationSampler = gl.getUniformLocation(program, 'sampler');
gl.uniform1i(uniformLocationSampler, 0);
const uniformLocationProjection = gl.getUniformLocation(program, 'projection');
gl.uniformMatrix4fv(uniformLocationProjection, false, projection);
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
<canvas id='canvas' width='256' height='256'></canvas>
Activating the previously commented manual rounding before the texture call (only change) on what's still float32 causes the green pixel line to re-appear, fixing the problem, but why?
const assert = (condition, message) => {
if (!condition) throw new Error(message);
};
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl2');
assert(gl !== null, `WebGL2 was unexpectedly not supported.`);
const testImage = new Uint8Array(Array.from(
{ length: 512 * 512 },
(_, i) => (i % 16 > 7) !== (Math.floor(i / 512) % 16 > 7)
? [0, 0xff, 0, 0xff]
: [0, 0, 0xff, 0xff],
).flat());
const bitsRequired = 17;
const offset = -1 / (1 << bitsRequired);
const vData = new Float32Array([
-1, 0, 0, 0.5 + offset, 1, 0, 0.015625, 0.5 + offset,
1, 2, 0.015625, 0.515625 + offset, -1, 2, 0, 0.515625 + offset,
]);
const zoom = 2 ** (bitsRequired - 14);
const projection = new Float32Array([
zoom, 0, 0, 0,
0, zoom, 0, 0,
0, 0, -zoom, 0,
0, 0, 0, 1,
]);
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
const texture = gl.createTexture();
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, 512, 512, 0, gl.RGBA, gl.UNSIGNED_BYTE, testImage);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);
const vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 16, 8);
gl.bufferData(gl.ARRAY_BUFFER, vData, gl.STATIC_DRAW);
const iBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
const vertexShaderSrc = `#version 300 es
precision highp float;
uniform mat4 projection;
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTextureCoord;
out vec2 vTextureCoord;
void main(void) {
gl_Position = projection * vec4(aPosition, 0.0, 1.0);
vTextureCoord = aTextureCoord;
}`;
const fragmentShaderSrc = `#version 300 es
precision highp float;
uniform sampler2D sampler;
in vec2 vTextureCoord;
out vec4 fColor;
void main(void){
vec2 texCoordRounded = vec2(
(floor(vTextureCoord.x * 512.) + 0.5) / 512.,
(floor(vTextureCoord.y * 512.) + 0.5) / 512.
);
vec4 color = texture(sampler, texCoordRounded);
// vec4 color = texture(sampler, vTextureCoord);
fColor = color;
}`;
const program = gl.createProgram();
assert(program !== null, `Program was unexpectedly \`null\`.`);
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
assert(vertexShader !== null, `Vertex-shader was unexpectedly \`null\`.`);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);
assert(gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS), `Vertex-shader failed to compile:\n${gl.getShaderInfoLog(vertexShader)}`);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
assert(fragmentShader !== null, `Vertex-shader was unexpectedly \`null\`.`);
gl.shaderSource(fragmentShader, fragmentShaderSrc);
gl.compileShader(fragmentShader);
assert(gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS), `Fragment-shader failed to compile:\n${gl.getShaderInfoLog(fragmentShader)}`);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
assert(gl.getProgramParameter(program, gl.LINK_STATUS), `Program linking failed:\n${gl.getProgramInfoLog(program)}`);
gl.useProgram(program);
const uniformLocationSampler = gl.getUniformLocation(program, 'sampler');
gl.uniform1i(uniformLocationSampler, 0);
const uniformLocationProjection = gl.getUniformLocation(program, 'projection');
gl.uniformMatrix4fv(uniformLocationProjection, false, projection);
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
<canvas id='canvas' width='256' height='256'></canvas>
[1]: Edit: I found a comment in an otherwise unrelated question which supports my guess of sampling using normalized integers, but still no official documentation:
Many modern GPUs use some fixed-point representation for the texcoords, so there is only a limited set of positions which can be sampled between two texels (typically 256).
Note that the comment is from 2014 (9 years old), and my guess would be the default is 16 bit normalized integer instead of 8 bit by now.
Edit2: I now also found this in the directx d3d spec (thanks to a blog post)
Texture coordinates for sampling operations are snapped to fixed point (after being scaled by texture size), to uniformly distribute precision across texture space, in choosing filter tap locations/weights. Weight values are converted back to floating point before actual filtering arithmetic is performed.
I still can't find any authoritative documentation for opengl/webgl though. It's getting more and more clear, that what's happening is exactly my guess, but where is the documentation, and is "uniform distribution" enough reason to cut off 8 bit of precision?
why degrade the coordinates at the last step?
... because GPUs can do a few billion texture filtering operations per second, and you really don't want to waste power and silicon area doing calculations at fp32 precision if all practical use cases need 8-bit fixed point.
Note this is 8-bits of sub-texel accuracy (i.e. granularity for GL_LINEAR filtering between two adjacent texels). Selecting texels is done at whatever higher precision is needed (most modern GPUs can uniquely address 16K textures, with 8-bits of subtexel accuracy).

WebGL gl.triangle make square

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// position
-0.9, 0.9,
0.9, 0.9,
-0.9, -0.9,
0.9, 0.9,
// color
1, 0, 0, 1,
0, 1, 0, 1,
1, 0, 1, 1,
1, 0, 0, 1
]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.enableVertexAttribArray(colorLocation);
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
var size = 4;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = Float32Array.BYTES_PER_ELEMENT * 8;
gl.vertexAttribPointer(colorLocation, size, type, normalize, stride, offset);
This is some portion of the code ( the code is too long to fit ) so I will put these part where I'm curious about why it didnt draw a square but still a triangle? I know that I used gl.Triangle but I want to try using gl.Triangle to draw a square which I'm not sure which part of this is wrong and I have searched about it but no one do the same thing as I do ( the one where I put position and vertices in the same array )
There's also this part where count is 3 which I'm not sure what it does ( this code is given by my professor to let me make it a square and colored by changing a few setting so I do not know how to code opengl yet )
// Draw the geometry.
var offset = 0;
var count = 3;
gl.drawArrays(gl.TRIANGLES, offset, count);
below is the full code
<!DOCTYPE html>
<html>
<head>
<title>CS299 - Assignment 1.1</title>
<link type="text/css" href="https://webgl2fundamentals.org/webgl/resources/webgl-tutorials.css" rel="stylesheet" />
<!-- css override -->
<style type="text/css">
body { background-color: #CCCCCC; }
#group {background-color: #E8F49F;}
canvas { background-color: #4DC72F; width: 300px; height: 300px; border: 0px; }
.gman-widget-slider {min-width: 200px;}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<!-- util functions -->
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script>
<!-- main WebGL2 code -->
<script>
"use strict";
var vs = `#version 300 es
// an attribute is an input (in) to a vertex shader.
// It will receive data from a buffer
in vec2 a_position;
in vec4 a_color;
// color output from vertex shader to fragment shader
out vec4 v_color;
// all shaders have a main function.
void main() {
// default position output variable
// convert vec2 to vec4
gl_Position = vec4(a_position, 0, 1);
// color passthrough
v_color = a_color;
}
`;
var fs = `#version 300 es
precision highp float;
// color passthrough
in vec4 v_color;
// outout color
out vec4 outColor;
void main() {
outColor = v_color;
}
`;
function main() {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl2");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromSources(gl, [vs, fs]);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var colorLocation = gl.getAttribLocation(program, "a_color");
// Create set of attributes
var vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// Create a buffer (formerly called "vertex buffer object", now just "buffer").
var vbo = gl.createBuffer();
// Set Geometry.
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
// [40%] Modify the code to draw a square instead of a triangle.
// Assign C,M,Y, and K colors to the 4 vertices of the square.
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// position
-0.9, 0.9,
0.9, 0.9,
-0.9, -0.9,
0.9, 0.9,
// color
1, 0, 0, 1,
0, 1, 0, 1,
1, 0, 1, 1,
1, 0, 0, 1
]), gl.STATIC_DRAW);
// tell the position attribute how to pull data out of the current ARRAY_BUFFER
gl.enableVertexAttribArray(positionLocation);
gl.enableVertexAttribArray(colorLocation);
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
var size = 4;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = Float32Array.BYTES_PER_ELEMENT * 8; // must be in bytes
gl.vertexAttribPointer(colorLocation, size, type, normalize, stride, offset);
// Draw the scene.
function drawScene() {
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas
gl.clearColor(0.15, 0.15, 0.15, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Bind the attribute/buffer set we want.
gl.bindVertexArray(vao);
// Draw the geometry.
var offset = 0;
var count = 3;
// [1.5 points] Use gl.TRIANGLE_STRIP instead of gl.TRIANGLES
gl.drawArrays(gl.TRIANGLES, offset, count);
}
drawScene();
}
main();
</script>
<p id="group">Group: 4DC72F</p>
</html>
I would like some hint instead of answer if that is ok because I was trying to learn but I cant find this method anywhere on the internet
In your vertex specification, the coordinate (0.9, 0.9) is duplicated, however, that's not the only problem.
See Triangle primitives. The primitive type gl.TRIANGLES renders, as the name suggests, triangles. For 2 triangles you need 6 verticals (2*3). Each triangle consists of 3 vertices, and the triangles are completely independent and have no common vertices. e.g.:
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// position
// triangle 1
-0.9, 0.9,
0.9, 0.9,
-0.9, -0.9,
// triangle 2
0.9, 0.9,
0.9, -0.9,
-0.9, -0.9,
// color
// [...]
]), gl.STATIC_DRAW);
However you can use the primitive type gl.TRIANGLE_STRIP to draw a single quad:
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// position
-0.9, 0.9,
0.9, 0.9,
-0.9, -0.9,
0.9, -0.9,
// color
1, 0, 0, 1,
0, 1, 0, 1,
1, 0, 1, 1,
1, 0, 0, 1
]), gl.STATIC_DRAW);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

How to use gl.readPixels to read drawn gl.POINTS with pointsize > 1?

My goal is to read pixels of a large point at the canvas. Currently I can't even get the pixels drawn at pointSize = 1. Here's a link: https://codepen.io/issabln/pen/LYEGWyO
the code snippet:
function drawOneBlackPixel( gl, x, y ) {
// Fills the buffer with a single point?
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([
x, y]), gl.STATIC_DRAW );
// Draw one point.
gl.drawArrays( gl.POINTS, 0, 1 );
}
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// These tests are supposed to be x,y coordinates from top left.
drawOneBlackPixel( gl, 1, 0 );
const pix = new Uint8Array(4);
gl.readPixels(1, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pix);
console.log(pix)
Any idea?
Flipping the Y coordinate does the trick(note the -1 otherwise you'd read outside the viewport)
gl.readPixels(1, gl.drawingBufferHeight-1, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pix);
Let's try it
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 1.0;
}
`;
const fs = `
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`;
const prg = twgl.createProgram(gl, [vs, fs]);
gl.useProgram(prg);
const posLoc = gl.getAttribLocation(prg, 'position');
const colorLoc = gl.getUniformLocation(prg, 'color');
function drawPixel(gl, px, py, color) {
// compute center of pixel in clip space
const clipX = (px + 0.5) / gl.canvas.width * 2 - 1;
const clipY = (py + 0.5) / gl.canvas.height * 2 - 1;
gl.vertexAttrib2f(posLoc, clipX, clipY);
gl.uniform4fv(colorLoc, color);
gl.drawArrays(gl.POINTS, 0, 1);
}
function checkPixel(gl, px, py, expected, msg) {
const pixel = new Uint8Array(4);
gl.readPixels(px, py, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
const actual = Array.from(pixel).map(v => v / 255);
let same = true;
for (let i = 0; same && i < 4; ++i) {
same = actual[i] === expected[i];
}
if (same) {
console.log(`pass: ${px},${py} was ${ actual.join(',')}`);
} else {
console.error(`fail: ${px},${py} was ${actual.join(',')} expected ${expected.join(',')}`);
}
}
drawPixel(gl, 0, 0, [1, 0, 0, 1]);
drawPixel(gl, 299, 0, [0, 1, 0, 1]);
drawPixel(gl, 0, 149, [0, 0, 1, 1]);
drawPixel(gl, 299, 149, [0, 1, 1, 1]);
checkPixel(gl, 0, 0, [1, 0, 0, 1]);
checkPixel(gl, 299, 0, [0, 1, 0, 1]);
checkPixel(gl, 0, 149, [0, 0, 1, 1]);
checkPixel(gl, 299, 149, [0, 1, 1, 1]);
body { background: #444; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>

WebGL color buffer issue: [GL_INVALID_OPERATION : glDrawArrays]

I am trying to get starting learning WebGL; I got my proof of concept working without color, but as soon as I tried added color by adding
colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array(
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
), gl.STATIC_DRAW);
ColorAttribute = gl.getAttribLocation(program, 'color');
gl.enableVertexAttribArray(ColorAttribute);
gl.vertexAttribPointer(ColorAttribute, 4, gl.FLOAT, false, 0, 0);
where
gl is the WebGLRenderingContext,
program is the successfully compiled program with a vertex and a fragment shader attached
colorBuffer, ColorAttribute are null variables
in the main code, and changing
gl_FragColor = vec4(0.2, 0.4, 0.6, 1);
to
gl_FragColor = vcolor;
in the fragment shader source(commenting the shader body does not make the error go away); I got the following error:
[.Offscreen-For-WebGL-0000000005BB7940]GL ERROR :GL_INVALID_OPERATION : glDrawArrays: attempt to access out of range vertices in attribute 1
Which is strange because my color buffer has 3 colors in it, one for each vertex of the triangle:
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array(
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
), gl.STATIC_DRAW);
and my vertex buffer has 3 vertices in it:
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0, 0,
0, 1, 0,
1, 1, 0
]), gl.STATIC_DRAW);
I made sure that i set the item size of color buffer to 4, and item size of vertex buffer to 3 in my calls to vertexAttribPointer, so I am not sure what could be out of range.
Below is a code that works, with the color changes commented out, followed by one that doesn't work with color changes in. Both samples work by pasting into browser developer console on any window, but the screenshots were taken in "about:blank".
Both snippets are self contained, but only tested in Chrome.
This is the working version:
(function() {
"use strict";
var hWnd;
var src_vertexShader;
var src_fragmentShader;
var canvas;
var gl;
var program;
var vertexShader;
var fragmentShader;
var vertexBuffer;
var colorBuffer;
var PositionAttribute;
var ColorAttribute;
// | canvas container.
hWnd = document.createElement("div");
hWnd.style.position = "fixed";
hWnd.style.top = "0px";
hWnd.style.left = "0px";
hWnd.style.border = "1px solid #000000";
hWnd.addEventListener("click", function() {
this.outerHTML = '';
});
// | vertex shader source.
src_vertexShader = `
attribute vec3 position;
attribute vec4 color;
varying vec4 vcolor;
void main() {
gl_Position = vec4(position, 1.0);
vcolor = color;
}`;
// | fragment shader source.
src_fragmentShader = `
varying lowp vec4 vcolor;
void main() {
gl_FragColor = vec4(0.2, 0.4, 0.6, 1);
//gl_FragColor = vcolor;
}`;
// | our WebGL canvas.
canvas = document.createElement('canvas');
canvas.width = 320;
canvas.height = 200;
// | our WebGLRenderingContext.
gl = canvas.getContext('webgl', {antialias: false});
// | setting up our program using a Vertex and a Fragment shader.
program = gl.createProgram();
vertexShader = gl.createShader(gl.VERTEX_SHADER);
fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader, src_vertexShader);
gl.shaderSource(fragmentShader, src_fragmentShader);
gl.compileShader(vertexShader);
console.log('Shader compiled successfully: ' + gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS));
console.log('Shader compiler log: ' + gl.getShaderInfoLog(vertexShader));
gl.compileShader(fragmentShader);
console.log('Shader compiled successfully: ' + gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS));
console.log('Shader compiler log: ' + gl.getShaderInfoLog(fragmentShader));
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
console.log(gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS));
// | create and attach a vertex buffer with data for one triangle.
vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0, 0,
0, 1, 0,
1, 1, 0
]), gl.STATIC_DRAW);
PositionAttribute = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(PositionAttribute);
gl.vertexAttribPointer(PositionAttribute, 3, gl.FLOAT, false, 0, 0);
/*
// | create and attach a color buffer with color data for our triangle.
colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array(
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
), gl.STATIC_DRAW);
ColorAttribute = gl.getAttribLocation(program, 'color');
gl.enableVertexAttribArray(ColorAttribute);
gl.vertexAttribPointer(ColorAttribute, 4, gl.FLOAT, false, 0, 0);
*/
// | clear the screen.
gl.clearColor(0.93, 0.93, 0.93, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// | draw the triangle.
gl.drawArrays(gl.TRIANGLES, 0, 3);
hWnd.appendChild(canvas)
document.body.appendChild(hWnd);
})();
This is the version that complains:
(function() {
"use strict";
var hWnd;
var src_vertexShader;
var src_fragmentShader;
var canvas;
var gl;
var program;
var vertexShader;
var fragmentShader;
var vertexBuffer;
var colorBuffer;
var PositionAttribute;
var ColorAttribute;
// | canvas container.
hWnd = document.createElement("div");
hWnd.style.position = "fixed";
hWnd.style.top = "0px";
hWnd.style.left = "0px";
hWnd.style.border = "1px solid #000000";
hWnd.addEventListener("click", function() {
this.outerHTML = '';
});
// | vertex shader source.
src_vertexShader = `
attribute vec3 position;
attribute vec4 color;
varying vec4 vcolor;
void main() {
gl_Position = vec4(position, 1.0);
vcolor = color;
}`;
// | fragment shader source.
src_fragmentShader = `
varying lowp vec4 vcolor;
void main() {
gl_FragColor = vcolor;
}`;
// | our WebGL canvas.
canvas = document.createElement('canvas');
canvas.width = 320;
canvas.height = 200;
// | our WebGLRenderingContext.
gl = canvas.getContext('webgl', {antialias: false});
// | setting up our program using a Vertex and a Fragment shader.
program = gl.createProgram();
vertexShader = gl.createShader(gl.VERTEX_SHADER);
fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader, src_vertexShader);
gl.shaderSource(fragmentShader, src_fragmentShader);
gl.compileShader(vertexShader);
console.log('Shader compiled successfully: ' + gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS));
console.log('Shader compiler log: ' + gl.getShaderInfoLog(vertexShader));
gl.compileShader(fragmentShader);
console.log('Shader compiled successfully: ' + gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS));
console.log('Shader compiler log: ' + gl.getShaderInfoLog(fragmentShader));
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
console.log(gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS));
// | create and attach a vertex buffer with data for one triangle.
vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0, 0,
0, 1, 0,
1, 1, 0
]), gl.STATIC_DRAW);
PositionAttribute = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(PositionAttribute);
gl.vertexAttribPointer(PositionAttribute, 3, gl.FLOAT, false, 0, 0);
// | create and attach a color buffer with color data for our triangle.
colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array(
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
), gl.STATIC_DRAW);
ColorAttribute = gl.getAttribLocation(program, 'color');
gl.enableVertexAttribArray(ColorAttribute);
gl.vertexAttribPointer(ColorAttribute, 4, gl.FLOAT, false, 0, 0);
// | clear the screen.
gl.clearColor(0.93, 0.93, 0.93, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// | draw the triangle.
gl.drawArrays(gl.TRIANGLES, 0, 3);
hWnd.appendChild(canvas)
document.body.appendChild(hWnd);
})();
Thanks ahead of time.
The issue is the code is missing square brackets when defining the colors
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array(
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
), gl.STATIC_DRAW);
vs this
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array([
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
]), gl.STATIC_DRAW);
(function() {
"use strict";
var hWnd;
var src_vertexShader;
var src_fragmentShader;
var canvas;
var gl;
var program;
var vertexShader;
var fragmentShader;
var vertexBuffer;
var colorBuffer;
var PositionAttribute;
var ColorAttribute;
// | canvas container.
hWnd = document.createElement("div");
hWnd.style.position = "fixed";
hWnd.style.top = "0px";
hWnd.style.left = "0px";
hWnd.style.border = "1px solid #000000";
hWnd.addEventListener("click", function() {
this.outerHTML = '';
});
// | vertex shader source.
src_vertexShader = `
attribute vec3 position;
attribute vec4 color;
varying vec4 vcolor;
void main() {
gl_Position = vec4(position, 1.0);
vcolor = color;
}`;
// | fragment shader source.
src_fragmentShader = `
varying lowp vec4 vcolor;
void main() {
gl_FragColor = vcolor;
}`;
// | our WebGL canvas.
canvas = document.createElement('canvas');
canvas.width = 320;
canvas.height = 200;
// | our WebGLRenderingContext.
gl = canvas.getContext('webgl', {antialias: false});
// | setting up our program using a Vertex and a Fragment shader.
program = gl.createProgram();
vertexShader = gl.createShader(gl.VERTEX_SHADER);
fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader, src_vertexShader);
gl.shaderSource(fragmentShader, src_fragmentShader);
gl.compileShader(vertexShader);
console.log('Shader compiled successfully: ' + gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS));
console.log('Shader compiler log: ' + gl.getShaderInfoLog(vertexShader));
gl.compileShader(fragmentShader);
console.log('Shader compiled successfully: ' + gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS));
console.log('Shader compiler log: ' + gl.getShaderInfoLog(fragmentShader));
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
console.log(gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS));
// | create and attach a vertex buffer with data for one triangle.
vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0, 0,
0, 1, 0,
1, 1, 0
]), gl.STATIC_DRAW);
PositionAttribute = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(PositionAttribute);
gl.vertexAttribPointer(PositionAttribute, 3, gl.FLOAT, false, 0, 0);
// | create and attach a color buffer with color data for our triangle.
colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array([
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
]), gl.STATIC_DRAW);
ColorAttribute = gl.getAttribLocation(program, 'color');
gl.enableVertexAttribArray(ColorAttribute);
gl.vertexAttribPointer(ColorAttribute, 4, gl.FLOAT, false, 0, 0);
// | clear the screen.
gl.clearColor(0.93, 0.93, 0.93, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// | draw the triangle.
gl.drawArrays(gl.TRIANGLES, 0, 3);
hWnd.appendChild(canvas)
document.body.appendChild(hWnd);
})();

webgl readpixels is always returning 0,0,0,0

I am trying to do picking in WebGl. I have two shapes rendered along with different texture mapped on each. I am trying to grab pixel on certain co-ordinates. Here is the example.
var pixelValues = new Uint8Array(4);
gl.readPixels(10, 35, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixelValues);
console.log(pixelValues);
But pixelValues always contain [0,0,0,0]. What am I doing wrong? Do I need to do something related to framebuffer?
You don't need preserveDrawingBuffer: true to call readPixels. What you need is to call readPixels before exiting the current event.
The spec says if you call any function that affects the canvas (gl.clear, gl.drawXXX) then the browser will clear the canvas after the next composite operation. When that composite operation happens is up to the browser. It could be after it processes several mouse events or keyboard events or click events. The order is undefined. What is defined is that it won't do it until the current event exits so
render
read
const gl = document.querySelector("canvas").getContext("webgl");
render();
read(); // read in same event
function render() {
gl.clearColor(.25, .5, .75, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function read() {
const pixel = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
log(pixel);
}
function log(...args) {
const elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
<canvas></canvas>
works where as
render
setTimeout(read, 1000); // some other event
does not work
const gl = document.querySelector("canvas").getContext("webgl");
render();
setTimeout(read, 1000); // read in other event
function render() {
gl.clearColor(.25, .5, .75, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function read() {
const pixel = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
log(pixel);
}
function log(...args) {
const elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
<canvas></canvas>
Note that since it's the composite operation (the browser actually drawing the canvas on the page with the rest of the HTML) that triggers the clear, if the canvas is not on the page then it's not composited and won't be cleared.
In other words the case that didn't work above does work here
// create an offscreen canvas. Because it's offscreen it won't be composited
// and therefore will not be cleared.
const gl = document.createElement("canvas").getContext("webgl");
render();
setTimeout(read, 1000); // read in other event
function render() {
gl.clearColor(.25, .5, .75, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function read() {
const pixel = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
log(pixel);
}
function log(...args) {
const elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
Now, if you want to call readPixels in some other event, like when the user clicks an element, then you have at least 2 options
Set preserveDrawingBuffer: true
Render again in your event
screenshotElement.addEventListener('click', event => {
render();
gl.readPixels(...);
});
According to WebGL specs, you need to call getContext setting the preserveDrawingBuffer flag, like:
var ctx = canvas.getContext("webgl", {preserveDrawingBuffer: true});
if you plan to read the pixels after exiting the event where the GL context is rendered. This prevents the drawing buffer (color, depth, stencil) from being cleared after they are draw to screen. Keep in mind that settings this may cause a performance penalty.
Alternatively, you can read the pixels before they are presented, which should also work.
I have two shapes rendered along with different texture mapped on each.
gl.readPixels(10, 35, 1, 1, ...);
I'm sure you thought gl.readPixels starts reading pixels from the top left corner, but it doesn't. gl.readPixels starts reading them from the lower left corner.
From the WebGLRenderingContext.readPixels() documentation:
Parameters
x
A GLint specifying the first horizontal pixel that is read from the lower left corner of a rectangular block of pixels.
y
A GLint specifying the first vertical pixel that is read from the lower left corner of a rectangular block of pixels.
<!DOCTYPE html>
<body>
<head>
<title>Pick object by click. WebGL, JavaScript</title>
<script src="https://cdn.jsdelivr.net/npm/gl-matrix#3.4.3/gl-matrix-min.js"></script>
<style>
#renderCanvas {
position: absolute;
}
#outputEN {
position: absolute;
top: 210px;
left: 20px;
}
#outputRU {
position: absolute;
top: 235px;
left: 20px;
}
#outputCH {
position: absolute;
top: 260px;
left: 20px;
}
#outputPinyin {
position: absolute;
top: 285px;
left: 20px;
}
</style>
</head>
<body>
<div>
<canvas id="renderCanvas" width="300" height="300"></canvas>
<span id="outputEN">Click on any object or outside</span>
<span id="outputRU">Кликните на любой объект или мимо</span>
<span id="outputCH">单击任何对象或外部</span>
<span id="outputPinyin">Dān jí rènhé duìxiàng huò wàibù</span>
</div>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec2 aPosition;
uniform mat4 uMvpMatrix;
void main() {
gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec3 uColor;
uniform bool uClick;
uniform vec3 uPickColor;
void main() {
if (!uClick) {
gl_FragColor = vec4(uColor, 1.0);
} else {
gl_FragColor = vec4(uPickColor, 1.0);
}
}
</script>
<script>
const gl = document.getElementById("renderCanvas").getContext("webgl");
const outputEN = document.getElementById("outputEN");
const outputRU = document.getElementById("outputRU");
const vShader = gl.createShader(gl.VERTEX_SHADER);
const vSrc = document.getElementById("vertexShader").firstChild.textContent;
gl.shaderSource(vShader, vSrc);
gl.compileShader(vShader);
let ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
if (!ok) {
console.log("vert: " + gl.getShaderInfoLog(vShader));
};
const fShader = gl.createShader(gl.FRAGMENT_SHADER);
const fSrc = document.getElementById("fragmentShader").firstChild.textContent;
gl.shaderSource(fShader, fSrc);
gl.compileShader(fShader);
ok = gl.getShaderParameter(fShader, gl.COMPILE_STATUS);
if (!ok) {
console.log("frag: " + gl.getShaderInfoLog(fShader));
};
const program = gl.createProgram();
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
gl.linkProgram(program);
ok = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!ok) {
console.log("link: " + gl.getProgramInfoLog(program));
};
gl.useProgram(program);
const vertPositions = [
-0.5, -0.5,
-0.5, 0.5,
0.5, -0.5,
0.5, 0.5
];
const vertPosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertPosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertPositions), gl.STATIC_DRAW);
const aPositionLocation = gl.getAttribLocation(program, "aPosition");
gl.vertexAttribPointer(aPositionLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPositionLocation);
const modelMatrix = glMatrix.mat4.create();
const mvpMatrix = glMatrix.mat4.create();
const projMatrix = glMatrix.mat4.create();
glMatrix.mat4.ortho(projMatrix, -0.5, 2.5, 2.5, -0.5, 10, -10);
const viewMatrix = glMatrix.mat4.create();
glMatrix.mat4.lookAt(viewMatrix, [0, 0, 10], [0, 0, 0], [0, 1, 0]);
const projViewMatrix = glMatrix.mat4.create();
glMatrix.mat4.mul(projViewMatrix, projMatrix, viewMatrix);
const uMvpMatrixLocation = gl.getUniformLocation(program, "uMvpMatrix");
const uColorLocation = gl.getUniformLocation(program, "uColor");
const uClickLocation = gl.getUniformLocation(program, "uClick");
const uPickColorLocation = gl.getUniformLocation(program, "uPickColor");
gl.uniform1i(uClickLocation, 0);
const firstObj = {
pos: glMatrix.vec3.fromValues(0, 0, 0),
scale: glMatrix.vec3.fromValues(0.7, 0.7, 1),
color: glMatrix.vec3.fromValues(0.50, 0.84, 0.22)
};
const secondObj = {
pos: glMatrix.vec3.fromValues(1, 0, 0),
scale: glMatrix.vec3.fromValues(0.7, 0.7, 1),
color: glMatrix.vec3.fromValues(0.07, 0.59, 0.09)
};
const thirdObj = {
pos: glMatrix.vec3.fromValues(2, 0, 0),
scale: glMatrix.vec3.fromValues(0.7, 0.7, 1),
color: glMatrix.vec3.fromValues(0.12, 0.88, 0.48)
};
const fourthObj = {
pos: glMatrix.vec3.fromValues(0, 1, 0),
scale: glMatrix.vec3.fromValues(0.7, 0.7, 1),
color: glMatrix.vec3.fromValues(0.65, 0.37, 0.07)
};
const pickColors = {
first: glMatrix.vec3.fromValues(255, 0, 0),
second: glMatrix.vec3.fromValues(0, 255, 0),
third: glMatrix.vec3.fromValues(0, 0, 255),
fourth: glMatrix.vec3.fromValues(255, 255, 0)
};
gl.canvas.onmousedown = (e) => {
// Get coordinates of mouse pick
const rect = gl.canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const pickX = mouseX;
const pickY = rect.bottom - rect.top - mouseY - 1;
// console.log("mouse pick coords:", pickX, pickY);
// Set the click flag and color id
gl.uniform1i(uClickLocation, 1);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw objects for picking
gl.uniform3fv(uPickColorLocation, pickColors.first);
glMatrix.mat4.fromTranslation(modelMatrix, firstObj.pos);
glMatrix.mat4.scale(modelMatrix, modelMatrix, firstObj.scale);
glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.uniform3fv(uPickColorLocation, pickColors.second);
glMatrix.mat4.fromTranslation(modelMatrix, secondObj.pos);
glMatrix.mat4.scale(modelMatrix, modelMatrix, secondObj.scale);
glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.uniform3fv(uPickColorLocation, pickColors.third);
glMatrix.mat4.fromTranslation(modelMatrix, thirdObj.pos);
glMatrix.mat4.scale(modelMatrix, modelMatrix, thirdObj.scale);
glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.uniform3fv(uPickColorLocation, pickColors.fourth);
glMatrix.mat4.fromTranslation(modelMatrix, fourthObj.pos);
glMatrix.mat4.scale(modelMatrix, modelMatrix, fourthObj.scale);
glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
const pixels = new Uint8Array(4);
gl.readPixels(pickX, pickY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
// console.log("pick color:", pixels[0], pixels[1], pixels[2], pixels[3]);
const pickResult = glMatrix.vec3.fromValues(pixels[0], pixels[1],
pixels[2]);
let messageEN = "";
let messageRU = "";
let messageCH = "";
let messagePinyin = "";
if (glMatrix.vec3.exactEquals(pickResult, pickColors.first)) {
messageEN = "First object";
messageRU = "Первый объект";
messageCH = "第一个对象";
messagePinyin = "Dì yī gè duìxiàng";
} else if (glMatrix.vec3.exactEquals(pickResult, pickColors.second)) {
messageEN = "Second object";
messageRU = "Второй объект";
messageCH = "第二个对象";
messagePinyin = "Dì èr gè duìxiàng";
} else if (glMatrix.vec3.exactEquals(pickResult, pickColors.third)) {
messageEN = "Third object";
messageRU = "Третий объект";
messageCH = "第三个对象";
messagePinyin = "Dì sān gè duìxiàng";
} else if (glMatrix.vec3.exactEquals(pickResult, pickColors.fourth)) {
messageEN = "Fourth object";
messageRU = "Четвёртый объект";
messageCH = "第四个对象";
messagePinyin = "Dì sì gè duìxiàng";
} else {
messageEN = "You didn't click on the objects";
messageRU = "Вы не кликнули по объектам";
messageCH = "你没有点击对象";
messagePinyin = "Nǐ méiyǒu diǎnjī duìxiàng";
}
console.log(messageEN);
outputEN.innerText = messageEN;
outputRU.innerText = messageRU;
outputCH.innerText = messageCH;
outputPinyin.innerText = messagePinyin;
gl.uniform1i(uClickLocation, 0);
draw();
};
function draw() {
gl.clearColor(0.9, 0.9, 0.95, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
glMatrix.mat4.fromTranslation(modelMatrix, firstObj.pos);
glMatrix.mat4.scale(modelMatrix, modelMatrix, firstObj.scale);
glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
gl.uniform3fv(uColorLocation, firstObj.color);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
glMatrix.mat4.fromTranslation(modelMatrix, secondObj.pos);
glMatrix.mat4.scale(modelMatrix, modelMatrix, secondObj.scale);
glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
gl.uniform3fv(uColorLocation, secondObj.color);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
glMatrix.mat4.fromTranslation(modelMatrix, thirdObj.pos);
glMatrix.mat4.scale(modelMatrix, modelMatrix, thirdObj.scale);
glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
gl.uniform3fv(uColorLocation, thirdObj.color);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
glMatrix.mat4.fromTranslation(modelMatrix, fourthObj.pos);
glMatrix.mat4.scale(modelMatrix, modelMatrix, fourthObj.scale);
glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
gl.uniform3fv(uColorLocation, fourthObj.color);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
draw();
</script>
</body>
</body>

Categories