Archive for category General

WebGL Basics 7 – Colored torus

After the summer pause and some weeks spent to re-work the perspective projection in the part 5, it’s time to add colors to our shape. By the way, I created a page to link all posts related to this mini WebGL tutorial. Thanks to the authors of smashinglabs and learningwebgl for referencing my blog.

Introduction

In the part 6, we defined a toroidal shape colored in white. With one uniform color, the fragment shader is simplistic as it returns always the same constant value. As the faces cannot be differentiated (white on white without visible edges), it is interesting to note that the depth-ordered drawing of faces is irrelevant, i.e. a face wrongly rendered over a nearer face is not noticeable.

In this post, we add colors to the toroidal shape. The necessary changes can be summarized as following:

  • Activation of depth test (not activated until now as it was not necessary)
  • Creation and storage of a table of colors on the torus object
  • Transfer of the color values to the shaders
  • Support of vertex coloring in the fragment and vertex shaders

The WebGL context in the modified HTML page looks like this:

Depth test / Z buffering

One recurrent issue, when drawing a set of 3D faces onto a 2D screen, is to display the visible faces and to hide the faces masked by other ones. The algorithm that achieves this task in WebGL is called Z Buffering. It works by keeping one depth value for each rendered pixel. This depth value can be changed when a pixel belonging to a 3D face is drawn: if the depth (i.e. z coordinate) of the pixel being rendered is lower than the currently stored depth value, nothing is drawn, but if the depth is greater, what means that the pixel is on a visible part of the face, the pixel is drawn and the stored depth value is updated with the depth of the drawn pixel.

In Javascript, the depth test is enabled by:

// Enables Z-Buffering
gl.enable(gl.DEPTH_TEST);

The default settings of the depth test parameters fulfill the most common needs, i.e. a value in the Z Buffer is updated if it is "LESS" than the stored depth value.

Creation of a table of colors

The function makeTorus, introduced in part 6, needs extensive re-working. First, it must return an object that stores the vertices and the colors, i.e. it cannot be a simple array. Second, color values must be created for each vertex. The new Javascript code looks like:

// Creates a 3D torus in the XY plane, returns the data in a new object composed of
//   several Float32Array objects named 'vertices' and 'colors', according to
//   the following parameters:
// r:  big radius
// sr: section radius
// n:  number of faces
// sn: number of faces on section
// k:  factor between 0 and 1 defining the space between strips of the torus
function makeTorus(r, sr, n, sn, k)
{
  // Temporary arrays for the vertices, normals and colors
  var tv = new Array();
  var tc = new Array();
  
  // Iterates along the big circle and then around a section
  for(var i=0;i<n;i++)               // Iterates over all strip rounds
    for(var j=0;j<sn+1*(i==n-1);j++) // Iterates along the torus section
      for(var v=0;v<2;v++)           // Creates zigzag pattern (v equals 0 or 1)
      {
        // Pre-calculation of angles
        var a =  2*Math.PI*(i+j/sn+k*v)/n;
        var sa = 2*Math.PI*j/sn;
        var x, y, z;
      
        // Coordinates on the surface of the torus  
        tv.push(x = (r+sr*Math.cos(sa))*Math.cos(a)); // X
        tv.push(y = (r+sr*Math.cos(sa))*Math.sin(a)); // Y
        tv.push(z = sr*Math.sin(sa));                 // Z
      
        // Colors
        tc.push(0.5+0.5*x);  // R
        tc.push(0.5+0.5*y);  // G
        tc.push(0.5+0.5*z);  // B
        tc.push(1.0);  // Alpha
      }

  // Converts and returns array
  var res = new Object();
  res.vertices = new Float32Array(tv);
  res.colors = new Float32Array(tc);
  return res;
}

The value returned by the function is a new complex object that contains two arrays, one for the vertices and one for the colors. Both arrays have the type Float32Array to be compatible with the subsequent WebGL function calls.

In order to fill the colors array, RGBA values are computed by simply transforming the corresponding vertex coordinates, i.e. each color component R,G,B depends respectively on x, y, z and the alpha component is set to 1.0 (not transparent). As a result, because space coordinates are obviously continuous, the colors defined all around the torus do not show any visible discontinuity. Apart from this nice smoothness property, the exact computation of the color components is not important.

Data transfer to shaders

First of all, the new makeTorus function is called from updateObject (called each time the torus parameters are changed) and the returned data is put in global arrays:

// Creates the object - changed in part 7
var obj = makeTorus(0.7, sradius, numsegs, numssegs, interleave);
vertices = obj.vertices;
colors = obj.colors;

The Javascript code that deals with the transfer of color components to the shaders is very similar to the code introduced in the last posts for the vertices:

// Connects colors array to vertex shader via the 'pcolor' attribute 
var cattrib = gl.getAttribLocation(program, 'pcolor');
if(cattrib == -1){alert('Error retrieving pcolor address');return;}
gl.enableVertexAttribArray(cattrib);
var cbuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cbuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
gl.vertexAttribPointer(cattrib, 4, gl.FLOAT, false, 0, 0);

The data is piped to the attribute variable pcolor of type vec4 (see next section). Color components are floats sent 4-by-4, as defined in the vertexAttribPointer function.

Changes in shaders

The WebGL pipeline is such that data in a Javascript structure can only be passed to the vertex shader, not to the fragment shader, even for color data. Color data must hence be forwarded from the vertex shader to the fragment shader (after a transformation when necessary). The same principle applies to our code: color components are piped to pcolor in the vertex shader, and are forwarded via the ‘varying‘ variable forward_color:

// GLSL ES code to be compiled as vertex shader
vertexShaderCode=
'attribute vec3 ppos;'+
'attribute vec4 pcolor;'+
'varying mediump vec4 forward_color;'+
'uniform mat4 mvp;'+
'void main(void) {'+
'  gl_Position = mvp * vec4(ppos.x, ppos.y, ppos.z, 1.0);'+
'  gl_PointSize = 2.0;'+  // Inserted in part 6
'  forward_color = pcolor;'+
'}';

The definition of the forward_color variable is exactly the same in both shaders (varying mediump vec4 forward_color). The gl_FragColor variable (output color of the fragment shader) shares the same basic data type mediump vec4. The qualifier "mediump" is the precision of the forwarded color components (floats with 14 bits mantissa) and is necessary in order to make forward_color compatible with gl_FragColor.

According to the GLSL ES 2.0 specification, varying is used for the linkage between a vertex shader and a fragment shader for interpolated data.

Once the variables are correctly defined, the code is straightforward:

// GLSL ES code to be compiled as fragment shader
fragmentShaderCode=
'varying mediump vec4 forward_color;'+
'void main(void) {'+
'  gl_FragColor = forward_color;'+
'}';

Results

As usual, the result can be seen on-line. It should look like this:

The following picture was obtained by reducing the number of segments of the torus and by using the ‘TRIANGLES‘ rendering mode:

Please note how the colors are shaded on each triangle. This is the standard color rendering scheme in WebGL: the color components are linearly interpolated between the fixed values given for each vertex. This is the reason why colors are defined per vertex and not per face.

Summary

Not a lot of mathematics in this part. The main points to remember:

  • Z-Buffering, a.k.a. depth test, allows to correctly draw overlapping faces and is enabled by calling gl.enable(gl.DEPTH_TEST)
  • Our makeTorus function now computes one color (RGBA – 4 components) per vertex and returns this data in a complex object containing 2 arrays of floats (vertices and colors)
  • The array of colors is bound to the vertex shader using the same set of WebGL functions we used for the array of vertices (i.e. gl.getAttribLocation, gl.enableVertexAttribArray, gl.createBuffer, gl.bindBuffer, gl.bufferData, gl.vertexAttribPointer)
  • Color data is transfered to the vertex shader via a new attribute vec4 input variable,
  • It is forwarded to the fragment shader via a new varying mediump vec4 variable defined using the same name and type in both shaders.
  • There is a smooth color shading between the points of the triangles when rendered.

Stay tuned for next part.

Advertisements

1 Comment

WebGL Basics 6 – Torus-shaped strip

In the previous post we looked at complex mathematical transformations in order to set-up an environment where the observer can turn and move in all directions. This post deals with the creation of a torus object and the way WebGL renders a strip of triangles.

Introduction

Our test page is now ready for more complex objects. This part 6 is dedicated to:

  • The creation (per Javascript code) of a torus composed of one long strip rolled around it
  • Some changes in the code to demonstrate different rendering modes and the dynamic control of the object being rendered

The results looks like a ring-shaped spring:

Torus surface formulas

First of all, we need to determine the coordinates of a point on the surface of the torus. In the previous post we saw the rotation formulas, so we use them again. Namely the torus is defined as a circle in the XZ plane that rotates around the Z axis (r is the radius of the big circle, θ the rotation angle around Z, sr the section radius, α the angle of the point in the section):

We start by defining the coordinates of the point on the section circle centered at (r,0,0) with radius sr (vector on the right hand side) and let it rotate around the Z axis, hence multiply the coordinates by a rotation matrix (see previous post for the formula):

The result of the product gives the coordinates of a point on the surface of the torus:

Creation of the toroidal strip

The idea here is to compute the coordinates of the points that form a long strip rolled on the surface of the torus. The strip itself is a made of a long broken zigzag line as illustrated hereafter:

By rendering the coordinates as a “TRIANGLE_STRIP“, WebGL will then use the points successively 3-by-3 to build a continuous strip of connected triangles.

The formulas giving the successive point coordinates are derived from the previous section, with a tweaked definition of the rotation angles. If we set n to be the number of times the strip is rolled around the torus, and sn the number of triangle pairs on one round of the strip. Then for each round, α goes from 0 to with steps of 2π/sn (every two points, due to the zigzag pattern). θ is continuously incremented during the complete torus computation such that the strip rolls around, θ goes hence from 0 to with a step equal to 2π/sn at each new round, and 2π/(sn*n) at each new zigzag pattern.

Finally, we introduce an interleave factor to control the space between one round of the strip and the next one. It is defined as a multiplication factor of the width of the strip, i.e. an interleave equal to 1 would leave no space between 2 successive rounds of the strip.

Changes in the code

The function that gives the torus coordinates is as following:

// Creates a 3D torus in the XY plane, returns the vertices in a Float32Array
// r:  big radius
// sr: section radius
// n:  number of faces
// sn: number of faces on section
// k:  factor between 0 and 1 defining the space between strips of the torus
function makeTorus(r, sr, n, sn, k)
{
  // Temporary arrays for the vertices and the normals
  var tv = new Array();
  
  // Iterates along the big circle and then around a section
  for(var i=0;i&amp;lt;n;i++)
    for(var j=0;j&amp;lt;sn+1*(i==n-1);j++)
    {
      // Pre-calculation of angles
      var a =  2*Math.PI*(i+j/sn)/n;
      var a2 = 2*Math.PI*(i+j/sn+k)/n;
      var sa = 2*Math.PI*j/sn;
      
      // Coordinates on the surface of the torus  
      tv.push((r+sr*Math.cos(sa))*Math.cos(a)); // X
      tv.push((r+sr*Math.cos(sa))*Math.sin(a)); // Y
      tv.push(sr*Math.sin(sa));                 // Z
      
      // Second vertex to close triangle
      tv.push((r+sr*Math.cos(sa))*Math.cos(a2)); // X
      tv.push((r+sr*Math.cos(sa))*Math.sin(a2)); // Y
      tv.push(sr*Math.sin(sa));                  // Z
    }

  // Converts and returns array
  return new Float32Array(tv);
}

The other changes are straightforward. First new controls are created in the HTML part to let the user play with the torus parameters. Second a new function updateObject is created, which gathers the code called each time the torus is re-computed:

// Updates object with global parameters
function updateObject()
{ 
  // ----------- added in part 6 --------------------
 
  // Gets the torus factor from the HTML page
  var interleave = parseFloat(document.getElementById('interleave').value);
  var numsegs = parseFloat(document.getElementById('numsegs').value);
  var numssegs = parseFloat(document.getElementById('numssegs').value);
  var sradius = parseFloat(document.getElementById('sradius').value);

  // Creates the object in &amp;quot;vertices&amp;quot;
  vertices = makeTorus(0.7, sradius, numsegs, numssegs, interleave);

  // ---------- moved from function start() -----------

  // Gets address of the input 'attribute' of the vertex shader
  var vattrib = gl.getAttribLocation(program, 'ppos');
  if(vattrib == -1)
   {alert('Error during attribute address retrieval');return;}
  gl.enableVertexAttribArray(vattrib);

  // Initializes the vertex buffer and sets it as current one
  var vbuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vbuffer);

  // Puts vertices to buffer and links it to attribute variable 'ppos'
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  gl.vertexAttribPointer(vattrib, 3, gl.FLOAT, false, 0, 0);
}

As usual you can play with the page on-line and experiment different combinations of parameters.

Summary

The main points to remember:

  • A torus has been defined as a sequence of point coordinates according to a zigzag pattern
  • WebGL renders these points as a continuous strip of triangles using the mode TRIANGLE_STRIP in drawArrays
  • New controls allow the user to change parameters of the torus, which is re-calculated on-the-fly

Stay tuned for the next part.

3 Comments

About the name

Some trivial (and a little pedantic) words about the name of this blog: blogoben.

Basically, in English, the name must be just read as "Blog o’ Ben", a shorted form of "Blog of Ben" (Benjamin is my first name).

By the way, there is another blog with almost the same name on WordPress. So if you are looking for the evolution of a kid named Ben, here is the wrong place!

In French, what is meant is "Blog au Ben", which has almost the same meaning as the English version. The grammar is not completely respected (one translation could be "blog belonging to the Ben"), the spelling is incorrect as well, but it sounds definitely somehow friendly! The way the name is written fits in a strange habit in French about the wrong spelling of Internet names (for example "kelkoo.com" is a quite modern spelling of "quel coût").

In German, something I realized later, the name can be read as "Blog oben", what can be translated into "blog at the top"… It is obviously very far from my ambition with this blog, but remains an interesting coincidence.

Leave a comment

My first post

Hello World!

This is the first post on my blog.

Blogging is a brand new activity for me, so I hope I will find the time and energy to fill these pages with some interesting material. Time will show if such an activity can be sustained in the long term between work and family.

In the future you can expect to read about all sorts of things around technology and science here. So stay tuned.

I’m typing right now using Blogilo under Ubuntu, which seems to be a very comprehensive and feature-rich blogging client. It allows me to blog off-line.

See you next time.

Ben

Leave a comment

%d bloggers like this: