Notes for Tuesday and Thursday March 3-5 class -- Making a mesh

 

Looking through the homeworks, I see that quite a few students have fallen behind. So rather than give you new work this week, I am going to let you use the time between now and Thursday March 12 to catch up on assignment four, without any late penalty.

If you are having trouble, this is your chance to reach out to me or to the grader for help.

One note about hw4. I had written the code for translate() as:

   this.translate = function(x, y, z) {
      let m = [1,0,0,0, 0,1,0,0, 0,0,1,0, x,y,z,1]; // (1) CREATE TRANSLATION MATRIX.
      value = multiply(m, value);                   // (2) MULTIPLY TO CHANGE OBJECT value.
      return this;
   }
You might get better results by reversing the order of the argument in multiply() as follows:
   this.translate = function(x, y, z) {
      let m = [1,0,0,0, 0,1,0,0, 0,0,1,0, x,y,z,1]; // (1) CREATE TRANSLATION MATRIX.
      value = multiply(value, m);                   // (2) MULTIPLY TO CHANGE OBJECT value.
      return this;
   }
and also do the same when implementing the rotate and scale methods. Depending on your implementation of multiply(), that ordering might result in more intuitive motion.

Sometime in the next day or so, I am going to put on-line the notes from our lectures this week on triangle meshes. But there is not yet any corresponding homework assigment, while we are waiting for people to catch up.

For those of you who have been keeping up, feel free to add more to your assignment for extra credit. And of course you can get a jump on the new material, after I have put that up on-line.

Creating a mesh from a triangle strip

In order to send vertex data down from the CPU to the GPU for rendering, we need to tell the GPU how many values are in each vertex. If we store only x,y,z position, then each vertex will have 3 floating point values. On the other hand, if we also store normal, then each vertex will have 6 floating point values: x,y,z, nx,ny,yz

We call this value the "stride" of the vertex buffer, since it indicates how large is the stride from one entry in the vertex buffer to the next. It is useful to declar a variable that indicates how many floating point values are in each vertex:

   let stride = 6;

We can send vertex data down from the CPU to the GPU by sending individual triangles as follows, but this is usually inefficient, since for most shapes, multiple triangles share the same vertex:

    gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length/stride); 
In many cases, it is better to form a triangle strip, which zigzags as follows:
    1\--3\--5\--7\--9\
    | \ | \ | \ | \ | \ ....
    0--\2--\4--\6--\8
This allows most vertices to be shared between 3 triangles, so we don't need to send as much data down from the CPU to the GPU.

We can create a shape as a 2D rectangular mesh of vertices by sending a single triangle strip for first the first row, then the second, then the third, etc. In order to avoid problems of spurious triangles as between the end of one row and the beginning of the next, we add two extra vertices, to create degenerate triangles that won't show up in the final rendering.

Note in the below implementation taht we are passing in, as the first argument is a function that takes (u,v) as arguments and returns the data for a single vertex:

   let createTriangleMesh = (uvToVertex, nCols, nRows) => {
      let mesh = [];
      let appendVertex = p => {
         for (let n = 0 ; n < p.length ; n++)
            mesh.push(p[n]);
      }
      for (let row = 0 ; row < nRows ; row++) {
         let v0 =  row    / nRows,
             v1 = (row+1) / nRows;
         for (let col = 0 ; col <= nCols ; col++) {
            let u = col / nCols;
            appendVertex(uvToVertex(u, v0));
            appendVertex(uvToVertex(u, v1));
         }
         appendVertex(uvToVertex(1, v1));
         appendVertex(uvToVertex(0, v1));
      }
      return mesh;
   }
For example, if we want to create a unit sphere with 20 columns and 10 rows, and with position (x,y,z) and normal (nx,ny,nz) at each vertex, we could write:
   let vertices = createTriangleArray(uvToSphere, 20, 10);
where:
   let uvToSphere = (u, v) => {
      let theta = 2 * Math.PI * u,
          phi = Math.PI * (v - .5),
          x = Math.cos(theta) * Math.cos(phi),
	  y = Math.sin(theta) * Math.cos(phi),
	  z = Math.sin(phi);
      return [ x,y,z, x,y,z ]; // RETURN POSITION AND NORMAL
   }
Since we now have multiple attributes -- poth position and normal -- to send down the the vertex shader, in our Javascript we now need to map out the structure of each vertex in the vertex buffer more clearly:
   let aPos = gl.getAttribLocation(program, 'aPos');                      // Set aPos attribute for each vertex.
   let aNor = gl.getAttribLocation(program, 'aNor');                      // Set aNor attribute for each vertex.
   gl.enableVertexAttribArray(aPos);
   gl.enableVertexAttribArray(aNor);
   let bpe = Float32Array.BYTES_PER_ELEMENT;                              // Bytes per floating point number
   gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 6 * bpe, 0      );    // aPos is 0,1,2 within the 6 slots.
   gl.vertexAttribPointer(aNor, 3, gl.FLOAT, false, 6 * bpe, 3 * bpe);    // aNor is 3,4,5 within the 6 slots.