In this lesson, we look at how to consolidate different data, such as position and color, into a single vertex array. This becomes more important as we keep adding attributes such as per-vertex data for lighting and textures. We will update the
stride value in the
vertexAttribPointer method to accommodate these changes.
We've seen how to pass an array of vertices to the vertex shader, and how to pass another array of colors in a way that each color is assigned to a single vertex, and then interpolated across the surface by the fragment shader. But the way we've been doing it is very precarious. We have two completely separate arrays holding two different sets of data, and we're relying on them to match up based on the relative positions of each element.
It's fine now because we're using completely random positions and random colors, so if something went wrong we probably wouldn't even notice it. But let's look at what happens when we have something more exactly. I've removed all the code that makes the random vertices and colors, and I've been setting up the project with a new set of predefined vertices, and I'll change the vertex count to 36, because that's how many points are in this dataset.
I'm also going to comment out the lines that set the vertex colors, our drawing mode is triangles, so let's see what this gives us. We have a ring of triangles going around in a circle, kind of like a crown. Because we removed the color data, they're all black right now. Now I'll paste in a colors array here. You should notice that the vertices are grouped in sets of three to create those individual triangles, and the colors are also grouped in three, with each group having the same color.
Thus each triangle will have all three of its vertices colored the same. We run that, and yep, that's what we get. So hopefully you can see how non-optimum this setup is. If I add a triangle, or remove one, or want to change the color at a specific vertex, I'm jumping back and forth between two arrays, trying to keep them in sync, and I can't even see both arrays on one screen without a lot of scrolling. One mistake and it all breaks, and it's really hard to see where the error is.
Add to this the fact that we'll eventually be adding additional per-vertex data for lighting and textures, now you have four sets of data to try to keep in sync. Ideally, all the data for a single vertex could be grouped together, so let me get rid of this mess and paste in a new vertices array. Now this looks better already, you see that we have groups of three vertices, each with three coordinate values, and four color values, all right in a row. If I were to add a triangle without adding in a color, for example, it would stick out like a sore thumb.
If I wanted to change a color on a specific vertex of a specific triangle, I know exactly where that color is defined. When we go to add in additional information, it can all be put right in the same place. But, now we need a way to use a single array of data to feed two different attributes. Fortunately, webgl has us covered. Remember back when we first looked at the glVertexAttribPointer method, and we just left those last two parameters as 0? Well, those are the key.
Those parameters let us slice up the data and take out just the chunks we need for each buffer. The fifth parameter is called stride, and the sixth is offset. The stride parameter lets webgl know how many pieces of data to skip over to get to the beginning of a new set of data, in other words, the size of each row. Here we have three values for position, and four for color, so we're going to need to tell it to stride over seven elements. The offset says within those seven elements, start getting your data at this point.
The position data is at the start of each row, so that'll be 0 for position. Now actually seven is not going to work here, this method does not want an integer, it wants a number of bytes to skip over, so we need to know how many bites are in each element of a float32 array. We're lucky because a float32 array has a property telling us just that. Here we can say float32array.btyesPerElement * 7. So to recap webgl grabs seven elements out of the buffer, skip zero, and take the next three for the first position.
It will then take the next seven elements and do the same for all the elements in the buffer. That works. Now we need to do the same thing for colors. We can actually get rid of this code that creates the color buffer because we no longer need a separate buffer, all of our data is in a single buffer. We'll have to make sure that we don't unbind it here though.
For this one, we're still going to take seven elements, but we want to skip the first three coordinate values to get to the color data, so we pass in float32array.bytesPerElement * 3, and we take the next four elements in the list which is our red, green, blue, and alpha data, all good. Run it, and yes we still have our colored triangles just where we left them. Now I can manipulate this data much easier. Say I wanted to give this triangle here a yellow top, I just go to the middle point and change that color to yellow.
Run it again, and sure enough, the blue triangle has a yellow top. If wanted to remove one of these triangles, I just cut out the whole three rows that define it and change the vertex count. No need to worry about synching multiple arrays, and it's gone. We could actually make that vertex count dynamic as well, which would make things even easier.