Notes for Thursday March 26 class -- parametric surfaces

 

Making a cube

We already know we can create a square as a triangle strip consisting of two triangles. By glueing together six such squares, we can create a triangle strip in the shape of a cube.

I went over my implementation of this in class, which you are free to use in your own work. You can also use this as a model for creating other shapes that you might want to piece together in creative ways:

let cube = [];
for (let s = -1 ; s <= 1 ; s += 2) {  // Loop thru two faces for each axis
   let v = [ new Array(stride),
             new Array(stride),
             new Array(stride),
             new Array(stride) ];
   for (let i = 0 ; i < 3 ; i++) {    // Loop thru x,y,z axes
      let j = (i + 1) % 3,
          k = (i + 2) % 3;
      for (let m = 0 ; m < 4 ; m++) { // Loop thru the 4 corners of one face

         v[m][i]   = m > 1 ? s : -s;       // Position
         v[m][j]   = m & 1 ? 1 : -1;
         v[m][k]   = s;

         v[m][i+3] = 0;                    // Normal
         v[m][j+3] = 0;
         v[m][k+3] = s;

         v[m][6]   = (1 + v[m][i]) / 2;    // u,v
         v[m][7]   = (1 + v[m][j]) / 2;
      }
      cube = glueMeshes(cube, v[0].concat(v[1].concat(v[2].concat(v[3]))));
   }
}

Playing with parametric surfaces

In class we showed that you can play around quite a bit with parametric surfaces. If you make the shape of the surface depend on time, then you can get really fun animating shapes, which create a kind of visual music.

The accompanying image is a snapshot of one of the animating shapes we created in class. This particular example is a torus that is continually twisting itself.

You too can create animating shapes, if you create a new triangle mesh every animation frame, and use time in the definition of your triangle mesh at each value of u,v.

Adding u,v to each vertex

In order to create textures, it will be useful to have access to the parametric values u,v at each vertex. We handle this by doing several things.

For one thing, we increase stride from 6 to 8, and we append u,v to each vertex in the vertex buffer.

For example, we used to define each vertex of a square as follows:

   let uvToSquare = (u,v) => [ 2*u-1,2*v-1,0, 0,0,1 ];
Here is what the same function looks like when we add u,v into each vertex. Note the addition of u,v on the end:
   let uvToSquare = (u,v) => [ 2*u-1,2*v-1,0, 0,0,1, u,v ];
We also need to do the following things:
  • declare attribute vec2 aUV in lib4.js
  • declare aUV in the vertex shader
  • assign aUV to varying vec2 vUV in the vertex shader
  • declare vUV in fragment shader
  • add texture amplitude ta and frequency tf to the phong parameters
  • use ta and tf to create textures in the fragment shader
We used procedural noise to show different kinds of texture mapping. If you texture based on vPos (the 3D position that is transformed by a matrix every animation frame), then you will get a solid texture that looks as though your shape is moving through a solid block of material.

If you want a solid texture that moves together with your shape, then you need to base your texture directly on vertex attribute aPos, without any matrix transformations. In order to do this, we created a varying vec3 vaPos, which is just assigned the value of aPos at every vertex.

Creating an open cone

We modified our definition of an open tube to create an open cone. Not only do we need to use v to taper the tube, but we also need to modify the surface normal, because the surface of the cone inclines toward the positive z axis:
let coneNxy = Math.sqrt(4/5);
let coneNz  = Math.sqrt(1/5);

let uvToOpenCone = (u,v) => {
   let theta = 2 * Math.PI * u;
   let x = Math.cos(theta) * (1-v),
       y = Math.sin(theta) * (1-v),
       z = 2 * v - 1;
   return [ x,y,z, coneNxy * x,coneNxy * y,coneNz, u,v ];
}

Creating a cone with an end cap

To put an end cap on the cone, we glue it together with a disk shaped triangle mesh. As we discussed in class, we pass a third argument to createTriangleMesh().

That argument gets passed by createTriangleMesh() into uvToDisk as a third argument. The uvToDisk function uses that third argument to translate the disk in z to -1.

let cone = glueMeshes(openCone,
                      createTriangleMesh(uvToDisk, 30, 2, -1));
In order to make this work I modified uvToDisk() as follows. Note the if/else statement. That's where I use the optional third argument s, where s=-1 or s=1, to shift the z position of the disk to either z=-1 or z=1:
let uvToDisk = (u,v,s) => {
   let theta = 2 * Math.PI * u,
       x = Math.cos(theta),
       y = Math.sin(theta),
       z = 0;

   if (s === undefined)
      s = 1;
   else
      z = s;

   return [ v*x*s,v*y,z, 0,0,s, u,v ];
}

Homework 6, due before class on Thursday April 2

First grab hw6.zip to use as your starting point.

I have left several places in hw6 unimplemented, for you to fill in.

You should implement the following in lib4.js:

  • The glueMeshes() function;

  • To create the primitive shapes that I left out, implement uvToSphere(), uvToTorus() and uvToTube() with the u,v values added to the end of each vertex (since stride is now 8).
You now have the components you need to create really interesting scenes, so in index.html I would like you to make an interesting and original scene that shows off the power of hierarchical animated 3D objects.

I would like you to be creative, and make something fun and exciting, ideally reflecting your own interests (music, architecture, games, science, cool mechanisms, etc.).

There are lots of opportunities for extra credit here:

  • You can create your own primitive shapes, perhaps a tetrahedron, octahedron, dodecahedron or icosahedron.

  • Create various kinds of procedural texture, built using either vaPos or vUV, or a combination of the two.

  • Create procedural textures that vary some other property of appearance, not just color.
Those are just a few suggestions for extra credit. I am sure you can think of all sorts of interesting things to try. This is your space, and your chance to play.