Tutorials


These tutorials are targeted at people with a reasonable web programming knowledge (HTML and JavaScript languages) and a basic web 3D programming knowledge (WebGL API).


Tutorial 8 : The Frame Buffer

This tutorial provides a basic demonstration of use about the SpiderGL class called Framebuffer. The example here proposed shows only one of the multiple utility of the on texture rendering, that it is the foundation required for adding picking, shadows, reflections, and many other effects in a 3D scene.

View DemoDownload Source

The aim of this lesson is to introduce the SpiderGL frame buffer class, another useful tool belonging to our library component called WebGL. This class, wrapper of the correspondent WebGL, as the name suggest enable the use of the frame buffer, that how is well know, allows to allocate an user defined memory space on the graphics card (alternative to the default), on the which to render.
This kind of buffer allows to obtain different effects very useful in a 3D scene, likes realistic reflections or shadows, or even the scene picking, by means of the attachment to three additional buffer (color, depth and stencil buffer), setting adequately which, is possible to render directly on a previous instantiated texture. And this is just what we are going to do in this tutorial.

The final effect we want to reach will be to draw a textured triangle on a squared texture, and give some animation features to both these objects.

Let's see how you can do that with SpiderGL, taking a look of the source code: as usual, all the code changes compared to the previous lesson are placed in the "CanvasHandler.prototype" specification, where you can find the now classic routines to initialize, manage the events, and finally draw. Begin to analyse just the "onInitialize".

As of the whole first part of this routine there's no need to explanation, because all the setting here defined were introduced in the previous lessons, and even something of these are exactly the same (like the shaders for example).
Scrolling down with order the code, after the shader program instantiation you can find the SpiderGL vertex and index buffers initializations for ours triangle and square (defined with their centroids in the origins of the axis), and immediately after two now usual plain 2D texture objects creations (textures which will then be applied on the triangle).

At this point begin the core of this lesson, i.e. the frame buffer set up:

this.framewidth  = 512;
this.frameheight = 512;

this.sceneColorTexture = new SglTexture2D(gl, {
  internalFormat : gl.RGBA,
  width          : this.framewidth,
  height         : this.frameheight
});

this.sceneDepthRenderbuffer = new SglRenderbuffer(gl, {
  internalFormat : gl.DEPTH_COMPONENT16,
  width          : this.framewidth,
  height         : this.frameheight
});

this.frameBuffer = new SglFramebuffer(gl, {
  autoViewport : true,
  color        : this.sceneColorTexture, 
  depth        : this.sceneDepthRenderbuffer 
});

First of all are instantiated two variables that will use to define our frame dimensions, then you pass to the real initialization of the target entities to attach to the frame buffer on which we want to render a portion of the scene.
The frame buffer indeed to work needs to handle various basic graphics objects, more or less necessary, depending on what we want to accomplish. For this reason, and according our example needs, we chosen to define a simple texture 2D and a render buffer (an objects to contain images, created and used specifically with the frame buffer), both with width and eight values equals to those earlier specified, and both created through the appropriate SpiderGL constructors, the already seen "Texture2D" and the new "Renderbuffer" (wrapper of the WebGL render buffer and placed in the namesake class of the library component WebGL, that take by input the rendering context "gl" and the above shown few optional parameter).
This done you can go to the real frame buffer creation, in the last part of the previous block of code. Using the express constructor is possible to instantiate the frame buffer object, passing it the usual WebGL rendering context and the here described optional parameters:

handle-the WebGL frame buffer object, that, if present, will be used as the wrapped one (otherwise a new one will be created);
autoViewport-a flag that, if true, enables the constructor to automatically set the viewport when a call is bind;
color-the texture target to attach;
depth-the render buffer target to attach;
stencil-the stencil buffer target to attach;
depthStencil-the depth-stencil buffer target to attach;

So, once linked the earlier defined 2D texture and render buffer at the rights frame buffer target, will have reserved a memory space, defined as 2D texture, large "framewidth" for "frameheight", and with associate the proper depth buffer, where render in place of the default output buffer.

The initialization stage terminates with some classic useful variables assignation, and you can thus pass to the event listener routines:

onKeyDown: function (key) {
  if ((key == "N") || (key == "M")) {
    this.ui.animateRate = 60;
  }
},

onKeyUp: function (key) {
  if (!this.ui.isKeyDown("N") && !this.ui.isKeyDown("M")) {
    this.ui.animateRate = 0;
  }
},

onAnimate: function (dt) {
  if (this.angle > 90.0) {
    if (this.texture == this.tex1) this.texture = this.tex2;
    else this.texture = this.tex1;
    this.angle -= 180.0;
  }
  if (this.angle < -90.0) {
    if (this.texture == this.tex1) this.texture = this.tex2;
    else this.texture = this.tex1;
    this.angle += 180.0;
  }
  if (this.ui.isKeyDown("N")) this.angle -= 45.0 * dt;
  if (this.ui.isKeyDown("M")) this.angle += 45.0 * dt;
  this.ui.postDrawEvent();
},

The previous code block does not do anything special if compared with the past tutorials, just manages the user interaction with two key ("N" and "M"), so as to change with their pressure the value of the "angle" variable (in the follow used to rotate the objects on the scene), and to switch the texture associated to the "texture" variable (witch changes every time our objects rotate by 180 degrees).
No SpiderGL new code there is here, so we can quickly go ahead, to the more interesting final routine.

The "onDraw" stage is maybe the most interesting of all the lesson, because the rendering in this tutorial occurs in two distinct phases: the first one when a textured triangle will be drawn on the frame buffer formerly setted, and the second one when this frame will be applied likes texture on a stretched square and sent to the shaders to be rendered on the default output.

Let's a look to the source code to see how all this happens, starting as usual form the begin. The first lines of this routine are dedicated to various variables assignations, the binding of the shaders program, and the definition of the JavaScript object "globals" bound to the uniform variable. Compared to the other tutorials were here removed the methods related to the cleaning of the buffers, the setting of the viewport, and to the modification of the transformation stack, functions all moved more ahead, as we shall see.

Just next these few code lines, with the frame buffer binding starts the already cited first rendering phase:

this.frameBuffer.bind();
  gl.clearColor(0.2, 0.2, 0.6, 1.0);;
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.clear(gl.DEPTH_BUFFER_BIT);
  gl.clear(gl.STENCIL_BUFFER_BIT);
  gl.viewport(0, 0, fw, fh);
  gl.enable(gl.DEPTH_TEST);

Calling the "bind" method of the SpiderGL frame buffer object you enable in fact this special chunk of memory, diverting the rendering streaming on a 2D texture (in this case). All the following function calls (that initialize the scene with the usual blue color, perform the buffers cleaning procedures, and set up the viewport with the previously instantiate frame width and height) are so related only to this frame view space, and will be redefined for the aforementioned rendering second phase.
Different speech instead is that concerning the operation on the transformation stack and their associated matrices. In fact as is well known, to manage in a separate way the model view projection matrix that define the scene in the frame buffer and in the default output buffer, you don't instantiate two different SpiderGL transformation stack objects, but rather you re-use more time that one earlier defined ("xform"), thanks to the appropriate "push" and "pop" related methods:

  xform.projection.push();
  xform.model.push();
    xform.projection.perspective(sglDegToRad(45),fw/fh,0.1,100.0);
    xform.model.scale([0.5, 1.0, 1.0]);
    xform.model.translate([0.0, 0.0,-3.0]);
    xform.model.rotate(sglDegToRad(-this.angle), [0.0, 1.0, 0.0]);
    globals.uMVPMatrix = xform.modelViewProjectionMatrix;
  xform.model.pop();
  xform.projection.pop();

  this.shaderProgram.setUniforms(globals);

So using these two functions, characteristic of the transformation stack, exactly as it happened for the similar stack in the fixed-pipeline versions of OpenGL, is possible to push into the stack the required matrices on which we want to work, operate with them, and then pop out the same cleaning the stack.
More in detail, as you can see in the above block of code, in this example we push only the projection and the model matrices, setting the projection perspective (with the appropriate frame width and height), and scaling, translating and rotating (around the Y axis of the "angle" amount) the model. Finally, after updating the model view projection matrix, we pop both the used matrices from the stack.

Once setted the shaders uniform variable concerning the model view projection matrix of the frame buffer you can pass to attach the pointers to the vertex and index buffer of the same. This is done in the now classic way, as showed in the below box:

  this.triangleVertexPositionBuffer.vertexAttribPointer({
    index     : 0, 
    size      : 3,
    glType    : gl.FLOAT, 
    normalized: false, 
    stride    : 0, 
    offset    : 0,
    enable    : true 
  });

  this.triangleVertexTexCoordBuffer.vertexAttribPointer({
    index     : 1, 
    size      : 2, 
    glType    : gl.FLOAT, 
    normalized: false, 
    stride    : 0, 
    offset    : 0, 
    enable    : true 
  });

  this.texture.bind();

  this.triangleVertexIndexBuffer.drawElements({
    glMode : gl.TRIANGLES, 
    count  : 3, 
    glType : gl.UNSIGNED_SHORT, 
    offset : 0 
  });

  this.texture.unbind();					

We will draw here our triangle, so after having latched the spatial and textures coordinates of the object in question, you bind the texture stored in the earlier defined "texture" variable and finally you render the meshes following the order specified in the index buffer.
Next the selected texture can be unbind, and the shaders attribute set up for the frame buffer thus ends.

The first phase of rendering finishes with a couple of function calls:

  gl.disable(gl.DEPTH_TEST);	

this.frameBuffer.unbind();

With the frame buffer unbinding the output of the "onDraw" routine turn back on the default buffer, so you pass directly to the second rendering phase.
The standard procedures of scene color (this time in white) and buffers cleaning, as that of viewport set up (with width and height relatives to the canvas dimensions), had to be redefined here to be effective for the default output buffer:

gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.clear(gl.STENCIL_BUFFER_BIT);
gl.viewport(0, 0, w, h);
gl.enable(gl.DEPTH_TEST);

This done you can pass to the setting of the model view projection matrix for this new scene. The projection and model matrices are newly pushed up from the usual transformation stack and modified with different values compared to the previous set up (change the viewport aspect ratio, which becomes concerning the canvas, and the model scale and rotation, this time run in the opposite direction):

xform.projection.push();
xform.model.push();
  xform.projection.perspective(sglDegToRad(45), w/h, 0.1, 100.0);
  xform.model.scale([1.5, 1.0, 1.0]);	
  xform.model.translate([0.0, 0.0,-3.0]);
  xform.model.rotate(sglDegToRad(this.angle), [0.0, 1.0, 0.0]);
  globals.uMVPMatrix = xform.modelViewProjectionMatrix;
xform.model.pop();
xform.projection.pop();

this.shaderProgram.setUniforms(globals);

Next the JavaScript object that bound the model view projection matrix is update, the used matrices popped in the stack, and at last the uniform variable is sent to the shaders.

At this point you perform the attach of the vertex buffers for the spatial and texture coordinates, in order to draw a (stretched) square on the main frame buffer.
The follow procedure is the same ever, as below shown:

this.squareVertexPositionBuffer.vertexAttribPointer({
  index     : 0, 
  size      : 3, 
  glType    : gl.FLOAT, 
  normalized: false, 
  stride    : 0, 
  offset    : 0,
  enable    : true 
});

this.squareVertexTexCoordBuffer.vertexAttribPointer({
  index     : 1, 
  size      : 2,
  glType    : gl.FLOAT, 
  normalized: false, 
  stride    : 0, 
  offset    : 0, 
  enable    : true 
});

this.sceneColorTexture.bind();

this.squareVertexIndexBuffer.drawElements({
  glMode : gl.TRIANGLES, 
  count  : 6, 
  glType : gl.UNSIGNED_SHORT,
  offset : 0 
});

this.sceneColorTexture.unbind();

However the very interesting thing of the previous code lines is represented by the texture bind before to perform the index buffer related "drawElements". If you pay attention you can indeed note that the texture used, the one associated to the "sceneColorTexture" variables, is exactly the that linked to the frame buffer "color" attribute, and so is just that where is redirected the first rendering phase of this tutorial.
So doing on our square object is applied an animated texture, the one with the textured triangle rotating on a blue background.

OK, this done it only remains the following two last function calls (without new code to explain), and the second rendering phase is ended too:

gl.disable(gl.DEPTH_TEST);					

this.shaderProgram.unbind();	

With these calls terminates the "onDraw" stage and together the current tutorial.

Well, we have so here described to the reader how to set out and use the powerful SpiderGL wrapper of the WebGL frame buffer, performing two concentric rendering of animated objects, as it was our initial purpose.
In the next lesson will be show instead two tools original own our library, able to facilitate and abridge the work of a WebGL programmer: the SpiderGL model renderer and shader technique!