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 4 : Vertex-Index Buffers And Shader Programs

This tutorial gives an overview of how SpiderGL rendering system works, showing how to draw a static colored triangle and a square on the screen. We will see how SpiderGL manages vertex and index buffers and shaders.

View DemoDownload Source

The rendering of even a simple triangle mesh, as known, needs to instantiate basic structures, such as buffers, view matrices, and shaders.
To help to operate on these various entities, SpiderGL provides some interesting tools that can come in handy also in a simple example like this (where you have just two single colored objects, a triangle and a square).

Almost all the relevant changes in the source code of this tutorial compared to the previous, are placed in the "CanvasHandler.prototype" block, while is virtually unchanged the HTML structure of the page and the others JavaScript methods.
Compared with the Lesson 3 all the various event listener routines have been removed since are not needed in this tutorial. The "onInitialize" has been greatly expanded and the method delegated to the rendering, the "onDraw", has been introduced.

As just mentioned the main innovations introduces by SpiderGL in this lesson concern two specific elements: the shaders (vertex and fragment) and the buffers (vertex and index).
So let's start to observe how we can setup with SpiderGL, the shaders, looking the source code in the "onInitialize" method.
Just after the variable declaration "gl", (that refer to the WebGL context) we can find the shaders definition. As we know, a shader is not written in JavaScript, but in the special language GLSL (Graphics Library Shading Language). Since both the vertex shader and the fragment shader here implemented does not contain any special SpiderGL functionalities but only standard GLSL code we can comment them briefly.

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

The first shader source code is that one of the vertex shader (which runs once for each geometric vertex given to the graphics processor, and has the purpose to transform each vertex 3D position of the World into the 2D coordinate in screen space, in addition to enable full control over the lighting, and color computations of the scene). In our case, next to the line code to tell the graphics card that the desired precision is floating point, associated with each vertex we have:

  • two input attribute variables, "aPosition" and "aColor", that allow to specify for each vertex its position and color;
  • two input uniform variables, "uModelViewProjectionMatrix" and "uColorCorrection" containing respectively the model view projection matrix and a variable for the color adjustment;
  • one varying variable, "vColor", to store the current color value (remember that the varying variables provide an interface between the vertex and the fragment shader: their value, defined in the vertex shader, is interpolated over the chosen primitive during the rasterization, and then passed at the fragment shader).
The rest code of the shader in the "main" function just calculates the right color of each vertex, and multiplies the vertex position by the model view projection matrix, pushing out the results to the next stage of the pipeline.

The fragment shader source code is reported below:

var fsSource = "\
  precision highp float;                                 \n\
                                                         \n\
  varying   vec3 vColor;                                 \n\
                                                         \n\
  void main(void) {                                      \n\
    gl_FragColor = vec4(vColor, 1.0);                    \n\
  }                                                      \n\
";

In this example, the fragment shader does pretty much nothing: next to the usual code line to set the floating-point numbers precision and variable declarations, the "main" just assign the final color for each fragment by adding to the 3-dimensional RGB color varying vector in input, the fourth component, used for the alpha parameter (that is a measure of opaqueness: from 0 for transparent to 1 for totally opaque).

Now, that we have seen the shaders source code is time to take a look to the shaders initialization, through the creation of the two SpiderGL shaders objects and of the SpiderGL shader program where to attach them:

var vShader = new SglVertexShader(gl, {source: vsSource});
var fShader = new SglFragmentShader(gl, {source: fsSource});

this.shaderProgram = new SglProgram(gl, {
  autoLink   : true, 
  shaders    : [vShader, fShader],
  attributes : {
    aPosition  : 0, 
    aColor     : 1 
  },
  uniforms   : {
    uModelViewProjectionMatrix: SglMat4.identity(),  
    uColorCorrection: 1.5 
  }
});

The creation of the two SpiderGL shader objects is made through the SpiderGL constructors "VertexShader" and "FragmentShader" (belonging to the namesakes class of the SpiderGL component WebGL), wrapper for the WebGL shader which take in input the WebGL context ("gl", earlier defined) and some further options (here "source", that gets/sets the shader source code).
Concerning the instantiation of the SpiderGL shader program object, the used method "program" (wrapper for the WebGL program objects) is placed in the same name class Program of the SpiderGL component WebGL, and take by input the WebGL context and some other optional parameters. Here is a complete list of those possible (for these options SpiderGL often provides a default value, so it is not necessary to specify all of them):

handle-the WebGL program, that if present will be used, together to his attached shaders, as the wrapped WebGLProgram (otherwise a new one will be created);
autoLink-a flag that, if true, enables the program to be linked automatically (whenever shaders are added or removed, or vertex attribute indices change);
shaders-the array of SpiderGL shader objects to attach to the program;
attributes-the object where each property has the name of a vertex shader attribute and whose value is the attribute index to wich the vertex attribute will be bound;
uniforms-the object where each property has the name of a uniform variable and whose value is the uniform default value.

In this example "autoLink" is set to true and the "shaders" field contains our vertex and fragment shader object names (vShader and fShader). For the position and color attributes were choose respectively the values 0 and 1 whom to be bound, and to our uniform variables were pass an arbitrary selected numeric value for the color correction, and a 4x4 dimensional identity matrix (gets by the SpiderGL math method "identity" placed in the Mat4 class) for the model view projection matrix.
Once this function has set up the program and attached the shaders, it gets a reference to the attribute and the uniform variables and ends his task.

At this point we can move on the next step, that is the buffers initialization.
The first one provides the 3D spatial positions for the triangle we want to draw:

var triangleVertices = [
  0.0, 1.0, 0.0, 
 -1.0,-1.0, 0.0,
  1.0,-1.0, 0.0
];

this.triangleVertexPositionBuffer = new SglVertexBuffer(gl, {
  data  : new Float32Array(triangleVertices), 
  usage : gl.STATIC_DRAW 
});

After defined the vertex positions as a JavaScript list (as points of an isosceles triangle with his centre at 0), thanks to the constructor "VertexBuffer" (placed in the class VertexBuffer of the SpiderGL component called WebGL), is created the vertex buffer object to be passed to the graphics card. This method represents a wrapper for a WebGL buffer to be bound to the "WebGLRenderingContext.ARRAY_BUFFER" target. It takes as input the usual WebGL rendering context and some further options:

handle-the WebGL buffer, that if provided will be wrapped and used together to his size and usage attributes (otherwise a new internal buffer will be created);
data-the buffer content to be set (if present, the data will be set both if a handle is provided or internally created);
size-the buffer size to be set (if present, it will be set both if a handle is provided or internally created, moreover if data parameter is present, the size field will be ignored);
usage-the WebGL buffer usage hint parameter;

In our initialization the "data" parameter is fundamental because define the type of data of the buffer (in this case a "Float32Array" JavaScript object). The other only option checked is "usage" (setted with the "STATIC_DRAW" constant, that indicates the data store contents will be modified once and used many times as source for the drawing commands), while the others are not made explicit (and so the SpiderGL will use their default values).

In the following code we see another buffer initialization, this time for the triangle color attributes:

var triangleColors = [
  1.0, 0.0, 0.0,  // red color
  0.0, 1.0, 0.0,  // green color
  0.0, 0.0, 1.0   // blue color
];

this.triangleVertexColorBuffer = new SglVertexBuffer(gl, {
  data  : new Float32Array(triangleColors), 
  usage : gl.STATIC_DRAW 
});

Like for the positions, the values provided for the colors are stored in a JavaScript list, one set of values for each object vertex (the colors are specified with three components: a red, a green and a blue one). Then, as previously done, the SpiderGL color buffer is constructed using the "triangleColors" list.

Well, with this the triangle structural elements are setup, so we can define the second object: the square.
First of all, we have chosen to draw the square as a set of simple triangle primitives by employing an index buffer.
Next, the code explored so far shows a basic builder of a plain object, with the simplest utilization of the buffers. The square is instantiated in a way different from the previous one:

var squareVerticesAndColors = [
  1.0, 1.0, 0.0, // 1° vertex position
  0.3, 0.3, 1.0, // 1° vertex color 
 -1.0, 1.0, 0.0, // 2° vertex position
  0.3, 0.3, 1.0, // 2° vertex color 				 
  1.0,-1.0, 0.0, // 3° vertex position
  0.3, 0.3, 1.0, // 3° vertex color 				
 -1.0,-1.0, 0.0, // 4° vertex position
  0.3, 0.3, 1.0  // 4° vertex color 				
];

this.squareVertexPositionColorBuffer = new SglVertexBuffer(gl, {
  data  : new Float32Array(squareVerticesAndColors), 
  usage : gl.STATIC_DRAW 
});

The above JavaScript list is created in the interleaving mode, i.e. alternating between the vertex position and the vertex color values. That means that a single SpiderGL vertex buffer this time will contain both the 3D spatial coordinates and the RGB color values for each vertex. The buffer definition is the same used for the triangle vertex buffers (note that this time the colors are the same for each vertex).
Let's build the square index buffer:

var squareIndices = [
  0, 1, 2, 
  1, 2, 3,
];

this.squareVertexIndexBuffer = new SglIndexBuffer(gl, {
  data: new Uint16Array(squareIndices), 
  usage: gl.STATIC_DRAW 
});

In this case each number in the JavaScript list corresponds to an index of a vertex buffer send to the vertex shader.
After you define the list of the vertex indices for position and color buffers, in a very similar way to the SpiderGL vertex buffer, thanks to the constructor "IndexBuffer" (placed in the class IndexBuffer of the SpiderGL component called WebGL), is created the index buffer object to pass to the graphics card.
This method also represents a wrapper for a WebGL buffer to be bound to the "WebGLRenderingContext.ARRAY_BUFFER" target. It takes as input the WebGL rendering context "gl" and the same further options of the SpiderGL vertex buffer.
"data" is again fundamental because defines the type of data of the buffer (in this case a "Uint16Array" JavaScript array).

So, the initialization stage is finish and it is the time to take a look at the "onDraw" routine! As the name suggest this is the block of code that runs every time we want to draw something on the screen.
Scrolled down the source code, we can find some basic WebGL settings (all implemented by calling methods of the WebGL context object "gl"):

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, w, h);
gl.enable(gl.DEPTH_TEST);

After this initialization, the uniform variable "uModelViewProjectionMatrix" of the vertex shader is set up. This variable refers to the model view projection matrix and, in general, it needs to be updated each time the "onDraw" is called to account for changes in view settings of the scene (perspective projection type, position of the observer camera, position of the model, etc.):

this.shaderProgram.bind();

var pMatrix = SglMat4.perspective(sglDegToRad(45.0),w/h,0.1,10.0);
var mvMatrix = SglMat4.translation([-1.5, 0.0, -5.0]);
var mvpMatrix = SglMat4.mul(pMatrix, mvMatrix);

this.shaderProgram.setUniforms({
  uModelViewProjectionMatrix: mvpMatrix
});

The first line of the previous code block performs the binding with the shader program through the SpiderGL shader program object related method "bind" (the same as should do for WebGL).
In the following three lines using the methods collected in the SpiderGL math Mat4 class is created the 4x4 dimensional model view projection matrix that define the scene: first of all a perspective projection matrix is instantiated (whereby of the "perspective" method with in input: the vertical field-of-view angle in radians, the projection plane aspect ratio, the distance of the near and far clipping planes), then a translation matrix is made (by means of the function "traslation" with in input the offset 3D vector), and finally the resulting transformation matrix is obtained (multiplying with "mul" the previous two).
At the end of all, using another SpiderGL shader program object related method ("setUniforms"), the just built model view projection matrix is provided to our shaders (as uniform variable).

Now, after the uniform, you can finish to set up the attribute variables, and more precisely to link to them the appropriate vertex and index buffers previously packed.
Start with those relative to our object triangle positions:

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

The SpiderGL "vertexAttribPointer" method in the block of code above (belonging to the VertexBuffer class of the component WebGL) is able to latch the WebGL selected vertex attribute pointer with the internal buffer (here "triangleVertexPositionBuffer").
The effect of this method is to bind the vertex buffer, call the WebGL "vertexAttribPointer" with the provided parameters, and finally run the WebGL "enableVertexAttribArray" command (practically it replaces three different classic function calls, simplifying the coding consistently). Let's a look to all the parameters that we can find in this procedure:

index-the attribute index selected in the constructor (in our case 0 for the position attribute);
size-the attribute size (here 3 because every vertex position is defined in the buffer by three spatial values);
glType-the attribute base type (floating-point numbers for us);
normalized-a boolean value, true if the attribute has an integer type and must be normalized (false by default, even if it would not be necessary to normalize in our example);
stride-the bytes from the beginning of an element and the beginning of the next one (in this case the special value 0 indicates that our set of values are adjacent);
offset-the offset (in bytes) from the beginning of the buffer (0 in our example, because the first useful element is the first of the buffer too);
enable-a boolean value, that if true, enabled the vertex attribute array at the earlier specified index number (obviously true for our position buffer).

Well, after have completed the setting of the position attribute pointer, binding and enabling the related vertex buffer of the triangle to draw, proceed similarly with the color attribute pointer:

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

Since the buffer to link with the color attribute is again a vertex buffer, the operations to do to latch the WebGL selected vertex attribute pointer with the internal buffer (this time "triangleVertexColorBuffer") are exactly the same of previously.
As you can see in the above code the only difference compared to the position buffer attribute pointer configuration is related to the "index" number, here set up to 1, or rather the value chosen earlier in the shader program constructor (note that the attribute "size" value here is 3 again, because every vertex color is defined in the buffer by the three RGB values).
Once this is done, all the attributes related to the triangle we want to render are latched with the rights buffers, so you can pass to the next step: draw!

gl.drawArrays(gl.TRIANGLES, 0, 3);

As you can see in this code box the drawing instruction is the classic WebGL command used to render simple array of vertex (i.e. "drawArrays", with triangles as kind of primitive, starting index item set to 0 and number of items to draw equals to 3).
At this point WebGL processes the data that you have previously given it in the form of attributes and uniform variables, and passes it along to the shaders. The triangle vertices are drawn in the canvas (slightly displaced on the left, due to the translation operation previously performed on the model view projection matrix), with their related different colors (linearly interpolated in the triangle area thanks to the color varying variable placed in the shaders).

Ok, this done you can go to set and draw our second object, the square.
Previous to set the square buffers attribute pointer is needed some operation on the uniform variable related to the model view projection matrix:

SglMat4.translate$(mvMatrix, [3.0, 0.0, 0.0]);
	
mvpMatrix = SglMat4.mul(pMatrix, mvMatrix);

this.shaderProgram.setUniforms({
  uModelViewProjectionMatrix: mvpMatrix
});

The SpiderGL "translate$" method will allow us to draw our square slightly displaced on the right on the canvas, with a nicest final appearance (note here the use of an "in place" function call, recognizable by the appropriate symbol "$").

Now you can pass to set the square position and color attribute, remembering that the buffer related with these attributes, the "square VertexPositionColorBuffer", was instantiate in the interleaving mode (i.e. alternating vertex position and color values):

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

this.squareVertexPositionColorBuffer.vertexAttribPointer({
  index     : 1, 
  size      : 3, 
  glType    : gl.FLOAT, 
  normalized: false, 
  stride    : 6 * SGL_SIZEOF_FLOAT32, 
  offset    : 3 * SGL_SIZEOF_FLOAT32,
  enable    : true 
});

So, how you can see in the above code box, unlike in the case of triangle, here we have two blocks of attribute pointer setting related to the same buffer, one referred to the square position (the first one, easily recognizable from the position "index" attribute 0), and one referred to the square color (the second one, that one with the color "index" attribute, i.e. 1).
The SpiderGL method "vertexAttribPointer" is the same earlier parsed, therefore requires no further explanations, but you need to pay attention to the "stride" and "offset" parameters, both edited to point the appropriate elements in the buffer.
Thus for the position values in the selected buffer we have "offset" 0 (indeed the first element is a vertex position) and "stride" equals to the memory amount (in bytes) taken up by six elements (three spatial coordinates and three color values) corresponding to the space from the first element of one set of position value and the next.
For the same reason in the color attribute pointer set up we have "stride" equals to the memory amount taken up by six elements (corresponding to the space from the first element of one set of color RGB value and the next), and "offset" equals to the memory amount filled by three elements (corresponding to the space from the beginning of the buffer and the first element of the first set of color RGB value).

Well, this done the work (and the tutorial) is pretty finish, we just have to render our square!
To draw a mesh using an index buffer (like in this case) our library provides a method, "drawElements", related to the index buffer SpiderGL object, and so placed in the IndexBuffer class of the WebGL component:

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

This function is able to bind the selected index buffers (in our case "squareVertexIndexBuffer") and call the WebGL method drawElements with the provided parameters. As shown in the above source code block these parameters include:

glMode-the WebGL primitive type (triangles in our case);
count-the number of elements to draw (six in this example, because our square is composed from two triangle each with three vertex);
glType-the index base type (unsigned short integer numbers for us);
offset-the offset (in bytes) from the beginning of the buffer (0 in this example, because the first useful index element is the first of the buffer too);

Ok, now WebGL can process too the square data given to it in the form of attributes and uniform variables, and passes it along to the shaders. The square vertices are drawn in the canvas with their related colors (this time all equals), and after two additional function calls (to unbind the SpiderGL shader program and to disable the depth test enabled at the beginning of the "OnDraw" routine), the rendering ends and the code execution terminates.

this.shaderProgram.unbind();

gl.disable(gl.DEPTH_TEST);	

So, this dense lesson finishes here... Now you should have understood how to instantiate a shader program and how to set up (in some different way) the vertex and index buffers with our library.
In the next tutorial will be introduced some SpiderGL animation features, and together also his transformation stack!