Text in OpenGL and WebGL

Hi All,

I have tried to show some words in OpenGL and WebGL. It seems I can use the function “glutBitmapCharacter” to show the wordings although it is not pretty. However, I would like to know is there any method for me to implement the wordings in WebGL?

In general openGL does not have any builtin font rendering. A quick search lead to webgl-text-rendering example.

What is common is for someone to generate a font bitmap. The basic idea is that you have a single texture with all the various letters/numbers/etc in it. Then you map a sub-rectangle of that bitmap to a quad that you draw for each letter. Even though this fontengine example uses deprecated GL functions it does give a flavor of what is commonly done and in principle could be modified for openGL ES for WebGL.

Hi, I have attempted to do it. However, I cannot map the text to the 3D object.

Is there any mistakes in my coding?

    var crateText = document.getElementById('text')
    var ctx = crateText.getContext('2d');
    ctx.fillStyle = 'white';
    ctx.lineWidth = 2.5;
    ctx.strokeStyle = 'black';
    ctx.save();
    ctx.font = 'bold 60px Verdana';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    var leftOffset = ctx.canvas.width / 2;
    var topOffset = ctx.canvas.height / 2;
    var text = "Testing";
    ctx.strokeText(text, leftOffset, topOffset);
    ctx.fillText(text, leftOffset, topOffset);
    ctx.restore();

    var texture = gl.createTexture();
    texture = crateText;
    crateTextures.push(texture);

    var crateImage = new Image();

    for (var i = 0; i < 3; i++) {
        var texture = gl.createTexture();
        texture.image = crateImage;
        crateTextures.push(texture);
    }
    
    crateImage.onload = function() {
        handleLoadedTexture(crateTextures)
    }
    crateImage.src = "../../Content/Images/crate.gif";

And the function handleLoadedTexture is:
function handleLoadedTexture(textures) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

    gl.bindTexture(gl.TEXTURE_2D, textures[0]);
    //gl.texImage2D(gl.TEXTURE_2D, 0, textures[0]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[0].image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

Please describe the problem in more detail. It is rather difficult to weed through partial code. Are you modifying the webGL example or are you doing a completely different coding?

Yes, I’m trying to modify the webGL example (http://learningwebgl.com/blog/?p=571).

I can see another canvas “text” shows the text “Testing”. However, The 3D objects cannot shows the text. It only shows the black blackground color. (Attached the screen.)

I haven’t tried this but take a look at energizeGL – this may help you with your original question.

Back to your last post – This code below works for me …


<html> 
 
<head> 
<title>Learning WebGL &mdash; lesson 6</title> 
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> 
 
<script type="text/javascript" src="sylvester.js"></script> 
<script type="text/javascript" src="glUtils.js"></script> 
 
<script id="shader-fs" type="x-shader/x-fragment"> 
  #ifdef GL_ES
  precision highp float;
  #endif
 
  varying vec2 vTextureCoord;
 
  uniform sampler2D uSampler;
 
  void main(void) {
    gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
  }
</script> 
 
<script id="shader-vs" type="x-shader/x-vertex"> 
  attribute vec3 aVertexPosition;
  attribute vec2 aTextureCoord;
 
  uniform mat4 uMVMatrix;
  uniform mat4 uPMatrix;
 
  varying vec2 vTextureCoord;
 
 
  void main(void) {
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
    vTextureCoord = aTextureCoord;
  }
</script> 
 
 
<script type="text/javascript"> 
 
  var gl;
  function initGL(canvas) {
    try {
      gl = canvas.getContext("experimental-webgl");
      gl.viewportWidth = canvas.width;
      gl.viewportHeight = canvas.height;
    } catch(e) {
    }
    if (!gl) {
      alert("Could not initialise WebGL, sorry :-(");
    }
  }
 
 
  function getShader(gl, id) {
    var shaderScript = document.getElementById(id);
    if (!shaderScript) {
      return null;
    }
 
    var str = "";
    var k = shaderScript.firstChild;
    while (k) {
      if (k.nodeType == 3) {
        str += k.textContent;
      }
      k = k.nextSibling;
    }
 
    var shader;
    if (shaderScript.type == "x-shader/x-fragment") {
      shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (shaderScript.type == "x-shader/x-vertex") {
      shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
      return null;
    }
 
    gl.shaderSource(shader, str);
    gl.compileShader(shader);
 
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      alert(gl.getShaderInfoLog(shader));
      return null;
    }
 
    return shader;
  }
 
 
  var shaderProgram;
  function initShaders() {
    var fragmentShader = getShader(gl, "shader-fs");
    var vertexShader = getShader(gl, "shader-vs");
 
    shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);
 
    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
      alert("Could not initialise shaders");
    }
 
    gl.useProgram(shaderProgram);
 
    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
 
    shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
    gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
 
    shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
    shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
  }
 
 
  function handleLoadedTexture(image, texture) {
    gl.enable(gl.TEXTURE_2D);
 
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    //gl.texImage2D(gl.TEXTURE_2D, 0, image);
    gl.texImage2D(gl.TEXTURE_2D, 0,gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE,image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    var mipMapQuality = document.getElementById("mipMapQuality").checked;
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mipMapQuality ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR_MIPMAP_NEAREST);
    gl.generateMipmap(gl.TEXTURE_2D);
 
    gl.bindTexture(gl.TEXTURE_2D, null);
  }
 
  var crateTexture;
  var crateImage;
  function two(number) {
    if (number < 10)
      return '0' + number;
    else
      return number;
  }
  function initTexture() {
    if (!crateTexture) crateTexture = gl.createTexture();
    crateImage = document.getElementById('text');
    var ctx = crateImage.getContext('2d');
    var date = new Date();
    var secs = date.getSeconds() + date.getMilliseconds() / 1000;
    ctx.fillStyle = 'hsl(' + 360 * (secs / 60) + ',100%,50%)';
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    ctx.fillStyle = 'white';
    ctx.lineWidth = 5;
    ctx.strokeStyle = 'black';
    ctx.save();
    ctx.font = "bold 80px Verdana";
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    var text = "Testing";
    //var text = two(date.getHours()) + ':' +
    //  two(date.getMinutes()) + ':' +
    //  two(date.getSeconds());
    var leftOffset = ctx.canvas.width / 2;
    var topOffset = ctx.canvas.height / 2;
    ctx.strokeText(text, leftOffset, topOffset);
    ctx.fillText(text, leftOffset, topOffset);
    ctx.restore();
    handleLoadedTexture(crateImage, crateTexture)
  }

  var mvMatrix;
  var mvMatrixStack = [];
 
  function mvPushMatrix(m) {
    if (m) {
      mvMatrixStack.push(m.dup());
      mvMatrix = m.dup();
    } else {
      mvMatrixStack.push(mvMatrix.dup());
    }
  }
 
  function mvPopMatrix() {
    if (mvMatrixStack.length == 0) {
      throw "Invalid popMatrix!";
    }
    mvMatrix = mvMatrixStack.pop();
    return mvMatrix;
  }
 
  function loadIdentity() {
    mvMatrix = Matrix.I(4);
  }
 
 
  function multMatrix(m) {
    mvMatrix = mvMatrix.x(m);
  }
 
  function mvTranslate(v) {
    var m = Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4();
    multMatrix(m);
  }
 
  function mvRotate(ang, v) {
    var arad = ang * Math.PI / 180.0;
    var m = Matrix.Rotation(arad, $V([v[0], v[1], v[2]])).ensure4x4();
    multMatrix(m);
  }
 
  var pMatrix;
  function perspective(fovy, aspect, znear, zfar) {
    pMatrix = makePerspective(fovy, aspect, znear, zfar);
  }
 
 
  function setMatrixUniforms() {
    gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, new Float32Array(pMatrix.flatten()));
    gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, new Float32Array(mvMatrix.flatten()));
  }
 
 
  var xRot = 0;
  var xSpeed = 0;
 
  var yRot = 0;
  var ySpeed = 0;
 
  var z = -5.0;
 
  var filter = 0;
 
 
  var currentlyPressedKeys = Object();
 
  function handleKeyDown(event) {
    currentlyPressedKeys[event.keyCode] = true;
 
    if (String.fromCharCode(event.keyCode) == "F") {
      filter += 1;
      if (filter == 3) {
        filter = 0;
      }
    }
  }
 
 
  function handleKeyUp(event) {
    currentlyPressedKeys[event.keyCode] = false;
  }
 
 
  function handleKeys() {
    if (currentlyPressedKeys[33]) {
      // Page Up
      z -= 0.05;
    }
    if (currentlyPressedKeys[34]) {
      // Page Down
      z += 0.05;
    }
    if (currentlyPressedKeys[37]) {
      // Left cursor key
      ySpeed -= 1;
    }
    if (currentlyPressedKeys[39]) {
      // Right cursor key
      ySpeed += 1;
    }
    if (currentlyPressedKeys[38]) {
      // Up cursor key
      xSpeed -= 1;
    }
    if (currentlyPressedKeys[40]) {
      // Down cursor key
      xSpeed += 1;
    }
  }
 
 
  var cubeVertexPositionBuffer;
  var cubeVertexTextureCoordBuffer;
  var cubeVertexIndexBuffer;
  function initBuffers() {
    cubeVertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
    vertices = [
      // Front face
      -1.0, -1.0,  1.0,
       1.0, -1.0,  1.0,
       1.0,  1.0,  1.0,
      -1.0,  1.0,  1.0,
 
      // Back face
      -1.0, -1.0, -1.0,
      -1.0,  1.0, -1.0,
       1.0,  1.0, -1.0,
       1.0, -1.0, -1.0,
 
      // Top face
      -1.0,  1.0, -1.0,
      -1.0,  1.0,  1.0,
       1.0,  1.0,  1.0,
       1.0,  1.0, -1.0,
 
      // Bottom face
      -1.0, -1.0, -1.0,
       1.0, -1.0, -1.0,
       1.0, -1.0,  1.0,
      -1.0, -1.0,  1.0,
 
      // Right face
       1.0, -1.0, -1.0,
       1.0,  1.0, -1.0,
       1.0,  1.0,  1.0,
       1.0, -1.0,  1.0,
 
      // Left face
      -1.0, -1.0, -1.0,
      -1.0, -1.0,  1.0,
      -1.0,  1.0,  1.0,
      -1.0,  1.0, -1.0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    cubeVertexPositionBuffer.itemSize = 3;
    cubeVertexPositionBuffer.numItems = 24;
 
    cubeVertexTextureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
    var textureCoords = [
      // Front face
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
 
      // Back face
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      0.0, 0.0,
 
      // Top face
      0.0, 1.0,
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
 
      // Bottom face
      1.0, 1.0,
      0.0, 1.0,
      0.0, 0.0,
      1.0, 0.0,
 
      // Right face
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      0.0, 0.0,
 
      // Left face
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
    cubeVertexTextureCoordBuffer.itemSize = 2;
    cubeVertexTextureCoordBuffer.numItems = 24;
 
    cubeVertexIndexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
    var cubeVertexIndices = [
      0, 1, 2,      0, 2, 3,    // Front face
      4, 5, 6,      4, 6, 7,    // Back face
      8, 9, 10,     8, 10, 11,  // Top face
      12, 13, 14,   12, 14, 15, // Bottom face
      16, 17, 18,   16, 18, 19, // Right face
      20, 21, 22,   20, 22, 23  // Left face
    ]
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
    cubeVertexIndexBuffer.itemSize = 1;
    cubeVertexIndexBuffer.numItems = 36;
  }
 
 
  function drawScene() {
    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
    perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
    loadIdentity();
 
    mvTranslate([0.0, 0.0, z]);
 
    mvRotate(xRot, [1, 0, 0]);
    mvRotate(yRot, [0, 1, 0]);
 
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
 
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
 
 
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, crateTexture);
    gl.uniform1i(shaderProgram.samplerUniform, 0);
 
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
    setMatrixUniforms();
    gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
 
  }
 
 
  var lastTime = 0;
  function animate() {
    var timeNow = new Date().getTime();
    if (lastTime != 0) {
      var elapsed = timeNow - lastTime;
 
      xRot += (xSpeed * elapsed) / 1000.0;
      yRot += (ySpeed * elapsed) / 1000.0;
    }
    lastTime = timeNow;
  }
 
 
  function tick() {
    handleKeys();
    drawScene();
    animate();
  }
 
 
 
  function webGLStart() {
    var canvas = document.getElementById("lesson06-canvas");
    initGL(canvas);
    initShaders();
    initBuffers();
    initTexture();
 
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
 
    gl.clearDepth(1.0);
 
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);
 
    document.onkeydown = handleKeyDown;
    document.onkeyup = handleKeyUp;
 
    setInterval(tick, 15);
    setInterval(initTexture, 200);
  }
 
 
 
</script> 
 
 
</head> 
 
 
<body onload="webGLStart();"> 
  <a href="http://learningwebgl.com/blog/?p=571">&lt;&lt; Back to Lesson 6</a><br /> 
 
  <canvas id="lesson06-canvas" style="border: none;" width="500" height="500"></canvas> 
  <canvas id="text" style="border: none" width="512" height="512"></canvas> 

 
  <h2>Controls:</h2> 

  <input type="checkbox" id="mipMapQuality" checked /> Use high quality mipmap<br/> 

 
  [ul] 
    [li]<code>Page Up</code>/<code>Page Down</code> to zoom out/in
[/li]    [li]Cursor keys: make the cube rotate (the longer you hold down a cursor key, the more it accelerates)
[/li]    [li]<code>F</code> to toggle through three different kinds of texture filters
[/li]  [/ul] 
 
  <br/> 
  <a href="http://learningwebgl.com/blog/?p=571">&lt;&lt; Back to Lesson 6</a><br /> 
 
</body> 
 
</html>

I try it and see a canvas "Text" with the text has the background color change effect. However, I only see nothing (only black color) in the canvas "lesson06-canvas". (Attached the screen)

Is it possible for me to make the text in the cube? Thanks your help.

In the energizeGL, I think it is easier than writing a lot of code to generate one object. However, I only can run it correctly in Firefox. Google Chromium shows errors message “No precision specific for float” . Is my google chromium version incorrect?

Strange I am seeing a rotating cube with the text on each face with the code that I gave you. Do you have all the js files in the same folder as your html file ie glUtils.js and sylvester.js?

The version of Google Chrome that I am running is 6.0.472.62.

My Google Chrome version is 7.0.530.0 (60007).

I’m sorry to tell you that I put the js files to wrong folders.

But, when I update the js files path. The coding still does not works.

This time, it shows me the white background (canvas “lesson06-canvas”) and the red background with text (canvas “text”).

Sorry for cannot provided the screen capture because of the max upload size exceeds.

I am at a loss to explain the fact that it works for my version of Chrome not your newer version. Maybe a new post asking this question would be valuable explaining this.

It is a good new that I have copied it again and I see the effect now.

However, when I fail to modify my coding based on your coding. Is there any things wrong in my coding?

My coding is the following:

<script src="…/…/Scripts/sylvester.js" type=“text/javascript”></script>
<script src="…/…/Scripts/glUtils.js" type=“text/javascript”></script>

<script id=“shader-fs” type=“x-shader/x-fragment”>
#ifdef GL_ES
precision highp float;
#endif

varying vec2 vTextureCoord;

uniform sampler2D uSampler;

void main(void) {
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
}
</script>

<script id=“shader-vs” type=“x-shader/x-vertex”>
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;

varying vec2 vTextureCoord;

void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vTextureCoord = aTextureCoord;
}
</script>

<script type=“text/javascript”>

var gl;
function initGL(canvas) {
    try {
        gl = canvas.getContext("experimental-webgl");
        gl.viewportWidth = canvas.width;
        gl.viewportHeight = canvas.height;
    } catch (e) {
    }
    if (!gl) {
        alert("Could not initialise WebGL, sorry :-(");
    }
}


function getShader(gl, id) {
    var shaderScript = document.getElementById(id);
    if (!shaderScript) {
        return null;
    }

    var str = "";
    var k = shaderScript.firstChild;
    while (k) {
        if (k.nodeType == 3) {
            str += k.textContent;
        }
        k = k.nextSibling;
    }

    var shader;
    if (shaderScript.type == "x-shader/x-fragment") {
        shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (shaderScript.type == "x-shader/x-vertex") {
        shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
        return null;
    }

    gl.shaderSource(shader, str);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(shader));
        return null;
    }

    return shader;
}


var shaderProgram;
function initShaders() {
    var fragmentShader = getShader(gl, "shader-fs");
    var vertexShader = getShader(gl, "shader-vs");

    shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert("Could not initialise shaders");
    }

    gl.useProgram(shaderProgram);

    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

    shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
    gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);

    shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
    shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
}


function handleLoadedTexture(textures) {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

    gl.bindTexture(gl.TEXTURE_2D, textures[0]);
    //gl.texImage2D(gl.TEXTURE_2D, 0, textures[0]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[0].image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

    gl.bindTexture(gl.TEXTURE_2D, textures[1]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[1].image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

    gl.bindTexture(gl.TEXTURE_2D, textures[2]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[2].image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
    gl.generateMipmap(gl.TEXTURE_2D);

    gl.bindTexture(gl.TEXTURE_2D, null);
}


var crateTextures = Array();

function initTexture() {
    var crateText = document.getElementById('text')
    var ctx = crateText.getContext('2d');
    ctx.fillStyle = 'white';
    ctx.lineWidth = 2.5;
    ctx.strokeStyle = 'black';
    ctx.save();
    ctx.font = 'bold 60px Verdana';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    var leftOffset = ctx.canvas.width / 2;
    var topOffset = ctx.canvas.height / 2;
    var text = "Testing";
    ctx.strokeText(text, leftOffset, topOffset);
    ctx.fillText(text, leftOffset, topOffset);
    ctx.restore();

    var texture = gl.createTexture();
    texture.image = crateText;
    crateTextures.push(texture);
    
    var crateImage = new Image();

    for (var i = 1; i &lt; 3; i++) {
        var texture = gl.createTexture();
        texture.image = crateImage;
        crateTextures.push(texture);
    }
    
    crateImage.onload = function() {
        handleLoadedTexture(crateTextures)
    }
    crateImage.src = "../../Content/Images/crate.gif";
}

var mvMatrix;
var mvMatrixStack = [];

function mvPushMatrix(m) {
    if (m) {
        mvMatrixStack.push(m.dup());
        mvMatrix = m.dup();
    } else {
        mvMatrixStack.push(mvMatrix.dup());
    }
}

function mvPopMatrix() {
    if (mvMatrixStack.length == 0) {
        throw "Invalid popMatrix!";
    }
    mvMatrix = mvMatrixStack.pop();
    return mvMatrix;
}

function loadIdentity() {
    mvMatrix = Matrix.I(4);
}

function multMatrix(m) {
    mvMatrix = mvMatrix.x(m);
}

function mvTranslate(v) {
    var m = Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4();
    multMatrix(m);
}

function mvRotate(ang, v) {
    var arad = ang * Math.PI / 180.0;
    var m = Matrix.Rotation(arad, $V([v[0], v[1], v[2]])).ensure4x4();
    multMatrix(m);
}

var pMatrix;
function perspective(fovy, aspect, znear, zfar) {
    pMatrix = makePerspective(fovy, aspect, znear, zfar);
}

function setMatrixUniforms() {
    gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, new Float32Array(pMatrix.flatten()));
    gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, new Float32Array(mvMatrix.flatten()));
}

var xRot = 0;
var xSpeed = 0;

var yRot = 0;
var ySpeed = 0;
var z = -5.0;
var filter = 0;

var currentlyPressedKeys = Object();

function handleKeyDown(event) {
    currentlyPressedKeys[event.keyCode] = true;

    if (String.fromCharCode(event.keyCode) == "F") {
        filter += 1;
        if (filter == 3) {
            filter = 0;
        }
    }
}

function handleKeyUp(event) {
    currentlyPressedKeys[event.keyCode] = false;
}

function handleKeys() {
    if (currentlyPressedKeys[33]) {
        // Page Up
        z -= 0.05;
    }
    if (currentlyPressedKeys[34]) {
        // Page Down
        z += 0.05;
    }
    if (currentlyPressedKeys[37]) {
        // Left cursor key
        ySpeed -= 1;
    }
    if (currentlyPressedKeys[39]) {
        // Right cursor key
        ySpeed += 1;
    }
    if (currentlyPressedKeys[38]) {
        // Up cursor key
        xSpeed -= 1;
    }
    if (currentlyPressedKeys[40]) {
        // Down cursor key
        xSpeed += 1;
    }
}

var cubeVertexPositionBuffer;
var cubeVertexTextureCoordBuffer;
var cubeVertexIndexBuffer;
function initBuffers() {
    cubeVertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
    vertices = [
    // Front face
  -1.0, -1.0, 1.0,
   1.0, -1.0, 1.0,
   1.0, 1.0, 1.0,
  -1.0, 1.0, 1.0,

    // Back face
  -1.0, -1.0, -1.0,
  -1.0, 1.0, -1.0,
   1.0, 1.0, -1.0,
   1.0, -1.0, -1.0,

    // Top face
  -1.0, 1.0, -1.0,
  -1.0, 1.0, 1.0,
   1.0, 1.0, 1.0,
   1.0, 1.0, -1.0,

    // Bottom face
  -1.0, -1.0, -1.0,
   1.0, -1.0, -1.0,
   1.0, -1.0, 1.0,
  -1.0, -1.0, 1.0,

    // Right face
   1.0, -1.0, -1.0,
   1.0, 1.0, -1.0,
   1.0, 1.0, 1.0,
   1.0, -1.0, 1.0,

    // Left face
  -1.0, -1.0, -1.0,
  -1.0, -1.0, 1.0,
  -1.0, 1.0, 1.0,
  -1.0, 1.0, -1.0,
];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    cubeVertexPositionBuffer.itemSize = 3;
    cubeVertexPositionBuffer.numItems = 24;

    cubeVertexTextureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
    var textureCoords = [
    // Front face
  0.0, 0.0,
  1.0, 0.0,
  1.0, 1.0,
  0.0, 1.0,

    // Back face
  1.0, 0.0,
  1.0, 1.0,
  0.0, 1.0,
  0.0, 0.0,

    // Top face
  0.0, 1.0,
  0.0, 0.0,
  1.0, 0.0,
  1.0, 1.0,

    // Bottom face
  1.0, 1.0,
  0.0, 1.0,
  0.0, 0.0,
  1.0, 0.0,

    // Right face
  1.0, 0.0,
  1.0, 1.0,
  0.0, 1.0,
  0.0, 0.0,

    // Left face
  0.0, 0.0,
  1.0, 0.0,
  1.0, 1.0,
  0.0, 1.0,
];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
    cubeVertexTextureCoordBuffer.itemSize = 2;
    cubeVertexTextureCoordBuffer.numItems = 24;

    cubeVertexIndexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
    var cubeVertexIndices = [
  0, 1, 2, 0, 2, 3,    // Front face
  4, 5, 6, 4, 6, 7,    // Back face
  8, 9, 10, 8, 10, 11,  // Top face
  12, 13, 14, 12, 14, 15, // Bottom face
  16, 17, 18, 16, 18, 19, // Right face
  20, 21, 22, 20, 22, 23  // Left face
]
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
    cubeVertexIndexBuffer.itemSize = 1;
    cubeVertexIndexBuffer.numItems = 36;
}

function drawScene() {
    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
    loadIdentity();

    mvTranslate([0.0, 0.0, z]);

    mvRotate(xRot, [1, 0, 0]);
    mvRotate(yRot, [0, 1, 0]);

    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);


    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, crateTextures[filter]);
    gl.uniform1i(shaderProgram.samplerUniform, 0);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
    setMatrixUniforms();
    gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
}

var lastTime = 0;
function animate() {
    var timeNow = new Date().getTime();
    if (lastTime != 0) {
        var elapsed = timeNow - lastTime;

        xRot += (xSpeed * elapsed) / 1000.0;
        yRot += (ySpeed * elapsed) / 1000.0;
    }
    lastTime = timeNow;
}

function tick() {
    handleKeys();
    drawScene();
    animate();
}

function webGLStart() {
    var canvas = document.getElementById("lesson05-canvas");
    initGL(canvas);
    initShaders();
    initBuffers();
    initTexture();

    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    gl.clearDepth(1.0);

    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);

    document.onkeydown = handleKeyDown;
    document.onkeyup = handleKeyUp;

    setInterval(tick, 15);
}

</script>

&lt;h2&gt;WebGLText&lt;/h2&gt;
&lt;canvas id="lesson05-canvas" style="border: none;" width="400" height="400"&gt;&lt;/canvas&gt; 
&lt;canvas id="text" style="border: none;" width="400" height="400"&gt;&lt;/canvas&gt; 

<script type=“text/javascript”>
webGLStart();
</script>

You were trying to load a gif from disk rather than use the created texture … modified code


handleLoadedTexture(crateTextures);

//crateImage.onload = function() {
//handleLoadedTexture(crateTextures)
//}
//crateImage.src = "../../Content/Images/crate.gif";

Also you did not enable texturing in handleLoadedTexture function. Here is a slightly modified version of your code that runs for me. Note, pressing “f” cycles thru the texture filter types but only works for “0”. I’ll leave that as an exercise for the reader :wink: Actually ran out of time to fix that …


<html> 
 
<head> 
<title>Learning WebGL &mdash; lesson 6</title> 
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> 
 
<script src="../../Scripts/sylvester.js" type="text/javascript"></script>
<script src="../../Scripts/glUtils.js" type="text/javascript"></script>

<script id="shader-fs" type="x-shader/x-fragment"> 
#ifdef GL_ES
precision highp float;
#endif

varying vec2 vTextureCoord;

uniform sampler2D uSampler;

void main(void) {
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
}
</script> 

<script id="shader-vs" type="x-shader/x-vertex"> 
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;

varying vec2 vTextureCoord;


void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vTextureCoord = aTextureCoord;
}
</script> 


<script type="text/javascript">

var gl;
function initGL(canvas) {
try {
gl = canvas.getContext("experimental-webgl");
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch (e) {
}
if (!gl) {
alert("Could not initialise WebGL, sorry :-(");
}
}


function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}

var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.nextSibling;
}

var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}

gl.shaderSource(shader, str);
gl.compileShader(shader);

if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}

return shader;
}


var shaderProgram;
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");

shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);

if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}

gl.useProgram(shaderProgram);

shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);

shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
}


function handleLoadedTexture(textures) {
gl.enable(gl.TEXTURE_2D);

gl.bindTexture(gl.TEXTURE_2D, textures[0]);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
//gl.texImage2D(gl.TEXTURE_2D, 0, textures[0]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[0].image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

gl.bindTexture(gl.TEXTURE_2D, textures[1]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[1].image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

gl.bindTexture(gl.TEXTURE_2D, textures[2]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[2].image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);

gl.bindTexture(gl.TEXTURE_2D, null);
}


var crateTextures = Array();

function initTexture() {
var crateText = document.getElementById('text')
var ctx = crateText.getContext('2d');
ctx.fillStyle = 'white';
ctx.lineWidth = 2.5;
ctx.strokeStyle = 'black';
ctx.save();
ctx.font = 'bold 60px Verdana';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var leftOffset = ctx.canvas.width / 2;
var topOffset = ctx.canvas.height / 2;
var text = "Testing";
ctx.strokeText(text, leftOffset, topOffset);
ctx.fillText(text, leftOffset, topOffset);
ctx.restore();

var texture = gl.createTexture();
texture.image = crateText;
crateTextures.push(texture);

var crateImage = new Image();

for (var i = 1; i < 3; i++) {
var texture = gl.createTexture();
texture.image = crateImage;
crateTextures.push(texture);
}
handleLoadedTexture(crateTextures);

//crateImage.onload = function() {
//handleLoadedTexture(crateTextures)
//}
//crateImage.src = "../../Content/Images/crate.gif";
}

var mvMatrix;
var mvMatrixStack = [];

function mvPushMatrix(m) {
if (m) {
mvMatrixStack.push(m.dup());
mvMatrix = m.dup();
} else {
mvMatrixStack.push(mvMatrix.dup());
}
}

function mvPopMatrix() {
if (mvMatrixStack.length == 0) {
throw "Invalid popMatrix!";
}
mvMatrix = mvMatrixStack.pop();
return mvMatrix;
}

function loadIdentity() {
mvMatrix = Matrix.I(4);
}


function multMatrix(m) {
mvMatrix = mvMatrix.x(m);
}

function mvTranslate(v) {
var m = Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4();
multMatrix(m);
}

function mvRotate(ang, v) {
var arad = ang * Math.PI / 180.0;
var m = Matrix.Rotation(arad, $V([v[0], v[1], v[2]])).ensure4x4();
multMatrix(m);
}

var pMatrix;
function perspective(fovy, aspect, znear, zfar) {
pMatrix = makePerspective(fovy, aspect, znear, zfar);
}


function setMatrixUniforms() {
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, new Float32Array(pMatrix.flatten()));
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, new Float32Array(mvMatrix.flatten()));
}


var xRot = 0;
var xSpeed = 0;

var yRot = 0;
var ySpeed = 0;

var z = -5.0;

var filter = 0;


var currentlyPressedKeys = Object();

function handleKeyDown(event) {
currentlyPressedKeys[event.keyCode] = true;

if (String.fromCharCode(event.keyCode) == "F") {
filter += 1;
if (filter == 3) {
filter = 0;
}
}
}


function handleKeyUp(event) {
currentlyPressedKeys[event.keyCode] = false;
}


function handleKeys() {
if (currentlyPressedKeys[33]) {
// Page Up
z -= 0.05;
}
if (currentlyPressedKeys[34]) {
// Page Down
z += 0.05;
}
if (currentlyPressedKeys[37]) {
// Left cursor key
ySpeed -= 1;
}
if (currentlyPressedKeys[39]) {
// Right cursor key
ySpeed += 1;
}
if (currentlyPressedKeys[38]) {
// Up cursor key
xSpeed -= 1;
}
if (currentlyPressedKeys[40]) {
// Down cursor key
xSpeed += 1;
}
}


var cubeVertexPositionBuffer;
var cubeVertexTextureCoordBuffer;
var cubeVertexIndexBuffer;
function initBuffers() {
cubeVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
vertices = [
// Front face
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,

// Back face
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,

// Top face
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,

// Bottom face
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,

// Right face
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
1.0, -1.0, 1.0,

// Left face
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
cubeVertexPositionBuffer.itemSize = 3;
cubeVertexPositionBuffer.numItems = 24;

cubeVertexTextureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
var textureCoords = [
// Front face
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,

// Back face
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,

// Top face
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,

// Bottom face
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,

// Right face
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,

// Left face
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
cubeVertexTextureCoordBuffer.itemSize = 2;
cubeVertexTextureCoordBuffer.numItems = 24;

cubeVertexIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
var cubeVertexIndices = [
0, 1, 2, 0, 2, 3, // Front face
4, 5, 6, 4, 6, 7, // Back face
8, 9, 10, 8, 10, 11, // Top face
12, 13, 14, 12, 14, 15, // Bottom face
16, 17, 18, 16, 18, 19, // Right face
20, 21, 22, 20, 22, 23 // Left face
]
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
cubeVertexIndexBuffer.itemSize = 1;
cubeVertexIndexBuffer.numItems = 36;
}


function drawScene() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
loadIdentity();

mvTranslate([0.0, 0.0, z]);

mvRotate(xRot, [1, 0, 0]);
mvRotate(yRot, [0, 1, 0]);

gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);


gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, crateTextures[filter]);
gl.uniform1i(shaderProgram.samplerUniform, 0);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);

}


var lastTime = 0;
function animate() {
var timeNow = new Date().getTime();
if (lastTime != 0) {
var elapsed = timeNow - lastTime;

xRot += (xSpeed * elapsed) / 1000.0;
yRot += (ySpeed * elapsed) / 1000.0;
}
lastTime = timeNow;
}


function tick() {
handleKeys();
drawScene();
animate();
}



function webGLStart() {
var canvas = document.getElementById("lesson05-canvas");
initGL(canvas);
initShaders();
initBuffers();
initTexture();

gl.clearColor(0.0, 0.0, 0.0, 1.0);

gl.clearDepth(1.0);

gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);

document.onkeydown = handleKeyDown;
document.onkeyup = handleKeyUp;

setInterval(tick, 15);
}



</script> 
 
 
</head> 
 
 
<body onload="webGLStart();"> 
  <canvas id="lesson05-canvas" style="border: none;" width="400" height="400"></canvas> 
  <canvas id="text" style="border: none" width="400" height="400"></canvas> 
</body> 
 
</html>

Does it mean I have to change the code to the following? (I copies the code you modified serveral times but it cannot work in my computer. I think I have to update some codes. But it still cannot work after my update. :frowning: )

function handleLoadedTexture(textures) {
    gl.enable(gl.TEXTURE_2D);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

    gl.bindTexture(gl.TEXTURE_2D, textures[0]);
    //gl.texImage2D(gl.TEXTURE_2D, 0, textures[0]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[0].image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

AND

/* var texture = gl.createTexture();
texture.image = crateText;
crateTextures.push(texture);
*/
var crateImage = new Image();

    for (var i = 0; i &lt; 3; i++) {
        var texture = gl.createTexture();
        texture.image = crateImage;
        crateTextures.push(texture);
    }
    handleLoadedTexture(crateTextures);

/* crateImage.onload = function() {
handleLoadedTexture(crateTextures)
}
crateImage.src = “…/…/Content/Images/crate.gif”;
*/

Hi,

Thanks your help. I find out the problem.

The texture size should be the 2-power but the size of canvas “text” is 400400. When I update it to "512512", it works.