Understanding the WebGL triangle
Tags: javascript, webgl, webdev
Published: 2021-01-23 20:57:41

Let's try to understand the Web/OpenGL triangle - aka OpenGL Hello World! We will use WebGL for simplicity, but the principles are quite similar to OpenGL.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/l2tl903zdcww8t8meldq.png)

# YouTube Version
{% youtube 18rTdbE2qS4 %}

# The END
Let's start from the end. If you haven't heard it by now, OpenGL is a "state machine", practically what it means is that instead of calling to a function (like `drawTriangle(...)`) we will first have to setup parameters on the gl context (as if we adjust knobs on a machine), and then when the "state" is all set for our task, we will call the `draw` method.

## The draw method
The method we will use for drawing is `drawArrays(mode, first, count)`, from the [docs](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawArrays) it "renders primitives from array data".

Parameters:
* mode - The **primitive** type to draw. We learn from this that OpenGL can draw specific primitives, like: `POINTS`, `LINES`, `TRIANGLES` (and some more). **we will use TRIANGLES**.
* first - The starting index in the array of vector points. From this we learn we will have to setup some "array with vector" points in our "machine". Specifically these will be array with the corners of our triangle.
* count - Number of indices to be rendered (from the above array). Since we are rendering a triangle we will need **3 indices** (representing 3 vertices).

OK, so now that we know what is required for the draw method, we an go ahead and see how to set it all up...

# OpenGL rendering pipeline
The rendering pipeline is the process of rendering an image to the screen (or any output surface), below is a very simplistic schema of the rendering pipeline:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nun6s6wsit5ov4nkmttf.png)

1. It all starts with vertices we provided that represent the primitive we want to draw. Note that the vertices can be in any coordinate system we want, in the image above we choose the origin to be (0, 0) in a normalized space (all coords in range 0 - 1).
1. These vertices are then passed to the `vertex shader`, which is a program that **we write** and will run **on the GPU** for every vertex we provide (meaning 3 times in our case). The purpose of this method is to transform each vertex to OpenGL coordinate system, which is a normalized coordinate system with the origin at **(-1, -1)** (of course you can also choose to do extra manipulation on the vertex, like flip or rotate etc).
1. The next step is Rasterization - in this step OpenGL will "collect" all the *fragments* (pixels) that needs to be rendered.
1. All these fragments (pixels) are now processed with the `fragment shader`, which is a program **we write** and will run **on the GPU** <u>for every fragment</u>, i.e A LOT, so be careful here performance wise. The purpose of this program is to assign a **color** for every fragment (pixel).
1. And then the pipeline continue with preparing the final image to draw.

# The shaders programs
As described above, the vertex/fragments programs will run on the GPU and the language in which we develop is GLSL (OpenGL Shading Language). The specific version we are going to use is `ES 3.0` (ES is a flavour for Embedded Systems), full reference can be found [here](https://www.khronos.org/registry/OpenGL-Refpages/es3.0/). Overall the syntax resembles c++.

## Vertex Shader
As described above, the vertex program needs to transform each vertex (x, y) to OpenGL coord system, now it is totally up to us to choose whatever coord system WE want to use. If we choose the same coord system as OpenGL our vertex shader will have to do nothing but echo the input. But, having origin at (-1, -1) is not intuitive so we will work with origin at (0, 0) and our triangle coords will be:

```
   (0.5, 1)
  +---/\---+
  |  /  \  |
  | /    \ |
  |/      \|
  +--------+
(0,0)     (1, 0)
```

### Color interpolation
Another useful feature of the vertex shader, is that any **output** variable we define will get interpolated and passed on to the fragment shader. So if for the first corner (vertex) of our triangle we output the color red (rgb: 1,0,0), and for the second corner we output blue (rgb: 0,0,1), all the fragments (pixels) in between will get values between these two values.

**Note:** The shaders code is defined as a simple string, we will compile it later using WebGL context.

### Vertex shader code
This is our vertex shader, code is commented with explanations:
```js
const VERTEX_SHADER = `#version 300 es
  // Below are 2 input attributes
  // The values for these attributes will be provided
  // by us in the javascript "world"

  // vec2 is a vector with 2 values representing the
  // vertex coordinates (x/y).
  in vec2 a_position;

  // vec3 is a vector with 3 values representing the
  // vertex color (r/g/b).
  in vec3 a_color;

  // output color for this vertex,
  // OpenGL will interpolate these values automatically across all fragments
  out vec3 color;

  void main() {
    // convert coord from [0,1] space to [0,2] space
    vec2 zeroToTwo = a_position * 2.0;

    // convert coord from [0,2] space to [-1,1]
    vec2 glCoordSpace = zeroToTwo - 1.0;

    // set the output in the global predefined gl_Position variable.
    // Note a vertex has 4 values: x,y,z,w - we use 0,1 for z,w.
    gl_Position = vec4(glCoordSpace, 0, 1);

    // set the output color variable, just provide the user input
    color = a_color;
  }
`;
```

## Fragment Shader
The fragment shader just have to assign an `rgba` value for each pixel, quite simple:
```js
const FRAG_SHADER = `#version 300 es
  // Need to the the engine what float precision we want
  precision highp float;

  // input color for this fragment, provided by OpenGL
  // interpolating the output color variable that is in the
  // vertex shader.
  in vec3 color;

  // We should set this output param with the desired
  // fragment color
  out vec4 outColor;

  void main() {
    // just output the color we got, note we got vec3 (rgb)
	// and outColor is vec4, so we use 1.0 for the alpha.
    outColor = vec4(color, 1.0);
  }
`;
```

# Creating the GL "executable" program
OK, in order to use WebGL we need a "gl" context, that we can get from a `canvas` element (onto which the output will be rendered as well):
```html
<canvas id="canvas" width="300" height="300">
```

Then we can get the "gl" context:
```js
var canvas = document.getElementById('canvas');
// get webgl2 context
var gl = canvas.getContext('webgl2');
```

### Compile the shaders
Next we compile the shaders code:
```js
// Create shader object of type "VERTEX"
const vertexShader = gl.createShader(gl.VERTEX_SHADER);

// Load the shader source
gl.shaderSource(vertexShader, VERTEX_SHADER);

// Compile the shader
gl.compileShader(vertexShader);

// Check the compile status
var compiled = gl.getShaderParameter(vertexShader, gl.  COMPILE_STATUS);
if (!compiled) {
  // Something went wrong during compilation; get the error
  const lastError = gl.getShaderInfoLog(shader);
  throw new Error('Error compiling shader: ' + lastError);
}

// Do the same for the fragment shader
const fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, FRAG_SHADER);
gl.compileShader(fragShader);
// here we should check compilation status as well
```

### Link the gl program
Like any executable, our program should get linked too:
```js
// create program object
var program = gl.createProgram();

// attach it with the 2 shaders
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragShader);

// and link
gl.linkProgram(program);

// Check the link status
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
  // something went wrong with the link
  const lastError = gl.getProgramInfoLog(program);
  throw new Error('Error linking gl program:', lastError);
}
```

Yay, we have a GL program ready to Run!

# Loading data to the vertex shader
In the sections above we said that we will provide the vertex shaders with the vertices to render, now is the time.

## The vertex input buffer
If you look at our vertex shader you'll see it has 2 input attributes, `a_position` and `a_color`, let define the data for each.

First we create a "buffer", which is just a chunk of memory **on the GPU**:
```js
var vertexParamsBuffer = gl.createBuffer();
```

Then we should "bind" it, binding in OpenGL is like making something "active". Remember we said that OpenGL is a "state machine"? well, think of it as a "machine" with many slots each for a specific usage. So when we "bind" something it's as if we load an object into that dedicated slot. Here we bind our `vertexParamsBuffer` to the `ARRAY_BUFFER` slot.

From the [docs](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindBuffer), the `ARRAY_BUFFER` is "Buffer containing vertex attributes, such as vertex coordinates ... or vertex color data" - PERFECT!
```js
gl.bindBuffer(gl.ARRAY_BUFFER, vertexParamsBuffer);
```

Once our buffer is in the correct `ARRAY_BUFFER` "slot", we can load it with data (this copies bytes to the GPU):
```js
var verticesData = new Float32Array([
  // coord      color
    0.0, 0.0,  1.0, 0.0, 0.0, // 1st vertex
    0.5, 1.0,  0.0, 1.0, 0.0, // 2nd vertex
    1.0, 0.0,  0.0, 0.0, 1.0  // 3ed vertex
]);

// First parameter is the "slot" onto which load the data
// Second parameter is the chunk of bytes
// Third parameter is a hint to OpenGL how this data used
gl.bufferData(gl.ARRAY_BUFFER, verticesData, gl.STATIC_DRAW);
```

<u>**IMPORTANT!**:</u> Above we just uploaded a chunk of bytes into a buffer on the GPU, OpenGL still knows nothing about the **meaning** of the data, what we commented above as "1st vertex coord/color" etc is just for us, next we will tell OpenGL how to interpret the data.

## Describing the buffer data
So the data we uploaded above should go into our 2 attributes, `a_position` and `a_color`, lets start with the first:
```js
// Gets the index of the a_position input attribute
var a_positionIdx = gl.getAttribLocation(program, 'a_position');

// Fact that we declared "in vec2 a_position" attribute
// in our vertex shader doesn't mean it is "active",
// we have to enable it specifically:
gl.enableVertexAttribArray(a_positionIdx);

// And now we tell OpenGL how to read data from the buffer
// CURRENTLY bound to the ARRAY_BUFFER "slot" into the
// vertex attribute a_position
gl.vertexAttribPointer(a_positionIdx, 2, gl.FLOAT, false, 20, 0);
```

Let's [study](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer) the parameters of `vertexAttribPointer`:
* index - The index of the vertex (shader) attribute we describe
* size - The number of **components** for this attribute. since our `a_position` is a vec2, we use 2 **components** (that is, 2 float values).
* type - The data type for each component, we used a `Float32Array` so it is FLOAT.
* normalized - whether the input is a normalized int - FALSE
* stride - Specify the number of **bytes** between the <u>beginning</u> of each vertex data. In our case EACH vertex has 2 floats for x/y, and 3 floats for r/g/b => total of 5 float == 20 bytes - so from the beginning of a specific vertex data, jump 20 bytes and you get to the beginning of the next vertex data.
* offset - Specify the number of **bytes**, from the beginning of the vertex data, where the first component is. In our case the x/y components are at the beginning of the vertex data, so offset is 0.

Now we do the same to describe the color attribute:
```js
var a_colorIdx = gl.getAttribLocation(program, 'a_color');
gl.enableVertexAttribArray(a_colorIdx);
gl.vertexAttribPointer(a_colorIdx, 3, gl.FLOAT, false, 20, 8);
```

Lets describe the parameters here as well:
* index - as above
* size - `a_color` is a vec3 so it has 3 components
* type - FLOAT
* stride - like above
* offset - From the beginning of the vertex data we count 2 float of x/y and then it's the color data, so 8 bytes.

## Rendering
Once we setup all the vertex attributes we can ask OpenGL to draw!
```js
// First we set our program as the active program on the gpu
gl.useProgram(program);

// Call the draw method
gl.drawArrays(gl.TRIANGLES, 0, 3);
```

This takes us back to the beginning of our article, the `drawArrays` method, lets see if we understand now the parameters:
* mode - Easy, we want to draw a triangle.
* first - The starting index in the array of vector points. Remember our `ARRAY_BUFFER` slot? into which we loaded our vertices data? the data we described using `vertexAttribPointer` and the actual chunk of data we provided (the `verticesData` Float32Array) creates exactly 3 vertices in the `ARRAY_BUFFER` slot, and the index of the first vertex we want to draw is 0 !
* count - Easy, we want to draw 3 vertices.

# THE END

{% codepen https://codepen.io/ValYouW/pen/KKgLbeQ %}