Shaders are programs that run on the GPU: a processor that is designed specifically for graphics operations.
In most drawing libraries we draw one shape at a time. With shaders we go through every pixel on the screen and decide what color it should be.
WebGL can be tricky to use because it matches how the GPU works: it can't decide what to do next on its own.
Drawing with WebGL and shaders is a fundamentally different technique from most drawing libraries.
All processors work like a group of pipes.
The GPU is a processor with thousands of small pipes, or threads that work asynchronously.
Individual threads can't know what their neighbors are up to.
WebGL uses a rasterization algorithm to turn our model into pixels on the screen.
It requires that all geometry be composed of triangles.
It removes all triangles that are covered or off-screen.
In our demo, we will have three programs:
Start by downloading the source of this page here.
Follow the instructions in the README to run the page locally.
The changes for each step are in the gray box:
request
from the utils.
To start drawing with our vertex and fragment shaders, we need to create a new WebGL context.
The context object contains all of the methods that we will use to set up the program. I did some of the boring error handling in the utils.
Create a new WebGl context for the canvas element called gl
.
Create new shader objects from the vertex and fragment shader sources, and a program that uses the two shaders.
Clear the canvas with gl.clear
.
We want the vertex shader to iterate over our vertex positions, but we haven't yet given it any data to work with.
We use a vertex buffer to store our vertex positions, and an element buffer to store the order in which to traverse the vertices.
Even though vertex positions are 3-dimensional vectors, both the vertex and element buffers are flat arrays. We will tell WebGL to read the buffers in chunks of three components later.
This won't have any visual effect because we haven't drawn the buffers to the canvas yet.
Create arrays for the vertices and indices called positions
and elements
Create a vertexBuffer
and elementBuffer
, and populate them with the vertex positions and indices.
We have created buffers for our vertex data, now its time to convert the data into pixels in the canvas.
The vertex shader receives the vertex positions from the vertex buffer in an attribute. An attribute is a variable declared in the vertex shader whose memory location can be accessed from the main script.
First we write each element of the vertex buffer to an attribute we declared in the vertex shader.
When the vertex shader is finished processing all of the vertex data, the fragment shader colors in the pixels inside each triangle.
Get the memory location of the vertex position attribute and write the vertex buffer to it.
Run the vertex and fragment shaders by calling gl.drawElements
gl_Position
to return each of the vertices without manipulating them.
gl_FragColor
to render bright blue for every pixel.
The entire canvas is now blue because the edges of the cube line up with the corners of the canvas.
WebGL does not give you a 3d perspective by default; it just ignores the z axis. We need to do some math to project our 3d coordinates into the 2d space of the canvas.
The easiest way to do this is using matrix multiplication. Simply put, matrices perform different transformations on vectors when you multiply them together.
Add a function perspective
that produces a new perspective matrix.
Translate the vertex position away from the origin of the scene using vector addition.
Project the 3d vertex into 2d space by multiplying it by the perspective matrix.
At this point we can only see the front of the cube. Let's try rotating it.
We rotate the model in the same way we performed the perspective projection, with matrix multiplication.
Add a function rotateY
that returns a new rotation matrix.
Rotate the model by multiplying each vertex by the rotation matrix.
We can animate our scene by re-drawing the frame 60 times per second, and passing in a different global time variable for each frame.
WebGL supports passing global variables to shaders with uniforms. Uniforms are just constants that we set before running the vertex and fragment shaders.
Get the memory location of the time uniform in the vertex shader.
Start an animation loop using requestAnimationFrame
.
Write the current time to the uniform each frame.
We can add color to our cube by returning a different color for each pixel in the fragment shader.
We can use a varying to pass information from the vertex shader to the fragment shader. In this case, we give each vertex a color.
The varying will automatically interpolate between vertex values for the given fragment.
v_color
.
v_color
.
This one is just for fun.
Written and edited by Jonas Luebbers.