0000 0000 0000 0100
Adding colors
In this blog post we will first create another vertex array for the colors.
Afterwards we will modify the shaders so we can use the colors in the fragment shader.
Lastly we will create a new buffer for the colors which will enable the shader to read the colors.
Table of contents
Creating the color array
The first step in adding colors to the WebGL is defining the colors.
In the blog posts before this one, we have briefly covered how colors are processed in WebGL.
In this step I will provide a little tool to make the creation of colors a little bit easier.
In the block below I have made a small JavaScript color picker which can translate hex to WebGL colors and the other way around.
You can play around with it a bit to find your favorite color, or just paste the hex in directly.
You will want to pick 3 different colors, 1 for each vertex we defined in the previous blog post.
After picking the 3 different colors, it's time to put them into an array for WebGL.
The array will have 3 float values for each color we picked.
This means we will leave out the alpha channel for this post, but feel free to add it yourself if you feel like a challenge.
For colors, I have been a bit less creative, and simply picked red, green and blue.
The color values will look like this in a Float32Array:
Replace the float values with your own, and the colors will be ready for the buffer.
But before we will continue with the buffers, this time let's do the shaders first.
r(ed)
g(reen)
b(lue)
a(lpha)
Adding color to the shaders
The shaders will be changed so we can add colors into the shaders.
The actual shader we want the colors to be in, is the fragment shader as you might recall from the previous WebGL blog post I wrote.
To get the colors into the fragment shader it will have to pass through the vertex shader.
We can achieve this by inserting the colors into the vertex shader and passing them to the fragment shader using the out keyword.
The fragment shader will receive the colors with the in keyword and use them to change the fragmentColor defined in the shader.
As a small side note, when I use an attribute in this way, I usually prefix the vertex shader attribute with v_... and the fragment shader f_...
In this case we will end up with v_color and f_color.
The vertex shader will look like this after the modification: Let's break down the code to understand what is going on here. The first line specifies the version of GLSL. This has to be specified since WebGL2, otherwise the older version of GLSL will be active causing shader compile errors. Next we have the 2 'in' attributes that we expect to be supplied to the vertex shader through an array buffer object. The 'out' attribute is what we will supply to the next steps in the pipeline and eventually the fragment shader. In the main program we have only added the line where we state that the f_color out attribute equals the v_color in attribute. This will cause the color vertices to be passed on without any modifications.
Note that we have specified the out attribute as a lowp attribute, which means low precision. The values that can be stored in each rgb value are so small that having a higher precision would be a waste of resources.
Next we will modify the fragment shader so it can receive the f_color attribute and use it. In the fragment shader we have added an 'in' attribute with the same name as the 'out' attribute in the vertex shader. Having the same name will make sure that we are able to pass attributes from one shader to the other. The last change we made is simply adding the f_color into the fragmentColor as the first 3 parameters.
You might be wondering why the line vec4(f_color, 1.0) works. Normally we would expect a vec4 to be created like this: vec4(f_color.x, f_color.y, f_color.z, 1.0). The truth is that this is how the line is actually interpreted by GLSL. Converting from different vectors is so common in GLSL, that the vectors have been overloaded to accept vectors of smaller sizes in the arguments. This way we can replace the first 3 arguments with a vec3. The final 1.0 is the alpha channel.
This finishes up preparing the shaders for receiving colors in addition to the positions. We can now move on to the array buffer object and the vertex attribute pointer so we can supply these shaders the colors array.
The vertex shader will look like this after the modification: Let's break down the code to understand what is going on here. The first line specifies the version of GLSL. This has to be specified since WebGL2, otherwise the older version of GLSL will be active causing shader compile errors. Next we have the 2 'in' attributes that we expect to be supplied to the vertex shader through an array buffer object. The 'out' attribute is what we will supply to the next steps in the pipeline and eventually the fragment shader. In the main program we have only added the line where we state that the f_color out attribute equals the v_color in attribute. This will cause the color vertices to be passed on without any modifications.
Note that we have specified the out attribute as a lowp attribute, which means low precision. The values that can be stored in each rgb value are so small that having a higher precision would be a waste of resources.
Next we will modify the fragment shader so it can receive the f_color attribute and use it. In the fragment shader we have added an 'in' attribute with the same name as the 'out' attribute in the vertex shader. Having the same name will make sure that we are able to pass attributes from one shader to the other. The last change we made is simply adding the f_color into the fragmentColor as the first 3 parameters.
You might be wondering why the line vec4(f_color, 1.0) works. Normally we would expect a vec4 to be created like this: vec4(f_color.x, f_color.y, f_color.z, 1.0). The truth is that this is how the line is actually interpreted by GLSL. Converting from different vectors is so common in GLSL, that the vectors have been overloaded to accept vectors of smaller sizes in the arguments. This way we can replace the first 3 arguments with a vec3. The final 1.0 is the alpha channel.
This finishes up preparing the shaders for receiving colors in addition to the positions. We can now move on to the array buffer object and the vertex attribute pointer so we can supply these shaders the colors array.
A basic buffer experience
Just like the positions in the previous WebGL blog post the colors will need to be inserted in the shader through an array buffer.
This time around, we will handle all actions regarding the buffer in one go.
First we will create a buffer and attach the data.
Next we will find the position in the shader so we can connect the data to the shader program.
Lastly we will specify how to interpret the data.
So the first step is creating the buffer. The buffer we will use to attach the colors to the shader is the array buffer. This is the same type of buffer as we have used for the positions. After the creation of the buffer and attaching the color array to the buffer we can proceed with finding the color attribute in the shader program so we can attach the buffer to the shader program. Now we have attached the array to the buffer, and the buffer to the shader program, all that's left is telling the shader program how to interpret the data it's being fed. The arguments have been explained shortly in the previous WebGL blog post. In the next WebGL blog post we will go into more detail about the arguments in this call.
After modifying a buffer, it is important to reset the bound buffer to null. This behavior emerges because we can only modify a buffer by binding it as the active buffer. If there are multiple buffers of the same type bound, modifications will affect both buffers. Because we use the same buffer twice it is extremely important that we unbind the buffer after the position buffer and before the color buffer. This way modifications to the color buffer will not affect the position buffer. When we combine the full code of the buffer, the result should look like this. This almost finishes the addition of colors to WebGL. We only have to enable all our components and start drawing. The last thing we want to do is binding our defined buffers again, and activating the attribute array. Now that we have defined all our code, positioning of the code is very important. Make sure the shader is created before the buffers because the buffers require a reference to the shader program. It goes without saying that the position, color and index arrays need to be defined before the buffers as well. The drawing code will need to be executed at the bottom. If all parts of the code are placed correctly, the result should look like something like below. Don't worry if you did not manage to get the code right in one go, you can try to read through the blog posts or just the code snippets to see if you can spot a mistake. If that doesn't work out either, an example of the code I used for the result is available below the result.
So the first step is creating the buffer. The buffer we will use to attach the colors to the shader is the array buffer. This is the same type of buffer as we have used for the positions. After the creation of the buffer and attaching the color array to the buffer we can proceed with finding the color attribute in the shader program so we can attach the buffer to the shader program. Now we have attached the array to the buffer, and the buffer to the shader program, all that's left is telling the shader program how to interpret the data it's being fed. The arguments have been explained shortly in the previous WebGL blog post. In the next WebGL blog post we will go into more detail about the arguments in this call.
After modifying a buffer, it is important to reset the bound buffer to null. This behavior emerges because we can only modify a buffer by binding it as the active buffer. If there are multiple buffers of the same type bound, modifications will affect both buffers. Because we use the same buffer twice it is extremely important that we unbind the buffer after the position buffer and before the color buffer. This way modifications to the color buffer will not affect the position buffer. When we combine the full code of the buffer, the result should look like this. This almost finishes the addition of colors to WebGL. We only have to enable all our components and start drawing. The last thing we want to do is binding our defined buffers again, and activating the attribute array. Now that we have defined all our code, positioning of the code is very important. Make sure the shader is created before the buffers because the buffers require a reference to the shader program. It goes without saying that the position, color and index arrays need to be defined before the buffers as well. The drawing code will need to be executed at the bottom. If all parts of the code are placed correctly, the result should look like something like below. Don't worry if you did not manage to get the code right in one go, you can try to read through the blog posts or just the code snippets to see if you can spot a mistake. If that doesn't work out either, an example of the code I used for the result is available below the result.
The Result
* CODE *