Context
- Create an
HTML5
canvas - Get the canvas id
- 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 |
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.
// 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
];
Similarly, we can create an array for the indices follow the sequence.
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.
Create an empty buffer.
let vertex_buffer = gl.createBuffer(); let index_buffer = gl.createBuffer();
Bind an appropriate array object to the empty buffer.
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);
Pass the data (vertices/indices) to the buffer using one of the typed arrays.
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);
Unbind the buffer (Optional/Recommended).
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 |
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 |
void main(void) {
gl_FragColor = vec4(0, 0.8, 0, 1);
}
Store and Compiling
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
let vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
Same process for fragment shader
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
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
// 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()
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:
let vertices = [-0.5, -0.5, -0.25, 0.5, 0.0, -0.5];
gl.drawArrays(gl.TRIANGLES, 0, 3);
draw two contiguous triangles:
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);
drawElements()
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
orUNSIGNED_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:
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);
draw two contagious triangles:
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);