Basics Of WebGL (Drawing A Cube)

Helpful documentation

Vertices and Indices

Vertices are points with specific coordinates {x,y,z} in a 3D-space. We can build any figure by connecting vertices in triangles with indices.

Shaders

Shaders are functions, written in C, which describe how to draw and color polygons to Graphic Card.

Vertex Shaders describe vertice positions, so Graphic Card can position them by connecting with indices and project to 2D canvas.

Fragment Shaders describe the way polygons should be colored by assigning colors to Vertices or by applying textures to polygons.

Shaders can have parameters passed from Javascript code (uniforms, varyings and attributes). Fragment Shaders can also access data from Vertex Shaders (that ones called varyings).

Program

Program, as far as I understand, is a scene, that's described with Vertices, Indices, specific Vertex Shaders and Fragment Shaders.

Applying transformations

The best way to change positions inside Vertex Shaders or color in Fragment Shaders is to pass parameters (also called uniforms and varyings).

Read about that at open.gl and at Vertex Shaders.

Source code with explanations

// render-a-cube.tsimport { createShader } from "./create-shader";import vxShader from "./vertex.glsl?raw";import fgShader from "./fragment.glsl?raw";const canvas = document.getElementyId('view');const ctx = canvas.getRenderingContext('webgl');// should be put inside requestAnimationFramedrawCube(ctx)(); function drawCube (  gl: WebGL2RenderingContext,   width: number,  height: number) {  // Initializing viewport  gl.viewport(0, width, height);  gl.clearColor(0, 0, 0);  gl.clear(gl.COLOR_BUFFER_BIT);  const prg = gl.createProgram();  if (!prg) {    throw new Error("Can't init programm");  }  // Setting up VERTEX and FRAGMENT shaders  const vx = createShader(gl, vxShader, gl.VERTEX_SHADER);  gl.attachShader(prg, vx);  const fx = createShader(gl, fgShader, gl.FRAGMENT_SHADER);  gl.attachShader(prg, fx);  gl.linkProgram(prg);  if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {    throw new Error("Could not initialise shaders");  }  // Cube's vertices Array<[x,y,z]>, 8 items  const vertices = [    -1, -1, -1, // 0    1, -1, -1,  // 1    1, 1, -1,   // 2    -1, 1, -1,  // 3    -1, -1, 1,  // 4    1, -1, 1,   // 5    1, 1, 1,    // 6    -1, 1, 1,   // 7  ];  // indices, that form triangles, that form cube sides  const indices = [    2, 1, 0, // side 0 (first triangle)    0, 3, 2, // side 0 (second triangle)    0, 4, 7, // side 1 (first triangle)    7, 3, 0, // side 1 (second triangle)    0, 1, 5, // ...    5, 4, 0,     1, 2, 6,     6, 5, 1,     2, 3, 7,     7, 6, 2,     4, 5, 6,     6, 7, 4,  ];  // createe a vertex buffer and bind vertices to it  const squareVertexBuffer = gl.createBuffer();  gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexBuffer);  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);  gl.bindBuffer(gl.ARRAY_BUFFER, null);  // create a vertex buffer and bind indices to it  const squareIndexBuffer = gl.createBuffer();  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer);  gl.bufferData(    gl.ELEMENT_ARRAY_BUFFER,    new Uint16Array(indices),    gl.STATIC_DRAW  );  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);  // initial drawing  gl.clearColor(0.0, 0.0, 0.0);  gl.enable(gl.DEPTH_TEST);  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);  gl.viewport(0, width, height);  // bind squareVertexBuffer as vertex positions buffer  gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexBuffer);    // send every 3 bytes from squareVertexBuffer as {x,y,z} for each verticle  gl.vertexAttribPointer(      gl.getAttribLocation(prg, "aVertexPosition"),       3, // 3 bytes-long      gl.FLOAT,      false, // don't normalize (int to float)      0,       0  );  // send vertice buffer as `aVertexPosition` attribute inside vertex shader  gl.enableVertexAttribArray(      gl.getAttribLocation(prg, "aVertexPosition")  );  let i = 0;  let speed = 0.01;  // that's the main rendering callback  return () => {    gl.useProgram(prg);    const scale = i *  0.25;    // used for scaling inside Vertex Shader    gl.uniform1f(gl.getUniformLocation(prg, "slide"), scale);    // GL Screen is square, so we need to fix it's aspect ration    gl.uniform1f(gl.getUniformLocation(prg, "aspect"), height / width);    gl.bindBuffer(        gl.ELEMENT_ARRAY_BUFFER,         squareIndexBuffer    );        gl.drawElements(        gl.TRIANGLES,         indices        gl.UNSIGNED_SHORT,         0,    );    if (i > || 0) {      speed = -speed;    }    i += speed;  };};

Shader compiler

// create-shader.tsexport const createShader = (  gl: WebGL2RenderingContext,  sourceCode: string,  type: number, // gl.VERTEX_SHADER or gl.FRAGMENT_SHADER) => {  const shader = gl.createShader(type);  if (!shader) {    throw new Error(`Can't init shader`);  }  gl.shaderSource(shader, sourceCode);  gl.compileShader(shader);  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {    const info = gl.getShaderInfoLog(shader);    throw `Could not compile WebGL program. \n\n${info}`;  }  return shader;};

Vertex Shader Example

Read more at Vertex Shaders

// current vertice position {x,y,z,w}attribute vec4 aVertexPosition;// final vertice position with all transformations applied,// that will be passed to Fragment Shadervarying vec4 v_positionWithOffset;// Parameters passed from Javascript loopuniform float slide;uniform float aspect;void main(){  // float array of 4 elements, same as [slide,slide,slide,1]  vec4 scale=vec4(vec3(slide),1);  // float array of 4 elements, same as [aspect,1,1,1]  vec4 aspectRatioFix=vec4(aspect,vec3(1));  // vertice position, multiplied with matrices of scale and aspect ratio  gl_Position=aVertexPosition*scale*aspectRatioFix,  // vertice offset, that will be passed to fragment shader  v_positionWithOffset=gl_Position+vec4(1,1,1,1);}

Fragment Shader Example

Read more at Fragment Shaders.

precision highp float;// parameter from Vertex Shadervarying vec4 v_positionWithOffset;void main(void){  // color, attached to current verticle {r,g,b,alpha}  // same a[  //   v_positionWithOffset.x,  //   v_positionWithOffset.y,   //   v_positionWithOffset.z,   //   1  //  ]  gl_FragColor=vec4(v_positionWithOffset.xyz,1);}