Thomas_
Thomas_

Reputation: 43

How gl.drawElements "find" the corresponding vertices array buffer?

and thanks you in advance.

I am quite new in Webgl and I don't really understand the link between the drawElements method and the current vertices buffer I want to draw. I understand approximatively what is happening for the drawArray method (e.g creating a buffer, binding it to the context, filling it with data, pointing to the corresponding attribute, drawing it). But when I try to do the same with a indices array and less vertices data, I encounter this type of error :

[.Offscreen-For-WebGL-0x7fae2b940800]GL ERROR :GL_INVALID_OPERATION : glDrawElements: bound to target 0x8893 : no buffer

Maybe a hint of my code could help you.

    const cube = new Program(renderer.gl, vertex3d, fragment); // my webgl program
    const cubeData = new Cube(); // Only array of vertices/indices
    const cubeVertexPosition = new ArrayBuffer(renderer.gl, cubeData.vertices, 'STATIC_DRAW'); // ARRAY_BUFFER
    const cubeVertexIndices = new IndexBuffer(renderer.gl, renderer.gl.UNSIGNED_SHORT, cubeData.indices, 'STATIC_DRAW'); // ELEMENT_ARRAY_BUFFER
cubeVertexPosition.attribute('aPosition', 3, 'FLOAT', false); // define attribute corresponding in vertex shader
    cubeVertexPosition.attributePointer(cube); // enableVertexAttribArray + vertexAttribPointer
    [...]
    cubeVertexIndices.draw('TRIANGLES', 0, 36); // drawElements with gl.UNSIGNED_SHORT type

I am successful at drawing it with drawArray :)

(the [...] is only matrices transformation for the uniforms);

Maybe you have a rapid tips in mind that could help me understand this black magic,

Thanks a lot !

Upvotes: 2

Views: 3089

Answers (2)

user128511
user128511

Reputation:

The code you posted is not WebGL. You are using some library which is clear from the code. Things like Program, IndexBuffer, ArrayBuffer are all part of some library you're using. How that library does things is up to that library.

In general WebGL has shaders, a vertex shader who's job it is to set gl_Position to a clip space coordinate for each vertex and a fragment shader who's job it is to set gl_FragColor to a color for each pixel.

The vertex shader usually gets data about positions from attributes. Attributes usually get their data from buffers. You tell an attribute which buffer to get data from by first binding the buffer to the ARRAY_BUFFER bind point with gl.bindBuffer(gl.ARRAY_BUFFER, someBuffer) and then calling gl.vertexAttribPointer which tells WebGL how you want to get data out of that buffer (what type the data is, how many values there are per vertex, how many bytes to skip between vertices, how far in the buffer to start). gl.vertexAttribPointer saves all of that info for the given attribute and a reference to the current buffer that was bound to the ARRAY_BUFFER bind point so you are free to bind a different buffer there to setup another attribute.

When you call gl.drawArrays the data will be pulled from the buffers as you specified into the attributes for the shader, one set of values for each iteration of the shader

As for gl.drawElements it takes one more buffer, bound to ELEMENT_ARRAY_BUFFER and when you cll gl.drawElements you tell it the type of data in that buffer (gl.UNSIGNED_BYTE or gl.UNSIGNED_SHORT). It then uses the values of that buffer to pull values out of the attribute buffer.

gl.drawElements is exactly the same as gl.drawArrays if you put a simple increasing value in the the buffer. Example

 const offset  = 0;
 const numVerts = 100;

 // process 100 vertices from the buffers pointed to by the attributes
 // in order 0 to 99
 gl.drawArrays(gl.POINTS, offset, numVerts)

Is effectively the same as

 // fill a buffer with numbers 0 to 99 (0 to numVerts)
 const numVerts = 100;
 const indexData = new Uint16Array(numVerts);
 for (let i = 0; i < numVerts; ++i) {
   indexData[i] = i;
 }
 const indexBuffer = gl.createBuffer();
 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);

 // process 100 vertices from the buffers pointed to by the attributes
 // in order 0 to 99
 const offset  = 0;
 gl.drawElements(gl.POINTS, numVerts, gl.UNSIGNED_SHORT, offset);

but of course since in this second case you supplied the indexData it doesn't have to be consecutive.

I'd suggest reading some other webgl tutorials

Upvotes: 1

user8849929
user8849929

Reputation:

drawArray use only one or several ARRAY_BUFFER from where vertices are drawn in order they are in the buffers, from the first parameter for count parameter.

drawElements use one or several ARRAY_BUFFER AND an ELEMENT_ARRAY_BUFFER which contain indices that points to ARRAY_BUFFER vertices to draw. In drawElements the count parameter specify the count of indices to be read in the ELEMENT_ARRAY_BUFFER, while offset specify an offset in bytes where to begin to read the ELEMENT_ARRAY_BUFFER (usualy FirstIndex*sizeof(type) where type can be UNSIGNED_BYTE (1 bytes), UNSIGNED_SHORT (2 bytes) or UNSIGNED_INT (4 bytes).

ELEMENT_ARRAY_BUFFER:

[0][1][2][1][2][0][1][2][3][3][1][2][3][4][5][...

ARRAY_BUFFER:

|   0   |   1    |    2   |    3   |    4   | ...
[x][y][z][x][y][z][x][y][z][x][y][z][x][y][z][...

To properly work, the offset + count*sizeof(type) should not be greater than the ELEMENT_ARRAY_BUFFER size in byte. Also, elements index in the ELEMENT_ARRAY_BUFFER should be less than the count of vertices contained in the ARRAY_BUFFER.

Like drawArray, the drawElements takes currently bound buffer(s) (with attributes pointers configured) as data source. The difference with drawElements is that you must specify an additionnal elements (indices) buffer, using the ELEMENT_ARRAY_BUFFER target, like that :

gl.bindBuffer(gl.ARRAY_BUFFER, myVerticesA);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, myIndicesA);
// configure attributes pointers here
gl.drawElements(gl.TRIANGLES, 12345, gl.UNSIGNED_SHORT, 0);

The "how" drawElements will take attributes in the ARRAY_BUFFER buffer(s) according indices stored in ELEMENT_ARRAY_BUFFER depend on how you configured your attributes pointers.

Assume the following vertex buffer with interleaved positions, normals and texture coordinates:

|    p0    ||    n0    ||  t0  ||    p1    ||    n1    ||  t1  |
[px][py][pz][nx][ny][nz][tu][tv][px][py][pz][nx][ny][nz][tu][tv][...

We define the attribute pointers as the following:

let stride = 8*4;  // 8*float (8 * 4 bytes)
let offp = 0;   // positions at beginning
let offn = 3*4; // normals after 3*float position.
let offt = 6*4; // tex coords after 3*float position + 3*float normal 
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, stride, offp);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, stride, offn);
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, stride, offt);

Using elements (indices) buffer, GL will simply shift pointers position according indices stored in the ELEMENT_ARRAY_BUFFER buffer:

 // pseudo-code
 for(let i = start_elem; i < start_elem+count_elem; i++) {

   let index = ELEMENT_ARRAY_BUFFER[i];

   attrib[0] = ARRAY_BUFFER[(index*stride)+offp];
   attrib[1] = ARRAY_BUFFER[(index*stride)+offn];
   attrib[2] = ARRAY_BUFFER[(index*stride)+offt];

 }

Upvotes: 1

Related Questions