More vertex attributes
The only vertex attribute which we used in the previous lessons was a vertex position. However in real life you need more data associated with each vertex — a color, texture coordinates, a normal vector, etc. So in this lesson we will learn how to use multiple vertex attributes.
Once again, let's remember how did our vertex shader look before we added a uniform and rotation there:
#version 300 es
precision highp float;
in vec2 position;
void main(void)
{
gl_Position = vec4(position, 0.0, 1.0);
}
Now, lets add a color to vertex attributes:
#version 300 es
precision highp float;
in vec2 position;
// That's another vertex attribute
in vec3 color;
// An additional output value - oh, that's something new!
out vec3 v_color;
void main(void)
{
v_color = color; // Put color attribute into additional output parameter
gl_Position = vec4(position, 0.0, 1.0);
}
Also we need to update our fragment shader. Let's remember how's it look:
#version 300 es
precision highp float;
out vec4 frag_color;
void main(void)
{
frag_color = vec4(1.0, 0.5, 0.25, 1.0);
}
Now we need to connect the vertex shader output color parameter to fragment shader input parameter. To do that we need to update our fragment shader like that:
#version 300 es
precision highp float;
// A vertex shader ouput parameter is an input parameter of a fragment shader
in vec3 v_color;
out vec4 frag_color;
void main(void)
{
// Utilize v_color parameter to set the current pixel's color
frag_color = vec4(v_color, 1.0);
}
v_color
is an additional vertex shader output parameter. We will discuss it in the next lesson. Now, let's just focus on the additional vertex attributes. So, we added an input attribute into the vertex shader. Now we should update our GPU buffer by putting there a proper data. After that we should tell WebGL what sort of data is stored there.
Previously, our vertex data looked like that:
const vertices =
[
-0.5, -h / 3,
0.0, 2 * h / 3,
0.5, -h / 3
];
Three vertices, two coordinates each. Now, let's add a color to each vertex. To do that we should add RGB values into this array. Each of RGB values is just a number from [0; 1]
interval:
const vertices =
[
// x y r g b
-0.5, -h / 3, 0, 0.5, 1,
// x y r g b
0.0, 2 * h / 3, 1, 0, 0.5,
// x y r g b
0.5, -h / 3, 0.5, 1, 0
];
Notice that we put vertex attributes in an interleaved way. Next, we put that data into GPU buffer as before, nothing changed there:
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
However now we should tell WebGL that there are two different attributes for each of the vertices:
const position = gl.getAttribLocation(program, "position");
const color = gl.getAttribLocation(program, "color");
gl.enableVertexAttribArray(position);
gl.enableVertexAttribArray(color);
const stride = 5 * Float32Array.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, stride, 0);
gl.vertexAttribPointer(color, 3, gl.FLOAT, false, stride, 2 * Float32Array.BYTES_PER_ELEMENT);
Let's see what's going on here. Now we have two vertex attributes — position
and color
. Like before, we need to find their locations, enable them and bind a data to them. Finding and enabling are easy. Let's take a closer look into vertexAttribPointer
part:
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, stride, 0);
gl.vertexAttribPointer(color, 3, gl.FLOAT, false, stride, 2 * Float32Array.BYTES_PER_ELEMENT);
The first parameter is an attribute location — that's easy. The second parameter is a number of components per attribute. position
is a 2D vector, so it has two components: x
and y
. Color is presented as a 3D vector because we need three values per color: red, green and blue components. The third parameter is a type of each component and that's also an easy part. The next boolean parameter is ignored for FLOAT
values (we will discuss its meaning some later).
The last two parameters are stride
and offset
. stride
is a distance between two vertices in GPU buffer. In our case each of the vertex attributes have a total of 5 components — 2 for position
and 3 for color
. That's why the stride is 5. Also, a stride should be specified in bytes. That's why we also multiply it by Float32Array.BYTES_PER_ELEMENT
.
offset
is a relative byte offset of an attribute inside each of the vertex attributes group. We keep our attributes in an interleaved way, so the color
attribute goes right after position
attribute which size is 2 components. So we should skip position
to get to color
. That's why color
offset is 2, and since it's a byte offset, it is also multiplied by Float32Array.BYTES_PER_ELEMENT
.
So, two lines above configure how the data is presented in the vertex buffer. This data:
const vertices =
[
// x y r g b
-0.5, -h / 3, 0, 0.5, 1,
// x y r g b
0.0, 2 * h / 3, 1, 0, 0.5,
// x y r g b
0.5, -h / 3, 0.5, 1, 0
];
Now everything is ready and you should see a colorized triangle:
How does it work and why the colors are so nicely flow into each other? We will learn it in the next lesson!