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. #1 by Brad on January 21, 2013 - 22:51

    Great work – you should lend your talents to http://www.vortexmath.org and http://www.vortexspace.org to help them create a WebGL VxBrowser.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: