webgl State Textures


Example

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
   ...
};