Use your fingers or mouse to control the model (hold shift key or use mouse wheel to zoom it). Canvas is matched to your browser window.

Making a Cone

Below you can find short comments to a simple 5kb script cone.

The cone has parameters h, r1, r2 and a normal (in the XY plane)
    Nx = (r1 - r2)/N,   Ny = h/N,     N = ((r1 - r2)2 + h2)1/2.
The number of triangles in the cone is 2×nPhi = 200. It is not enough for specular color yet and nPhi = 500 is better (see cone).

The CanvasMatrix.js library is used for operations with matrices.
<script src="../CanvasMatrix.js" type="text/javascript">
The vertex shader calculates coordinates and colors (diffusive and specular) of rotated verteces.
<script id="shader-vs" type="x-shader/x-vertex"> 
  attribute vec3 aPos;
  attribute vec3 aNorm;
  uniform mat4 mvMatrix;
  uniform mat4 prMatrix;
  varying vec4 color;
  const vec3 dirDif = vec3(0., 0., 1.);
  const vec3 dirHalf = vec3(-.4034, .259, .8776);
void main(void) {
   gl_Position = prMatrix * mvMatrix * vec4(aPos, 1.);
   vec3 rotNorm = (mvMatrix * vec4(aNorm, .0)).xyz;
   float i = max( 0., dot(rotNorm, dirDif) );
   color = vec4(.9*i, .5*i, 0., 1.);
   i = pow( max( 0., dot(rotNorm, dirHalf) ), 120.);
   color += vec4(i, i, i, 0.);
}
</script> 
The fragment shader just transfers interpolated colors for every pixel.
<script id="shader-fs" type="x-shader/x-fragment"> 
precision mediump float;
  varying vec4 color;
void main(void) {
   gl_FragColor = color;
}
</script> 
Common for all scripts getShader() function compiles shaders.
If necessarily it writes compilation errors.
<script type="text/javascript"> 

function getShader ( gl, id ){
   var shaderScript = document.getElementById ( id );
   var str = "";
   var k = shaderScript.firstChild;
   while ( k ){
     if ( k.nodeType == 3 ) str += k.textContent;
     k = k.nextSibling;
   }
   var shader;
   if ( shaderScript.type == "x-shader/x-fragment" )
           shader = gl.createShader ( gl.FRAGMENT_SHADER );
   else if ( shaderScript.type == "x-shader/x-vertex" )
           shader = gl.createShader(gl.VERTEX_SHADER);
   else return null;
   gl.shaderSource(shader, str);
   gl.compileShader(shader);
   if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == 0)
      alert(id +"\n"+ gl.getShaderInfoLog(shader));
   return shader;
}
This is the main function executed on load. Initialization of WebGL and canvas matching to the brouser window.
var gl, canvas, h = 1, r1 = .5, r2 = .2, nPhi = 100;

function webGLStart() {
   canvas = document.getElementById("canvas");
   var size = Math.min(window.innerWidth, window.innerHeight) - 20;
   canvas.width =  size;   canvas.height = size;
   canvas.getContext("experimental-webgl");
   if (!window.WebGLRenderingContext){
     alert("Your browser does not support WebGL. See http://get.webgl.org");
     return;}
   try { gl = canvas.getContext("experimental-webgl");
   } catch(e) {}
   if ( !gl ) {alert("Can't get WebGL"); return;}
   gl.viewport(0, 0, size, size);
Shaders compiling.
   var prog  = gl.createProgram();
   gl.attachShader(prog, getShader( gl, "shader-vs" ));
   gl.attachShader(prog, getShader( gl, "shader-fs" ));
   gl.linkProgram(prog);
   gl.useProgram(prog);
Make points and normals for our cone, put them into GPU and bind to shader attributes.
   var pt = [], nt = [];
   var Phi = 0, dPhi = 2*Math.PI / (nPhi-1),
     Nx = r1 - r2, Ny = h, N = Math.sqrt(Nx*Nx + Ny*Ny);
   Nx /= N; Ny /= N;
   for (var i = 0; i < nPhi; i++ ){
      var cosPhi = Math.cos( Phi );
      var sinPhi = Math.sin( Phi );
      var cosPhi2 = Math.cos( Phi + dPhi/2 );
      var sinPhi2 = Math.sin( Phi + dPhi/2 );
      pt.push ( -h/2, cosPhi * r1, sinPhi * r1 );   // points
      nt.push ( Nx, Ny*cosPhi, Ny*sinPhi );         // normals
      pt.push ( h/2, cosPhi2 * r2, sinPhi2 * r2 );  // points
      nt.push ( Nx, Ny*cosPhi2, Ny*sinPhi2 );       // normals
      Phi   += dPhi;
   }
   var posLoc = gl.getAttribLocation(prog, "aPos");
   gl.enableVertexAttribArray( posLoc );
   gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pt), gl.STATIC_DRAW);
   gl.vertexAttribPointer(posLoc, 3, gl.FLOAT, false, 0, 0);

   var normLoc = gl.getAttribLocation(prog, "aNorm");
   gl.enableVertexAttribArray( normLoc );
   gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(nt), gl.STATIC_DRAW);
   gl.vertexAttribPointer(normLoc, 3, gl.FLOAT, false, 0, 0);
Prepare perspective and modelview matrices. Put the first to the vertex shader as uniform. The rotation matrix is used to save integrated rotation.
   var prMatrix = new CanvasMatrix4();
   prMatrix.perspective(45, 1, .1, 100);
   gl.uniformMatrix4fv( gl.getUniformLocation(prog,"prMatrix"),
      false, new Float32Array(prMatrix.getAsArray()) );
   var mvMatrix = new CanvasMatrix4();
   var rotMat = new CanvasMatrix4();
   rotMat.makeIdentity();
   var mvMatLoc = gl.getUniformLocation(prog,"mvMatrix");
GL initialization.
   gl.enable(gl.DEPTH_TEST);
   gl.depthFunc(gl.LEQUAL);
   gl.clearDepth(1.0);
   gl.clearColor(0, 0, .5, 1);
   var xOffs = yOffs = 0,  drag  = 0;
   var xRot = yRot = 0;
   var transl = -2.5;
   drawScene();
The drawScene() function prepares cone's rotation and translation then draws its points as one TRIANGLE_STRIP.
  function drawScene(){
    rotMat.rotate(xRot/5, 1,0,0);  rotMat.rotate(yRot/5, 0,1,0);
    yRot = 0;  xRot = 0;
    mvMatrix.load( rotMat );
    mvMatrix.translate(0, 0, transl);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.uniformMatrix4fv( mvMatLoc, false,
      new Float32Array(mvMatrix.getAsArray()) );
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 2*nPhi);
    gl.flush ();
  }
On window resize action
  canvas.resize = function (){
    var size = Math.min(window.innerWidth, window.innerHeight) - 10;
    canvas.width =  size;   canvas.height = size;
    gl.viewport(0, 0, size, size);
    drawScene();
  }
Mouse events processing to test if it is "draged" (these methods are outdated but they work).
  canvas.onmousedown = function ( ev ){
     drag  = 1;
     xOffs = ev.clientX;  yOffs = ev.clientY;
  }
  canvas.onmouseup = function ( ev ){
     drag  = 0;
     xOffs = ev.clientX;  yOffs = ev.clientY;
  }
On mouse "drag" we calculate two rotations and translation if the shift key is pressd. Then the cone is repainted.
  canvas.onmousemove = function ( ev ){
     if ( drag == 0 ) return;
     if ( ev.shiftKey ) {
        transl *= 1 + (ev.clientY - yOffs)/1000;
        yRot = - xOffs + ev.clientX; }
     else {
        yRot = - xOffs + ev.clientX;  xRot = - yOffs + ev.clientY; }
     xOffs = ev.clientX;  yOffs = ev.clientY;
     drawScene();
  }
}
This is the mouse wheel event handler
  var wheelHandler = function(ev) {
    var del = 1.1;
    if (ev.shiftKey) del = 1.01;
    var ds = ((ev.detail || ev.wheelDelta) > 0) ? del : (1 / del);
    transl *= ds;
    drawScene();
    ev.preventDefault();
  };
  canvas.addEventListener('DOMMouseScroll', wheelHandler, false);
  canvas.addEventListener('mousewheel', wheelHandler, false);

</script> 
JavaScript initialization on load and window resizing.
<body onload="webGLStart();" onresize="canvas.resize();"> 
   <canvas id="canvas" width="500" height="500"></canvas> 

Contents     updated 14 March 2011