Webgl
Webgl
#webgl
Table of Contents
About 1
Remarks 2
Examples 3
Installation or Setup 3
Hello World 7
Chapter 2: State 12
Examples 12
Attributes 12
Uniforms 14
Textures 14
Credits 17
About
You can share this PDF with anyone you feel could benefit from it, downloaded the latest version
from: webgl
It is an unofficial and free webgl ebook created for educational purposes. All the content is
extracted from Stack Overflow Documentation, which is written by many hardworking individuals at
Stack Overflow. It is neither affiliated with Stack Overflow nor official webgl.
The content is released under Creative Commons BY-SA, and the list of contributors to each
chapter are provided in the credits section at the end of this book. Images may be copyright of
their respective owners unless otherwise specified. All trademarks and registered trademarks are
the property of their respective company owners.
Use the content presented in this book at your own risk; it is not guaranteed to be correct nor
accurate, please send your feedback and corrections to [email protected]
https://riptutorial.com/ 1
Chapter 1: Getting started with webgl
Remarks
WebGL is a rasterization API that generally runs on your GPU giving you the ability to quickly draw
2D and 3D graphics. WebGL can also be used to do computations on arrays of data.
WebGL is a very low-level API. At a base level WebGL is an engine that runs 2 user supplied
functions on the GPU. One function is called a vertex shader. A vertex shader's job is to compute
vertex positions. Based on the positions the function outputs WebGL can then rasterize various
kinds of primitives including points, lines, or triangles. When rasterizing these primitives it calls a
second user supplied function called a fragment shader. A fragment shader's job is to compute a
color for each pixel of the primitive currently being drawn.
These functions are written in a language called GLSL that is somewhat C/C++ like and strictly
typed.
It's up to the programmer to supply those functions to make WebGL draw 2d, 3d or compute
something. Nearly every piece of WebGL is about setting up those 2 functions and then supplying
data to them.
• Uniforms
Uniforms are inputs to shader functions very much like function parameters or global variables.
They are set once before a shader is executed and remain constant during executing
• Attributes
Attributes supply data to vertex shaders only. Attributes define how to pull data out of buffers. For
example you might put positions, normal, and texture coordinates into a buffer. Attributes let you
tell WebGL how to pull that data out of your buffers and supply them to a vertex shader. A vertex
shader is called a user specified number of times by calling gl.drawArrays or gl.drawElements and
specifying a count. Each time the current vertex shader is called the next set of data will be pulled
from the user specified buffers and put in the attributes
• Textures
Textures are 2D arrays of data up 4 channels. Most commonly those 4 channels are red, green,
blue, and alpha from an image. WebGL doesn't care what the data is though. Unlike attributes and
buffers, shaders can read values from textures with random access.
• Varyings
Varyings are a way for a vertex shader to pass data to a fragment shader. Varyings are
interpolated between the values output by the vertex shader as a primitive is rasterized using a
https://riptutorial.com/ 2
fragment shader
Examples
Installation or Setup
WebGL is a browser technology so there isn't much to set up other than to have a browser. You
can get started with WebGL on JSFiddle or Codepen or JSBIn or any number of other sites that let
you edit HTML, CSS, and JavaScript online though there will be a few limitations (see below). You
can also host open source files on github pages or similar services.
On the other hand at some point you're probably going to work locally. To do that it's
recommended you run a simple web server. There are plenty to choose from that are simple to
use and require very little setup.
1. install node.js
2. Open a terminal or node command prompt and type npm install -g http-server (on OSX put
sudo in front of that.
3. type http-server to start serving the files in the current folder OR http-server path-to-folder
to server a different folder
4. Point your browser to http://localhost:8080/name-of-file to view your WebGL webpage
1. Download devd
2. Open a terminal and run devd with either devd . to server files from the current folder or devd
path-to-folder to serve a different folder
3. Point your browser to http://localhost:8000/name-of-file to view your WebGL webpage
1. Download Servez
2. Install It, Run it
3. Choose the folder to serve
4. Pick "Start"
5. Go to http://localhost:8080 or pick "Launch Browser"
https://riptutorial.com/ 3
Using "Web Server for Chrome" Chrome Extension
https://riptutorial.com/ 4
3. Set the folder where your files are then click the http://127.0.0.1:8787 link
https://riptutorial.com/ 5
Limitation of WebGL on Online Services
In WebGL it is very common to load images. In WebGL there are restrictions on how images can
be used. Specifically WebGL can not use images from other domains without permission from the
server hosting the images. Services that currently give permission to use images include imgur
and flickr. See Loading Cross Domain Images. Otherwise you'll need to have the images on the
same server as your webgl page or use other creative solutions like generating images with a
https://riptutorial.com/ 6
canvas tag
Hello World
Like it mentions in the remarks section we need to supply two functions. A vertex shader and a
fragment shader
If the entire thing was written in JavaScript instead of GLSL you could imagine it would be used
like this
var positionBuffer = [
0, 0, 0, 0,
0, 0.5, 0, 0,
0.7, 0, 0, 0,
];
var attributes = {};
var gl_Position;
void main() {
// gl_FragColor is a special variable a fragment shader
// is responsible for setting
gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple
}
Above we're setting gl_FragColor to 1, 0, 0.5, 1 which is 1 for red, 0 for green, 0.5 for blue, 1 for
https://riptutorial.com/ 7
alpha. Colors in WebGL go from 0 to 1.
Now that we have written the 2 functions lets get started with WebGL
<canvas id="c"></canvas>
var gl = canvas.getContext("webgl");
if (!gl) {
// no webgl for you!
...
Now we need to compile those shaders to put them on the GPU so first we need to get them into
strings. You can get your strings any way you normal get strings. By concatenating, by using
AJAX, by putting them in non-JavaScript typed script tags, or in this case by using multiline
template literals
var vertexShaderSource = `
// an attribute will receive data from a buffer
attribute vec4 position;
var fragmentShaderSource = `
// fragment shaders don't have a default precision so we need
// to pick one. mediump is a good default
precision mediump float;
void main() {
// gl_FragColor is a special variable a fragment shader
// is responsible for setting
gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple
}
`;
Then we need a function that will create a shader, upload the source and compile the shader
https://riptutorial.com/ 8
gl.compileShader(shader);
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
And call it
Now that we've created a GLSL program on the GPU we need to supply data to it. The majority of
the WebGL API is about setting up state to supply data to our GLSL programs. In this case our
only input to our GLSL program is position which is an attribute. The first thing we should do is
look up the location of the attribute for the program we just created
WebGL lets us manipulate many WebGL resources on global bind points. You can think of bind
points as internal global variables inside WebGL. First you set the bind point to your resource.
Then, all other functions refer to the resource through the bind point. So, let's bind the position
buffer.
https://riptutorial.com/ 9
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
Now we can put data in that buffer by referencing it through the bind point
// three 2d points
var positions = [
0, 0,
0, 0.5,
0.7, 0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
There's a lot going on here. The first thing is we have positions which is a JavaScript array.
WebGL on other hand needs strongly typed data so the line new Float32Array(positions) creates a
new array of 32bit floating point numbers and copies the values from positions. gl.bufferData then
copies that data to the positionBuffer on the GPU. It's using the position buffer because we bound
it to the ARRAY_BUFFER bind point above.
The last argument, gl.STATIC_DRAW is a hint to WebGL about how we'll use the data. It can try to
use that info to optimize certain things. gl.STATIC_DRAW tells WebGL we're not likely to change this
data much.
Now that we've put data in the a buffer we need to tell the attribute how to get data out of it. First
off we need to turn the attribute on
gl.enableVertexAttribArray(positionAttributeLocation);
A hidden part of gl.vertexAttribPointer is that it binds the current ARRAY_BUFFER to the attribute. In
other words now that this attribute is bound to positionBuffer we're free to bind something else to
the ARRAY_BUFFER bind point.
note that from the point of view of our GLSL vertex shader the position attribute was a vec4
vec4 is a 4 float value. In JavaScript you could think of it something like position = {x: 0, y: 0, z:
0, w: 0}. Above we set size = 2. Attributes default to 0, 0, 0, 1 so this attribute will get its first 2
values (x and y) from our buffer. The z, and w will be the default 0 and 1 respectively.
After all that we can finally ask WebGL to execute are GLSL program.
https://riptutorial.com/ 10
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 3;
gl.drawArrays(primitiveType, offset, count);
This will execute our vertex shader 3 times. The first time position.x and position.y in our vertex
shader will be set to the first 2 values from the positionBuffer. The 2nd time position.xy will be set
to the 2nd 2 values. The last time it will be set to the last 2 values.
Because we set primitiveType to gl.TRIANGLES, each time our vertex shader is run 3 times WebGL
will draw a triangle based on the 3 values we set gl_Position to. No matter what size our canvas is
those values are in clip space coordinates that go from -1 to 1 in each direction.
Because our vertex shader is simply copying our positionBuffer values to gl_Position the triangle
will be drawn at clip space coordinates
0, 0,
0, 0.5,
0.7, 0,
How those values translate to pixels depends on the gl.viewport setting. gl.viewport defaults to
the initial size of the canvas. Since we didn't set a size for our canvas it's the default size of
300x150. Converting from clip space to pixels (often called screen space in WebGL and OpenGL
literature) WebGL is going to draw a triangle at
WebGL will now render that triangle. For every pixel it is about to draw it will call our fragment
shader. Our fragment shader just sets gl_FragColor to 1, 0, 0.5, 1. Since the Canvas is an 8bit
per channel canvas that means WebGL is going to write the values [255, 0, 127, 255] into the
canvas.
There are 3 major things we still haven't covered from the remarks. Textures, varyings, and
uniforms. Each of those requires it's own topic.
https://riptutorial.com/ 11
Chapter 2: State
Examples
Attributes
Attributes are global state (*). If they were implemented in JavaScript they would look something
like this
// pseudo code
gl = {
ARRAY_BUFFER: null,
vertexArray: {
attributes: [
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
],
ELEMENT_ARRAY_BUFFER: null,
},
}
As you can see above there are 8 attributes and they are global state.
// pseudo code
gl.enableVertexAttribArray = function(location) {
gl.vertexArray.attributes[location].enable = true;
};
gl.disableVertexAttribArray = function(location) {
gl.vertexArray.attributes[location].enable = false;
};
// pseudo code
gl.vertexAttribPointer = function(location, size, type, normalize, stride, offset) {
var attrib = gl.vertexArray.attributes[location];
attrib.size = size;
attrib.type = type;
attrib.normalize = normalize;
attrib.stride = stride ? stride : sizeof(type) * size;
https://riptutorial.com/ 12
attrib.offset = offset;
attrib.buffer = gl.ARRAY_BUFFER; // !!!! <-----
};
Notice that attrib.buffer is set to whatever the current gl.ARRAY_BUFFER is set to. gl.ARRAY_BUFFER is
set by calling gl.bindBuffer(gl.ARRAY_BUFFER, someBuffer).
So, next up we have vertex shaders. In vertex shader you declare attributes. Example
...
void main() {
...
}
When you link a vertex shader with a fragment shader by calling gl.linkProgram(someProgram)
WebGL (the driver/GPU/browser) decide on their own which index/location to use for each
attribute. You have no idea which ones they're going to pick. It's up the the browser/driver/GPU.
So, you have to ask it which attribute did you use for position, texcoord and normal?. You do this by
calling gl.getAttribLocation
Let's say positionLoc = 5. That means when the vertex shader executes (when you call
gl.drawArrays or gl.drawElements) the vertex shader expects you to have setup attribute 5 with the
correct type, size, offset, stride, buffer etc.
Note that BEFORE you link the program you can choose the locations by calling
gl.bindAttribLoction(program, location, nameOfAttribute). Example:
Missing from the description above is that each attribute also has a default value. It is left out
above because it is uncommon to use it.
attributes: [
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?
value: [0, 0, 0, 1], },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?
value: [0, 0, 0, 1], },
..
https://riptutorial.com/ 13
You can set the value with the various gl.vertexAttribXXX functions. The value is used when enable
is false. When enable is true data for the attribute is pulled from the assigned buffer.
In the diagram above OES_vertex_array_object lets you create and replace the vertexArray. In other
words
creates the object you see attached to gl.vertexArray in the pseudo code above. Calling
ext.bindVertexArrayOES(vao) assign your created vertex array object as the current vertex array.
// pseudo code
ext.bindVertexArrayOES = function(vao) {
gl.vertexArray = vao;
}
This lets you set all of the attributes and ELEMENT_ARRAY_BUFFER in the current VAO so that when you
want to draw it's one call to ext.bindVertexArrayOES where as without the extension it would be up
to one call to both gl.bindBuffer gl.vertexAttribPointer (and possibly gl.enableVertexAttribArray)
per attribute.
Uniforms
Uniforms are per program state. Every shader program has its own uniform state and its own
locations.
Textures
Texture units are global state. If they were implemented in JavaScript they would look something
like this
// pseudo code
gl = {
activeTextureUnit: 0,
textureUnits: [
{ TEXTURE_2D: ?, TEXTURE_CUBE_MAP: ? },
{ TEXTURE_2D: ?, TEXTURE_CUBE_MAP: ? },
{ TEXTURE_2D: ?, TEXTURE_CUBE_MAP: ? },
{ TEXTURE_2D: ?, TEXTURE_CUBE_MAP: ? },
{ TEXTURE_2D: ?, TEXTURE_CUBE_MAP: ? },
{ TEXTURE_2D: ?, TEXTURE_CUBE_MAP: ? },
{ TEXTURE_2D: ?, TEXTURE_CUBE_MAP: ? },
{ TEXTURE_2D: ?, TEXTURE_CUBE_MAP: ? },
{ TEXTURE_2D: ?, TEXTURE_CUBE_MAP: ? },
],
};
https://riptutorial.com/ 14
You can choose which unit to index with gl.activeTexture.
// pseudo code
gl.activeTexture = function(textureUnit) {
gl.activeTextureUnit = textureUnit - gl.TEXTURE0;
};
Calling gl.bindTexture binds a texture to the active texture unit like this
// pseudo code
gl.bindTexture = function(target, texture) {
var textureUnit = gl.textureUnits[gl.activeTextureUnit];
textureUnit[target] = texture;
}
When you have a shader program that uses textures you have to tell that shader program which
texture units you bound the textures to. For example if you have a shader like this
...
Then, after you've made your shader program the current program
gl.useProgram(someProgram);
You then need to tell the shader which texture units you did/will put the textures on. For example
var diffuseTextureUnit = 3;
var normalMapTextureUnit = 5;
var environmentMapTextureUnit = 2;
gl.uniform1i(diffuseUniformLocation, diffuseTextureUnit);
gl.uniform1i(normalMapUniformLocation, normalMapTextureUnit);
gl.uniform1i(environmentMapUniformLocation, environmentMapTextureUnit);
Now you told the shader which units you did/will use. Which texture units you decide to use is
entirely up to you.
gl.activeTexture(gl.TEXTURE0 + diffuseTextureUnit);
gl.bindTexture(gl.TEXTURE_2D, diffuseTexture);
https://riptutorial.com/ 15
gl.activeTexture(gl.TEXTURE0 + normalMapTextureUnit);
gl.bindTexture(gl.TEXTURE_2D, normalMapTexture);
gl.activeTexture(gl.TEXTURE0 + environmentMapTextureUnit);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, environmentMapTexture);
For very simple WebGL examples that only use 1 texture it's common to never call
gl.activeTexture since it defaults to texture unit #0. It's also common not to call gl.uniform1i
because uniforms default to 0 so the shader program, will by default, use texture unit #0 for all
textures.
All other texture functions also work off the active texture and texture unit targets. For example
gl.texImage2D might look something like this
// Now that we've looked up the texture form the activeTextureUnit and
// the target we can effect a specific texture
...
};
https://riptutorial.com/ 16
Credits
S.
Chapters Contributors
No
https://riptutorial.com/ 17