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 7 : Textures Cube Map

This tutorial shows another simple example of use of the SpiderGL component WebGL, this time related to that library class dedicated to operate the texture cube map.

View DemoDownload Source

In this lesson will be dealt again the SpiderGL textures, in order to explain how to manage a cube map of these last using our library.
SpiderGL provides an appropriate class exclusively for the textures cube map, located (like the Texture2D class described in the previous tutorial) in the library component WebGL, and named TextureCubeMap.

How well known this kind of texture map is composed by six images (one for each face of the cube that gives him the name) and is usually used to create an environment mapping, so as to have realistic reflection effects.
In this example however we will stop a step before, showing how to apply our textures (here loaded by external image files) on a cube in the middle of the scene, but not pursuing any particular environmental reflection.

At the code level there aren't many changes between this tutorial structure and the last: the HTML basic elements are virtually unchanged, as well as the less important JavaScript function calls.
But this time, also the routine stages in the usual "CanvasHandler.prototype" are pretty the same, except for some little modification in the shaders, in the key event listener, and finally, surely, in the textures allocation and utilization.

So, let's start now to observe the source code, starting as usual from the "onInitialize" routine, and more specifically form the shaders definitions:

var vsSource = "\
  precision highp float;                                 \n\  
                                                         \n\
  attribute vec3 aPosition;                              \n\
  attribute vec3 aNorm;                                  \n\
                                                         \n\
  uniform   mat4  uModelViewProjectionMatrix;            \n\
                                                         \n\
  varying vec3 vNorm;                                    \n\
                                                         \n\
  void main(void) {                                      \n\
    vNorm = aNorm;                                       \n\
                                                         \n\
    gl_Position = 
      uModelViewProjectionMatrix * vec4(aPosition, 1.0); \n\
  }                                                      \n\
";

In the vertex shader above shown you can see that, compared with that one used for textures 2D, have been removed the "vec2" attribute and varying variable related to the textures coordinates.
In return have been introduced two new "vec3" (an attribute and the linked varying variable) needed to store the texture cube map coordinates.
Since by convention these specific texture coordinates are mapped on the vertex of a cube with center in (0,0,0) and sides that go from -1 to 1 (on all the three axis), them must be defined as three dimensional vectors, corresponding (in a geometric way) to normal vectors to each vertex of the cube in question (and this is the reason why the new introduced shader entities for texture cube coordinates are been here named "aNorm" and "vNorm", with clear reference to the normals to the surface).

The changes occurred in the vertex buffer, are of course reflected in the fragment shader:

var fsSource = "\
  precision highp float;                                 \n\
                                                         \n\
  varying vec3 vNorm;                                    \n\
                                                         \n\
  uniform samplerCube uSampler;                          \n\
                                                         \n\
  void main(void) {                                      \n\
    gl_FragColor = textureCube(uSampler, vNorm);         \n\
  }                                                      \n\
";

As you can see, clearly here too is present the varying variable relative to the normal vectors and has been removed the varying used to operate the textures coordinates, and more you can find a new uniform sampler, obviously of "samplerCube" type.
In the "main" method furthermore, now the WebGL function call to calculate each fragment color is the appropriate "textureCube" (which takes by input the just above mentioned variables for the cube normals and sampler).

The reported vertex and fragment shaders modifications naturally affect the SpiderGL shader program too, where however the changes are minimal and relatives only to the attributes chosen index and to the uniform variables default value.
We can so skip all of these, as we can leave out our cube vertex and the index buffer definitions, in all respects identical to these instantiated to render the cube in the tutorial number five, and jump directly in the middle of the "onInitialize" routine, where is placed core of this lesson, i.e. the texture cube map initialization:

this.texture = new SglTextureCubeMap(gl, {
  url : [
    "img/green.bmp",  //right	
    "img/blue.bmp",   //left
    "img/red.bmp",    //top
    "img/orange.bmp", //bottom
    "img/white.bmp",  //front
    "img/yellow.bmp"  //back		
  ],
  internalFormat            : gl.RGBA, 
  format                    : gl.RGBA, 
  border                    : 0, 
  type                      : gl.UNSIGNED_BYTE, 
  magFilter                 : gl.LINEAR, 
  minFilter                 : gl.LINEAR_MIPMAP_LINEAR, 
  wrapS                     : gl.CLAMP_TO_EDGE,
  wrapT                     : gl.CLAMP_TO_EDGE, 
  autoMipmap                : false, 
  generateMipmap            : true, 
  flipYPolicy               : false, 
  flipY                     : false, 
  premultiplyAlphaPolicy    : false, 
  premultiplyAlpha          : false, 
  colorspaceConversionPolicy: false, 
  colorspaceConversion      : false,
  onSuccess                 : function () {
    ui.postDrawEvent();
  }
});

To instantiate a SpiderGL texture cube map using the appropriate constructor "TextureCubeMap", belonging to the homonymous class. It is, just like that for the textures 2D, a wrapper for the WebGL texture object.
The structure of this method is practically the same of that one used to instantiate plain textures (same input requirements and similar optional parameters), but this time the constructor accepts as images source only the "url" option, ignoring the "data" property. This URL, that specified the texture images to load, will be an array of six strings, one for each cube map face, defined in this order: [+X, -X, +Y, -Y, +Z, -Z] (in our example related to the .bmp files respectively green, blue, red, orange, white and yellow).
Compared to the previous lesson there are two others differences it is worth observing: first of all you can note the absence of the texture dimension parameters "width" and "height", not required if the input texture image is an external file and supposed equals for all the six cube faces loaded (so you can't instantiate a texture cube map with files with different resolutions!). The second thing to which we must pay attention is the "onSuccess" function specified at the end of the texture constructor, a callback that will be executed only when all the six cube image faces has been acquired (performing in our case a "postDrawEvent" that will refresh the rendered model with the loaded textures).
With this last explanation the texture instantiation is over, and you can proceed further (as usual for all the others texture parameters you can refer to the online documentation).

Well, before ending the initialization stage we have the by then classic variables declaration, with only something news, reported in this block of code:

this.zValue = -5.0;
this.xAngle = 45.0;
this.yAngle = 45.0;
this.xSpeed =  0.0;
this.ySpeed =  0.0;

The five code lines above allocate as many variables afterwards used to move our cube on the scene. More specifically "zValue" will define the translation of the model on the Z axes, "xAngle" and "yAngle" instead will store the rotation angle of the cube respectively around the X and Y axis, and finally "xSpeed" and "ySpeed" will be used to register the rotation speed (as the names suggest always around the X and Y axis), so to have a pretty effect, i.e. our 3D object in continuous movement on the scene (even when the user will have ceased to interact with the keys).

OK, with this we have ended the initialization routine and so we can jump just to the user interface event listeners:

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

onKeyUp: function (key) {
  if ((this.xSpeed == 0) && (this.ySpeed == 0) && 
      !this.ui.isKeyDown("O") && !this.ui.isKeyDown("P") && 
      !this.ui.isKeyDown("A") && !this.ui.isKeyDown("Z") && 
      !this.ui.isKeyDown("N") && !this.ui.isKeyDown("M")) {
    this.ui.animateRate = 0;
  }
},

onAnimate: function (dt) {
  if (this.ui.isKeyDown("O")) this.zValue += 1.0 * dt;
  if (this.ui.isKeyDown("P")) this.zValue -= 1.0 * dt;

  if (this.ui.isKeyDown("A")) this.xSpeed -= 1.0;
  if (this.ui.isKeyDown("Z")) this.xSpeed += 1.0;
  this.xAngle += this.xSpeed * dt;

  if (this.ui.isKeyDown("N")) this.ySpeed -= 1.0;
  if (this.ui.isKeyDown("M")) this.ySpeed += 1.0;
  this.yAngle += this.ySpeed * dt;

  this.ui.postDrawEvent();
},

As concern the "onKeyDown" and the "onKeyUp" routines we have the same keys conduct seen in the past tutorial (same smart system that allows to operate more contemporary pressures), with only some key more handled and an additional control related to the component movement speed.
Looking at the "onAnimate" routine you can study in deep the actions linked to each key chosen: "O" and "P" are used to vary the amount of our cube translation along the Z axis, thus modifying the zoom level of the component, "A", "Z", "N" and "M" instead pilot the rotation around the the X and Y axis, but in different way compared to what we have seen so far for the others animation events: the keys interactions in fact don't modify directly the values related to the rotation angles, but increase (or decrease) the speed values (stored in the "xSpeed" and "ySpeed" variables earlier introduced), that only later will alter the "xAngle" and "yAngle" values. This implementation allows to keep on moving our 3D model in the scene also at keypress terminated.

Well, this explained you can pass to the last routine, the "onDraw", as usual.
All the code lines here have already been explained in the previous lessons, there are only little changes (for example like that concerning the transformation stack calls), but the main skeleton of this stage remains the same as always.
However needs to be shown the attribute pointer set up related to the cube texture coordinates (i.e. the cube vertex normals, as explained at the beginning ) passed to the vertex shader, here in the follow reported:

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

Note that for the attach of the texture coordinates buffer above reported (and recognizable by the index value set to 1, the value earlier chosen in the shader program just for this attribute) here point to the same SpiderGL vertex buffer used to define our object 3D spatial positions. This is made possible by the fact that the cube rendered in this example is centered in (0,0,0), so his vertices coordinates can serve as easily as vertices normals.
Once sent the vertex positions and the texture coordinates to the shaders the "onDraw" work is practically finished, you just have to render the primitives that compose the cube on the scene, following the index buffer previously defined, and you're done.

OK, with this the source code and the tutorial analysis end. Now the reader should be able to instantiate and to manage the SpiderGL textures in all forms, both 2D and cube map.
The next tutorial will introduce a new interesting argument: the frame buffer!