Sunday, June 5, 2011

Render text with canvas 2D

Hello everyone, and welcome to my 4th tutorial about WebGL! This week we shall try something more interesting than spinning a box - Instead of explaining what we are going to do, just take a look at the video below:

You can even put dirty content on it!


The code is not so different then the one from the previous tutorial. But this time, instead of using some static images, let's write our own simple textures - in real time! As before, you download the source code for this tutorial here . I am going to skip the introduction part and head straight over to the important things - how to render a texture with text content.

Step no.1 - create another canvas where we will render our text. That canvas is then going to be copied to a texture which will be used on our box. The new thing here is - we don't need WebGL to create this texture, instead  we can use anoter context - the 2D context. To get access to the 2D API, we use the same function as for WebGL, just with the '2d' parameter. That would then look like this:

context2D = document.getElementById('textCanvas').getContext('2d');

Our entry function has also a few new lines (all context2D calls refer to the textCanvas, all gl calls to the 3d scene):

  • setUpTextArea() - set's up the text format, colors, positioning etc.
  • writeTextToCanvas(Sring text, int textureIndex) - renders the text to a texture

        ....
        initBuffers();

        setUpTextArea();
       for (var ii = 0; ii< 6; ++ii){
         writeTextToCanvas('Type here!', ii);
       }
        gl.clearColor(0.5, 0.5, 0.5, 1.0);
       gl.enable(gl.DEPTH_TEST);
                .... 


So, the setUpTextArea() function does some the initial housekeeping. It's pretty straight forward once you understand the mehtods:


  function setUpTextArea(){
   context2D.font = 'normal 35px Verdana';
   context2D.fillStyle = 'rgba(0,0,0,255)';
   context2D.fillRect(0,0, 300, 300);
   context2D.lineWidth = 5;
    context2D.strokeStyle = 'rgba(0,0,0,255)';
   context2D.textAlign = 'center';
   context2D.textBaseline = 'middle';
}

The font property is I believe pretty obvious - play around with it to see the different looks! The fillStyle property is the actual drawing color - here we set it up to non-transparent black. LineWidth, textAlign and textBaseLine specify the formating properties (2d rendering context API). strokeStyle specifies the outline or shadow of the text - here we have set it also to black. All good? Let's continue.


The next function, writeTextToCanvas(), is actually writing the text for us. It looks like this:


function writeTextToCanvas(text, idx){
context2D.save();

context2D.clearRect ( 0 , 0 , 300 , 300);
context2D.fillStyle = faceColors[idx];
context2D.fillRect(0,0, 300, 300);

context2D.fillStyle = 'rgba(255, 255, 255, 255)';
context2D.strokeText(text, 150, 150);
context2D.fillText(text, 150, 150);

setTextureFromCanvas(context2D.canvas, textures[idx], idx);

context2D.restore();

}
     var faceColors = ['rgba(0, 0, 255, 255)', 'rgba(255, 0, 0, 255)',

                       'rgba(0, 255, 0, 255)', 'rgba(0, 0, 0, 255)',
                       'rgba(255, 0, 255, 255)', 'rgba(0, 255, 255, 255)'];


(Update: 300 is the width and height of the canvas, that's why it's hard coded. I was quite in a rush writing this, no hard feelings!)


The save() and restore() functions are something like push() and pop() in OpenGL - they are used for state management. At the beginning of this function I save my current set-up (which was the initialization), and at the end, once I have my texture created, I restore it again - this way I am always at my starting point when I want to create a texture. Now, a couple of things are important here:
  • strokeText() - writes the outline of the specified text starting at the specified position (with the previously set alignment etc.)
  • clearRectangle() - erases an previously draw objects from the canvas (from point (0,0) to (300,300))
  • fillRectangle() - fills the rectangle with the specified color (from point (0,0) to (300,300))
  • fillText() - writes the text itself (in essence the same function as strokeText())
  • setTextureFromCanvas() - renders the current canvas content to a WebGL Texture object (parameters canvas, a texture object to render on, and an index as a reference to a cube face)
Now, you might think that the last one is quite complicated? Well, it's not at all! In fact, it's almost the same function we used in the previous tutorial, except that instead from an image we are now loading our content from a canvas element! Pretty simple, right? In case you don't believe me, here is the sample code:

function setTextureFromCanvas(canvas, textTexture, idx) {
 gl.activeTexture(gl.TEXTURE0 + idx);
gl.bindTexture(gl.TEXTURE_2D, textTexture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE,          canvas);
if (isPowerOfTwo(canvas.width) && isPowerOfTwo(canvas.height)){
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
}else{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  }
gl.bindTexture(gl.TEXTURE_2D, textTexture);
gl.uniform1i(shaderTextureIndices[idx], idx); 
}

The big difference between the function from the previous tutorial is and this is that we use a canvas instead of an image. Also this function get's called dynamically as we write the text value - it generates the text and automatically binds the new texture to the shader (the same one as in the previous tutorial). That's why we set the current activeTexture at the begging of this function. All clear? Hope it is, if not drop me a line. And don't complain about my web page style - I'm not a designer :)

3 comments:

  1. The link to the source code is broken. Is there a live demo some where?

    ReplyDelete
  2. Can't you fix the links to the source (also in the previous tutorial)... ?

    ReplyDelete
  3. fix links to the source - drop box outdated probably

    ReplyDelete