0000 0000 0000 0101
Combining arrays, and a square
In this blog post we will be reviewing our arrays, buffers and attribute pointers.
After clearing up how they work right now, we will be combining our position and color arrays into one.
This also means combining the buffers.
We will still need both attribute pointers, but we do have to change them.
Lastly we will create a square to showcase how easy future changes will be after combining the arrays.
Table of contents
Current vertex array and vertex buffer
The situation as we have left it in the previous blog post leaves us with 2 arrays and buffers for the positions and colors.
To combine them into a single array of floats and into a single Vertex Buffer Object we need to obtain a better understanding of Vertex Buffer Objects and Vertex Attribute Pointers.
First let's review our data arrays.
Positions:
Each position has an x, y and z element.
Each element is a 32 bit float, packed together tightly in a Float32Array.
Colors:
Each color has an r, g and b element.
Each element is a 32 bit float, also packed together tightly in a Float32Array.
The array for positions and the array for colors are identical in almost every way.
This means combining them will amount to almost no work at all.
But before we get to that point, let's also look at the buffers and attribute arrays.
The buffer for the positions is an array buffer that takes in a Float32Array as data. As you probably would have guessed, the buffer for the colors is also an array buffer that takes in a Float32Array. The buffers are even more identical than the position and color arrays. This means we will have to do even less refactoring on the buffers. That also means that the bulk of the work will be in the vertex attribute pointers.
Positions:
Vertex 0
Vertex 1
Vertex 2
x
y
z
x
y
z
x
y
z
Vertex 0
Vertex 1
Vertex 2
r
g
b
r
g
b
r
g
b
The buffer for the positions is an array buffer that takes in a Float32Array as data. As you probably would have guessed, the buffer for the colors is also an array buffer that takes in a Float32Array. The buffers are even more identical than the position and color arrays. This means we will have to do even less refactoring on the buffers. That also means that the bulk of the work will be in the vertex attribute pointers.
Updated vertex array and vertex buffer
If we want to combine the arrays, we first have to decide how the data we supply should be read.
Because the attribute pointers are very flexible in reading the data, we can put all information of a single vertex right after each other.
This means a vertex will get twice as big, as a vertex will hold 6 elements.
These elements will be x, y, z, r, g and b.
These blocks will be what our data should be looking like.
The sequences actually stay the same, so if we format the code nicely it will actually not look too threatening at all.
And with that we have succesfully combined the positions and the colors into a single vertices array.
We will need a buffer for the new vertices array so it can be read from by the vertex attribute pointer. Fortunately for us, because we have reduced our vertex arrays to just one array, we will also only need a single vertex buffer. The only thing the buffer should do is take our vertex array as input. This means that we can fully delete 1 of the array buffers, and change the other one to take in our new vertices array. This is a single argument in the entire vertex buffer code. How convenient. This change finishes up the vertex buffer. We now have a new Vertex Array with the data and a Vertex Buffer which enables the data to be read by WebGL components. All that is left for us now is redefining how the data should be served to the shaders using the Vertex Attribute Pointer.
Vertex 0
x
y
z
r
g
b
↓
Vertex 1
x
y
z
r
g
b
↓
Vertex 2
x
y
z
r
g
b
We will need a buffer for the new vertices array so it can be read from by the vertex attribute pointer. Fortunately for us, because we have reduced our vertex arrays to just one array, we will also only need a single vertex buffer. The only thing the buffer should do is take our vertex array as input. This means that we can fully delete 1 of the array buffers, and change the other one to take in our new vertices array. This is a single argument in the entire vertex buffer code. How convenient. This change finishes up the vertex buffer. We now have a new Vertex Array with the data and a Vertex Buffer which enables the data to be read by WebGL components. All that is left for us now is redefining how the data should be served to the shaders using the Vertex Attribute Pointer.
Current vertex attribute pointer
Unlike the Vertex Arrays and the Vertex Buffers, we will not be combining the Vertex Attribute Pointers.
This is because we will want the data to be handed to 2 different attributes in the shader program.
We will however have to change the way we use the Vertex Attribute Pointers.
Before we change the vertex attribute pointers, let's review how they are working right now.
The first thing we did was get the position in the shader program where we will attach the buffer. Within the next call we specified how the data was supposed to be supplied to the shader program. We will be looking ast the 2nd call in particular. So let's look at each parameter in the call.
In this chart we can see that each iteration has 3 attributes as specified.
We also have the attributes tightly packed, meaning after 3 attributes the next iteration can directly start.
Lastly we skip 0 bytes at the start because the first thing in the array is the first attribute of the first vertex.
Now let's see how we can read our new combined buffer into the vertex attribute pointer.
The first thing we did was get the position in the shader program where we will attach the buffer. Within the next call we specified how the data was supposed to be supplied to the shader program. We will be looking ast the 2nd call in particular. So let's look at each parameter in the call.
- The 'in' attribute of the shader program that will receive the data.
- The number of elements to be passed to the shader 'in' attribute for each iteration.
- The data type of the elements being passed.
- The normalization indicator which has no effect on float values, so leave it as a false.
- The stride specifies the offset of vertex attributes in bytes. This is 0 because we read the positions after each other without any interruption. That means each consecutive iteration of supplying the vertices to the shader 'in' attribute takes the next 3 elements in line. The vertex attributes are tightly packed in the array if this is the case.
- The offset specifies the bytes in front of the first vertex attribute to be supplied to the shader 'in' attribute. This is 0 as well, because the first attribute we want to read is directly at the start of the array.
Vertex 0
Vertex 1
Vertex 2
x
y
z
x
y
z
x
y
z
↑
↑
↑
↑
↑
↑
↑
↑
↑
1
2
3
1
2
3
1
2
3
Iteration 1
Iteration 2
Iteration 3
Now let's see how we can read our new combined buffer into the vertex attribute pointer.
Updated vertex attribute pointer
We will need to create 2 attribute pointers.
The first one is responsible for getting the positions from the buffer and supplying them to the shader attribute.
The position attribute pointer will take in 3 elements (x,y,z).
The vertices are 6 elements long and we only want the first 3 of each vertex, because those are the x, y and z values.
The second one is responsible for getting the colors from the buffer and supplying them to a different shader attribute. The color attribute pointer will also take in 3 elements (r,g,b). The vertices are 6 elements long and we only want the last 3 of each vertex, because those are the r, g and b values.
To achieve this, we will make the stride 6 elements each. If the 2nd argument (number of elements) is 3, and the 5th argument (stride) is the size of 6 floats, the attribute pointer will take 3 attributes and skip 3 for each vertex. This means that for the color attribute array, we will have to skip 3 elements first, and then take 3.
The color attribute pointer will look like this:
Translating that into code, our results will look like this for both attribute pointers. The only noticable change for us are the last 2 parameters. The entire vertex is 6 floats, and a float in javascript has 4 bytes, so the 5th parameter becomes 6*4.
The 6th argument is only changed for the colors. This is because the color attribute pointer will skip 3*4 bytes at the start of the buffer. That also means the behavior of the color attribute pointer from then on is the same as the positions attribute pointer, meaning it will take 3 elements, then skip 3 elements.
The second one is responsible for getting the colors from the buffer and supplying them to a different shader attribute. The color attribute pointer will also take in 3 elements (r,g,b). The vertices are 6 elements long and we only want the last 3 of each vertex, because those are the r, g and b values.
To achieve this, we will make the stride 6 elements each. If the 2nd argument (number of elements) is 3, and the 5th argument (stride) is the size of 6 floats, the attribute pointer will take 3 attributes and skip 3 for each vertex. This means that for the color attribute array, we will have to skip 3 elements first, and then take 3.
Vertex 0
x
y
z
r
g
b
↑
↑
↑
1
2
3
.
.
.
Iteration 1
↓
Vertex 1
x
y
z
r
g
b
↑
↑
↑
1
2
3
.
.
.
Iteration 2
↓
Vertex 2
x
y
z
r
g
b
↑
↑
↑
1
2
3
.
.
.
Iteration 3
The color attribute pointer will look like this:
Vertex 0
x
y
z
r
g
b
↑
↑
↑
x
x
x
1
2
3
Iteration 1
↓
Vertex 1
x
y
z
r
g
b
↑
↑
↑
.
.
.
1
2
3
Iteration 2
↓
Vertex 2
x
y
z
r
g
b
↑
↑
↑
.
.
.
1
2
3
Iteration 3
Translating that into code, our results will look like this for both attribute pointers. The only noticable change for us are the last 2 parameters. The entire vertex is 6 floats, and a float in javascript has 4 bytes, so the 5th parameter becomes 6*4.
The 6th argument is only changed for the colors. This is because the color attribute pointer will skip 3*4 bytes at the start of the buffer. That also means the behavior of the color attribute pointer from then on is the same as the positions attribute pointer, meaning it will take 3 elements, then skip 3 elements.
Creating a square
Unfortunately we will not be creating a square at all in this blog post.
We will be creating 2 triangles that are placed to look like a square.
This is because nearly everything in WebGL is drawn out of the triangle primitive.
What we normally would have to do when creating 2 triangles, is defining all the vertex positions. Every triangle has 3 angles (obviously), so we would need 3 vertices per triangle. Because our triangles will share 2 of their vertices, we can get away with only defining 4 vertices. This is where the indices array and the Element Buffer Object come into play. We can define 6 points in the indices array to make 2 triangles, while we only need to define 4 positions and colors.
In the vertex array code, we will need to change our top position to be on the side in stead of in the center. Next we will only have to define another top point on the other side, and the vertex array will already be done. I have chosen yellow as my 4th color.
Next we will define our 2nd triangle in the indices array. Because we have already specified to the drawElements call that we want WebGL to continue drawing until indices.length has been reached, we are already done with the square.
If you combine all code in this blog post with the code in the previous blog posts, your result should look something like the result below. Don't worry if it didn't work. First try reading this blog post again to see if you can find where it went wrong. If that doesn't work, the full source code to my result is available at the bottom of the page.
What we normally would have to do when creating 2 triangles, is defining all the vertex positions. Every triangle has 3 angles (obviously), so we would need 3 vertices per triangle. Because our triangles will share 2 of their vertices, we can get away with only defining 4 vertices. This is where the indices array and the Element Buffer Object come into play. We can define 6 points in the indices array to make 2 triangles, while we only need to define 4 positions and colors.
In the vertex array code, we will need to change our top position to be on the side in stead of in the center. Next we will only have to define another top point on the other side, and the vertex array will already be done. I have chosen yellow as my 4th color.
Next we will define our 2nd triangle in the indices array. Because we have already specified to the drawElements call that we want WebGL to continue drawing until indices.length has been reached, we are already done with the square.
If you combine all code in this blog post with the code in the previous blog posts, your result should look something like the result below. Don't worry if it didn't work. First try reading this blog post again to see if you can find where it went wrong. If that doesn't work, the full source code to my result is available at the bottom of the page.
The Result
In the next WebGL blog post we will make a start with vector math so we can work towards moving our vertices around. Please look forward to it.
* CODE *