目录

WebGL Samples Explanation


Context

  1. Create an HTML5 canvas
  2. Get the canvas id
  3. Obtain WebGL Context

The parameter WebGLContextAttributes is not mandatory.

Attributes Description Default value
alpha true: provide an alpha buffer to the canvas; true
depth true: drawing buffer contains a depth buffer of at least 16 bits; true
stencil true: drawing buffer contains a stencil buffer of at least 8 bits; false
antialias true: drawing buffer performs anti-aliasing true
premultipliedAlpha true: drawing buffer contains colors with pre-multiplied alpha true
preserveDrawingBuffer true: buffers will not be cleared and will preserve their values until cleared or overwritten by the author false
1
2
let canvas = document.getElementById("my_canvas");
let context = canvas.getContext("webgl", { antialias: false, stencil: true });

Geometry

Definition

A 2D or 3D model drawn using vertices is call a mesh.

Each facet in a mesh is called a polygon and a polygon is made of 3 or more vertices.

1
2
3
4
5
6
// create a 2D triangle which lies on the coordinates {(-5, -5), (5, -5), (5, 5)}
let vertices = [
    -0.5, -0.5   // Vertex 0
    0.5, -0.5,   // Vertex 1
    0.5, 0.5,    // Vertex 2
];

https://raw.githubusercontent.com/ayamir/blog-imgs/main/geometry.jpg

Similarly, we can create an array for the indices follow the sequence.

1
let indices = [0, 1, 2];
  • drawArrays(): pass the vertices of the primitive using JavaScript arrays.
  • drawElements(): pass both vertices and indices of the primitive using JavaScript arrays.

Buffer Objects

A buffer object indicates a memory area allocated in GPU.

We can store data of the models corresponding to vertices, indices, color and etc.

There are 2 types of buffer objects:

  • Vertex Buffer Object(VBO): It holds the per-vertex data of the graphical model that is going to be rendered.
  • Index Buffer Object(IBO): It holds the indices of the graphical model that is going to be rendered.

After defining the required geometry and storing them in JavaScript arrays, we need to pass these arrays to the buffer objects, from where the data will be passed to the shader programs.

  1. Create an empty buffer.

    1
    2
    
    let vertex_buffer = gl.createBuffer();
    let index_buffer = gl.createBuffer();
    
  2. Bind an appropriate array object to the empty buffer.

    1
    2
    3
    4
    5
    6
    
    void bindBuffer(enum target, Object buffer);
    
    // ARRAY_BUFFER represents vertex data
    gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
    // ELEMENT_ARRAY_BUFFER represent index data
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
    
  3. Pass the data (vertices/indices) to the buffer using one of the typed arrays.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    void bufferData(enum target, Object data, enum usage);
    // Usage specifies how to use the buffer object data to draw shapes
    // gl.STATIC_DRAW -- Data will be specified once and used many times.
    // gl.STREAM_DRAW -- Data will be specified once and used a few times.
    // gl.DYNAMIC_DRAW -- Data will be specified repeatedly and used many times.
    
    // vertex buffer
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    // index buffer
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
    
  4. Unbind the buffer (Optional/Recommended).

    1
    
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    

Shader

Shaders are written in ES SL which has variables of its own data types, qualifiers, built-in inputs and outputs.

Data Types

Type Description
void empty value
bool true or false
int signed integer
float floating scalar
vec2, vec3, vec4 n-component floating point vector
bvec2, bvec3, bvec4 boolean vector
ivec2, ivec3, ivec4 signed integer vector
mat2, mat3, mat4 2x2, 3x3, 4x4 float matrix
sampler2D access a 2D texture
samplerCube access cube mapped texture

Qualifiers

Qualifier Description
attribute acts as a link between a vertex shader and OpenGL ES for per-vertex data. Its value changes for every execution of the vertex shader.
uniform links shader programs and the WebGL application. Its value is read-only. It can be used for to declare a variable with any basic data types: uniform vec4 lightPosition;.
varying forms a link between a vertex shader and fragment shader for interpolated data. It can be used with the following data types: float, vec2, vec3, vec4, mat2, mat3, mat4, arrays like: varying vec3 normal;

Vertex Shader

Vertex shader is a program code, which is called on every vertex. Programmer have to define attribute in code of vertex shader to handle data. The attribute point to a VBO written in JavaScript.

Predefined Variables

OpenGL ES SL provides the following predefined variables for every vertex shader

Variables Description
highp vec4 gl_Position Holds the position of the vertex
mediump float gl_PointSize Holds the transformed point size
1
2
3
4
5
attribute vec2 coordinates;

void main(void) {
    gl_Position = vec4(coordinates, 0.0, 1.0);
}

gl_Position is the predefined variable which is available only in the vertex shader. It contains the vertex position. As vertex shader is a per-vertex operation, the gl_Position value is calculated for each vertex.

Fragment Shader

A mesh is formed by multiple triangles, and the surface of each triangle is known as a fragment.

Fragment shader is the code that runs on every pixel on each fragment. This is written to calculate and fill the color on individual pixels.

Predefined Variables

Variables Description
mediump vec4 gl_FragCoord; Holds the fragment position within the frame buffer
bool gl_FrontFacing; Holds the fragment that belongs to a front-facing primitive
mediump vec2 gl_PointCoord; Holds the fragment position within a point
mediump vec4 gp_FragColor; Holds the output fragment color value of the shader
mediump vec4 gl_FragData[n]; Holds the fragment color for color attachment n
1
2
3
void main(void) {
    gl_FragColor = vec4(0, 0.8, 0, 1);
}

Store and Compiling

1
2
3
4
5
6
7
let vertCode =
    "attribute vec2 coordinates;" +
    "void main(void) {" +
    "gl_Postion = vec4(coordinates, 0.0, 1.0);" +
    "}";

let fragCode = "void main(void) {" + "gl_FragColor = vec4(0, 0.8, 0, 1);" + "}";

Compilation involves following 3 steps

  • Creating the shader object
  • Attaching the source code to the created shader object
  • Compiling the program
1
2
3
let vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);

Same process for fragment shader

1
2
3
let fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);

Combined Program

  • Create a program object
  • Attach both the shaders
  • Link both the shaders
  • Use the program
1
2
3
4
5
let shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertShader);
gl.attachShader(shaderProgram, fragShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);

Associating Attributes & Buffer Objects

  • Get the attribute location
  • Point the attributes to a vertex buffer object
  • Enable the attribute
1
2
3
4
5
6
7
// ulong getAttribLocation(Object program, string name)
let coordinatesVar = gl.getAttribLocation(shaderProgram, "coordinates");

// void vertexAttribPointer(location, int size, enum type, bool normalized, long stride, long offset)
gl.vertexAttribPointer(coordinatesVar, 3, gl.FLOAT, false, 0, 0);

gl.enableVertexAttribArray(coordinatesVar);

Drawing a Model

drawArrays()

1
void drawArrays(enum mode, int first, long count);
  • mode: gl.POINTS, gl.LINE_STRIP, gl.LINE_LOOP, gl.LINES, gl.TRIANGLE_STRIP, gl.TRANGLE_FAN, gl.TRIANGLES.
  • first: specified the starting element in the enabled arrays. (Non-negative)
  • count: specifies the number of elements to be rendered.

WebGL will create the geometry in the order in which the vertex coordinates while rendering the shapes.

draw a triangle:

1
2
let vertices = [-0.5, -0.5, -0.25, 0.5, 0.0, -0.5];
gl.drawArrays(gl.TRIANGLES, 0, 3);

https://raw.githubusercontent.com/ayamir/blog-imgs/main/triangle.jpg

draw two contiguous triangles:

1
2
3
4
let vertices = [
    -0.5, -0.5, -0.25, 0.5, 0.0, -0.5, 0.0, -0.5, 0.25, 0.5, 0.5, -0.5
];
gl.drawArrays(gl.TRIANGLES, 0, 6);

https://raw.githubusercontent.com/ayamir/blog-imgs/main/triangles_1.jpg

drawElements()

1
void drawElements(enum mode, long count, enum type, long offset);
  • mode: same as drawArrays();
  • count: same as drawArrays();
  • type: specifies the data type of the indices which must be UNSIGNED_BYTE or UNSIGNED_SHORT;
  • offset: specifies the starting point for rendering, usually the first element (0);

If use drawElements() to draw a model, then index buffer object should also be created along with the vertex buffer object. The vertex data will be processed once and used as many time as mentioned in the indices.

draw a triangle:

1
2
3
4
let vertices = [-0.5, -0.5, 0.0, -0.25, 0.5, 0.0, 0.0, -0.5, 0.0];
let indices = [0, 1, 2];

gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

https://raw.githubusercontent.com/ayamir/blog-imgs/main/triangle-16462062700213.jpg

draw two contagious triangles:

1
2
3
4
5
6
7
let vertices = [
    -0.5, -0.5, 0.0, -0.25, 0.5, 0.0, 0.0, -0.5, 0.0, 0.25, 0.5, 0.0, 0.5, -0.5,
    0.0
];
let indices = [0, 1, 2, 2, 3, 4];

gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

https://raw.githubusercontent.com/ayamir/blog-imgs/main/triangles_1-16462064099025.jpg