webgl

State

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.

When you call gl.enableVertexAttribArray(location) or gl.disableVertexAttribArray you can think of it like this

// pseudo code
gl.enableVertexAttribArray = function(location) {
  gl.vertexArray.attributes[location].enable = true;
};

gl.disableVertexAttribArray = function(location) {
  gl.vertexArray.attributes[location].enable = false;
};

In other words location directly refers to the index of an attribute.

Similarly gl.vertexAttribPointer would be implemented something like this

// 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;
  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

attribute vec4 position;
attribute vec2 texcoord;
attribute vec3 normal;

...

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

var positionLoc = gl.getAttribLocation(program, "position");
var texcoordLoc = gl.getAttribLocation(program, "texcoord");
var normalLoc = gl.getAttribLocation(program, "normal");

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:

// Tell `gl.linkProgram` to assign `position` to use attribute #7
gl.bindAttribLocation(program, 7, "position");

full attribute state

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], },
   ..

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.

Vertex Array Objects

WebGL has an extension, OES_vertex_array_object

In the diagram above OES_vertex_array_object lets you create and replace the vertexArray. In other words

var vao = ext.createVertexArrayOES();

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: ? },
  ],
};

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

uniform sampler2D diffuse;
uniform sampler2D normalMap;
uniform samplerCube environmentMap;

...

For you need to query the uniform locations

var diffuseUniformLocation = gl.getUniformLocation(someProgram, "diffuse");
var normalMapUniformLocation = gl.getUniformLocation(someProgram, "normalMap");
var environmmentMapUniformLocation = gl.getUniformLocation(someProgram,
                                                           "environmentMap");

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.

To actually bind textures to texture units you’d do something like this

gl.activeTexture(gl.TEXTURE0 + diffuseTextureUnit);
gl.bindTexture(gl.TEXTURE_2D, diffuseTexture);
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

gl.texImage2D = function(target, level, internalFormat, width, height, 
                         border, format, type, data) {
   var textureUnit = gl.textureUnits[gl.activeTextureUnit];
   var texture = textureUnit[target];

   // Now that we've looked up the texture form the activeTextureUnit and
   // the target we can effect a specific texture
   ...
};

This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow