Multiple textures in WebGL

The above image is the combination of two images: a red-green checkerboard, and an image of me. This is made with WebGL, where the fragment shader combines two textures:

precision mediump float;
uniform sampler2D checkerboardTexture;
uniform sampler2D jimTexture;
void main(void) {
  vec2 texCoord = vec2(gl_FragCoord.x/200.0, gl_FragCoord.y/200.0);
  gl_FragColor = (texture2D(checkerboardTexture, texCoord) + texture2D(jimTexture, texCoord)) * 0.5;
  gl_FragColor.a = 1.0;

To use this fragment shader, we must tell it which textures to use for each sampler2D. The sampler2D is actually an int which indexes into a “texture units” array. We use texture units 0 and 1:

const checkerboardTextureLoc = gl.getUniformLocation(prog, "checkerboardTexture");
gl.uniform1i(checkerboardTextureLoc, 0);
const jimTextureLoc = gl.getUniformLocation(prog, "jimTexture");
gl.uniform1i(jimTextureLoc, 1);

To use these texture units 0 and 1, we must create new textures and bind them to these indices:

const checkerboardTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, checkerboardTexture);

const jimTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, jimTexture);

The objects checkerboardTexture and jimTexture are WebGLTexture objects. We created them, but they don’t have any image data associated with them yet. For that, we use texImage2D:

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, jimImg);

The object jimImg is an Image, which is loaded from a URL:

const jimImg = new Image();
jimImg.onload = function() {
  const jimTexture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, jimTexture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, jimImg);
  gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
jimImg.src = '/assets/jim_512.jpg';

There are many levels of indirection here! We have an image at /assets/jim_512.jpg, which is loaded into a JS Image, which is copied to a WebGLTexture, which is bound to the texture unit 1, the index of which is copied to the fragment shader’s sampler2D, which is accessed by the shader with texture2D.

Note the oddity in these lines:


What is gl.TEXTURE0? On my machine, it’s 33984. These identifiers go up to gl.TEXTURE31, which is 34015. We use these identifiers in gl.activeTexture, but we do not use them in the shader uniforms!

gl.activeTexture(0);            // WRONG!
gl.activeTexture(gl.TEXTURE0);  // right!

gl.uniform1i(checkerboardTextureLoc, gl.TEXTURE0);  // WRONG!
gl.uniform1i(checkerboardTextureLoc, 0);            // right!
I just released Vidrio, a free app for macOS and Windows to make your screen-sharing awesomely holographic. Vidrio shows your webcam video on your screen, just like a mirror. Then you just share or record your screen with Zoom, QuickTime, or any other app. Vidrio makes your presentations effortlessly engaging, showing your gestures, gazes, and expressions. #1 on Product Hunt. Available for macOS and Windows.

With Vidrio

With generic competitor

More by Jim

Tagged . All content copyright James Fisher 2017. This post is not associated with my employer. Found an error? Edit this page.