Introduction to Raw WebGL

Claude Ando
4 min readDec 17, 2022

--

Let’s understand low-level technology.

WebGL (Web Graphics Library) is a JavaScript API that enables 2D and 3D graphics in the browser. As developers, you might have played around with libraries such as three.js.

Today, we will have a look at behind the scenes of three.js in order for us to have more insights into three.js.

Here is how you write Raw WebGL:

  1. Declare canvas element in the HTML
  2. Initialisation process
  3. Compilation of shaders
  4. Generation of shaders and program objects
  5. Generation and notification of the vertex buffer ( VBO )
  6. Creation and notification of coordinate transformation matrices
  7. Issuing drawing commands
  8. Updating and rendering the canvas

1. Declare canvas element.

// index.html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Introduction to WebGL</title>
<script src="script.js" type="text/javascript"></script>
<script src="https://wgld.org/j/minMatrix.js" type="text/javascript"></script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
// style.css

body {
display: flex;
justify-content: center;
align-items: center;
}

Alternatively, the canvas element can be declared on the JavaScript side.

2. Initialisation.

// script.js

// get canvas element from HTML
const canvas = document.getElementById('canvas');
canvas.width = 650;
canvas.height = 650;

// get webgl context
const gl = canvas.getContext('webgl');

// reset canvas
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

canvas.getContext(‘webgl’) look like 2d canvas configuration.

3. Create Shaders

There are two main types of shaders that play a role in depicting a 3D environment.

  1. Vertex Shader: determines where the vertices are.
  2. Fragment Shader: paints the area defined by vertices.

The order in which they are written is fixed, from Vertex Shader to Fragment Shader as fragment shader requires vertices information to draw or paint colors.x]

The content of the shader (GLSL) is written on the HTML side.

Vertex Shader

Vertex shader comes with three types of variables.

  • attribute variable: Different information is set for each vertex.
  • uniform variable: All vertices are processed together.
  • varying variable: Passing information from the vertex shader to the fragment shader.

Regarding the attribute, if you log three.js geometry into the console, you will see there is an attribute section. Three.js geometry comes with default attributes of position, UV, and normal.

const geo = new THREE.BoxGeometry(1, 1, 1)
const mat = new THREE.MeshNormalMaterial()
const mesh = new THREE.Mesh(geo, mat)

console.log(geo)

Fragment Shader

precision medium specifies the resolution/precision of the drawing.

  • lowp: Low precision
  • mediump: Medium precision
  • highp : High precision

By using the varying variable on the fragment shader side, the colour information can be passed from the vertex shader to the fragment shader. The colour information is passed from the vertex shader to the fragment shader.

Finally, the colour information is specified with gl_FragColour to complete the process.

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Introduction to WebGL</title>
<script src="script.js" type="text/javascript"></script>
<script src="https://wgld.org/j/minMatrix.js" type="text/javascript"></script>

<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
uniform mat4 mvpMatrix;
varying vec4 vColor;

void main(void){
vColor = color;
gl_Position = mvpMatrix * vec4(position, 1.0);
}
</script>


<script id="fs" type="x-shader/x-fragment">
precision mediump float;

varying vec4 vColor;

void main(void){
gl_FragColor = vColor;
}
</script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>

4. Create Shader Programme Object.

// script.js

// create vertex and fragment shader
const vs= create_shader('vs');
const fs= create_shader('fs');

// create programme object and link them
const prg = create_program(vs, fs);

// get attributeLocation in an array
const attLocation = new Array(2);
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'color');

// store attribute number in an array
const attStride = new Array(2);
attStride[0] = 3;
attStride[1] = 4;

The entire code:

// index.html 

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>wgld.org WebGL sample 003</title>
<script src="script.js" type="text/javascript"></script>
<script src="https://wgld.org/j/minMatrix.js" type="text/javascript"></script>

<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
uniform mat4 mvpMatrix;
varying vec4 vColor;

void main(void){
gl_Position = mvpMatrix * vec4(position, 1.0);
vColor = color;
}
</script>

<script id="fs" type="x-shader/x-fragment">
precision mediump float;

varying vec4 vColor;

void main(void){
gl_FragColor = vColor;
}
</script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>

This also

// script.js

window.addEventListener('load', init)

function init() {
const canvas = document.getElementById('canvas')
canvas.width = 650
canvas.height = 650

const gl = c.getContext('webgl') || c.getContext('experimental-webgl')

gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clearDepth(1.0)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

// create vertex shader and fragment shader
const v_shader = create_shader('vs')
const f_shader = create_shader('fs')

const prg = create_program(v_shader, f_shader)

const attLocation = new Array(2)
attLocation[0] = gl.getAttribLocation(prg, 'position')
attLocation[1] = gl.getAttribLocation(prg, 'color')

const attStride = new Array(2)
attStride[0] = 3
attStride[1] = 4

const vertex_position = [
0.0, 1.0, 0.0,
1.0, 0.0, 0.0,
-1.0, 0.0, 0.0
]

const vertex_color = [
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0
]

const position_vbo = create_vbo(vertex_position)
const color_vbo = create_vbo(vertex_color)

gl.bindBuffer(gl.ARRAY_BUFFER, position_vbo)
gl.enableVertexAttribArray(attLocation[0])
gl.vertexAttribPointer(attLocation[0], attStride[0], gl.FLOAT, false, 0, 0)

gl.bindBuffer(gl.ARRAY_BUFFER, color_vbo)
gl.enableVertexAttribArray(attLocation[1])
gl.vertexAttribPointer(attLocation[1], attStride[1], gl.FLOAT, false, 0, 0)

const m = new matIV()

const mMatrix = m.identity(m.create())
const vMatrix = m.identity(m.create())
const pMatrix = m.identity(m.create())
const mvpMatrix = m.identity(m.create())

m.lookAt([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0], vMatrix)
m.perspective(90, canvas.width / canvas.height, 0.1, 100, pMatrix)
m.multiply(pMatrix, vMatrix, mvpMatrix)
m.multiply(mvpMatrix, mMatrix, mvpMatrix)

const uniLocation = gl.getUniformLocation(prg, 'mvpMatrix')

gl.uniformMatrix4fv(uniLocation, false, mvpMatrix)
gl.drawArrays(gl.TRIANGLES, 0, 3)
gl.flush()

// create shader function
function create_shader(id) {
let shader

const scriptElement = document.getElementById(id)

if (!scriptElement) { return; }

switch (scriptElement.type) {

case 'x-shader/x-vertex':
shader = gl.createShader(gl.VERTEX_SHADER)
break

case 'x-shader/x-fragment'
shader = gl.createShader(gl.FRAGMENT_SHADER)
break
default:
return
}

gl.shaderSource(shader, scriptElement.text)
gl.compileShader(shader)

if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
return shader
} else {
alert(gl.getShaderInfoLog(shader))
}
}

// create program object and link shaders
function create_program(vs, fs) {
const program = gl.createProgram()

gl.attachShader(program, vs)
gl.attachShader(program, fs)
gl.linkProgram(program)

if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
gl.useProgram(program)
return program
} else {
alert(gl.getProgramInfoLog(program))
}
}

function create_vbo(data) {
const vbo = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, vbo)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW)
gl.bindBuffer(gl.ARRAY_BUFFER, null)
return vbo
}

};

Well, it has been a long journey to just picture a triangle. My simple conclusion is “just rely on three.js”. After this raw WebGL, I think you are able to appreciate how three.js is simple. Hope you took away something from this article.

Have a good day :))

--

--

Claude Ando

Entrepreneur - 6 figure agency business / investment / live in hotels somewhere in 🌏