Pointed Light (Per Fragment)

A nearby light source gives rise to pointed light. The direction of the light from a point light source differs at each position.

So the light direction needs to be calculated at each position:
<light direction> = <light source position> - <surface position>

While it is possible to implement the shading in the vertex shader, implementing it in the fragment shader yields a more realistic result.
RESETRUNFULL
<!DOCTYPE html><html><head>
<script src="/shared/webgl-library.js"></script>
<script id="vs" type="x-shader/x-vertex">
   attribute vec4 p;
   attribute vec4 n;
   uniform mat4 modelMatrix;
   uniform mat4 viewMatrix;
   uniform mat4 projectionMatrix;
   uniform mat4 normalMatrix;
   varying vec3 v_Normal;
   varying vec3 v_Position;
   void main() {
      gl_Position = projectionMatrix*viewMatrix*modelMatrix*p;
      v_Position = vec3(modelMatrix * p);
      v_Normal = normalize(vec3(normalMatrix * n));
   }
</script>
<script id="fs" type="x-shader/x-fragment">
   #ifdef GL_ES
   precision mediump float;
   #endif
   uniform vec3 lightColor;
   uniform vec3 lightPosition;
   uniform vec3 ambientLight;
   varying vec3 v_Normal;
   varying vec3 v_Position;
   void main() {
      vec4 surfaceColor = vec4(1.0,1.0,1.0,1.0);
      vec3 normal = normalize(v_Normal);
      vec3 lightDirection = normalize(lightPosition - v_Position);
      float nDotL = max(dot(lightDirection, normal), 0.0);
      vec3 diffuse = lightColor * surfaceColor.rgb * nDotL;
      vec3 ambient = ambientLight * surfaceColor.rgb;
      gl_FragColor = vec4(diffuse + ambient, surfaceColor.a);
   }
</script>
<script>
function main() {
   var canvas = document.getElementById('myCanvas');
   var gl = canvas.getContext('webgl');
   initShaders(gl, 'vs', 'fs');
   var n = initVertexBuffers(gl);
   var cvL = gl.getUniformLocation(gl.program, 'lightColor');
   var pvL = gl.getUniformLocation(gl.program, 'lightPosition');
   var avL = gl.getUniformLocation(gl.program, 'ambientLight');
   gl.uniform3f(cvL, 0.8, 0.8, 0.8);
   gl.uniform3f(pvL, 3.0, 6.0, 5.0);
   gl.uniform3f(avL, 0.1, 0.1, 0.1);
   var mmL = gl.getUniformLocation(gl.program, 'modelMatrix');
   var vmL = gl.getUniformLocation(gl.program, 'viewMatrix');
   var pmL = gl.getUniformLocation(gl.program, 'projectionMatrix');
   var mm = new Matrix4();
   var vm = new Matrix4();
   var pm = new Matrix4();
   mm.scale(1, 1.5, 1);
   pm.perspective(30, canvas.width/canvas.height, 1, 10);
   vm.lookAt(3, 1, 5, 0, 0, 0, 0, 1, 0);
   gl.uniformMatrix4fv(mmL, false, new Float32Array(mm.entries));
   gl.uniformMatrix4fv(vmL, false, new Float32Array(vm.entries));
   gl.uniformMatrix4fv(pmL, false, new Float32Array(pm.entries));
   var nmL = gl.getUniformLocation(gl.program, 'normalMatrix');
   var nm = new Matrix4(mm.entries);
   nm.inverse();
   nm.transpose();
   gl.uniformMatrix4fv(nmL, false, new Float32Array(nm.entries));
   gl.clearColor(0, 0, 0, 1);
   gl.enable(gl.DEPTH_TEST);
   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
   gl.drawElements(gl.TRIANGLES,n,gl.UNSIGNED_SHORT,0);
}

function initVertexBuffers(gl) { // Create a sphere
   var SPHERE_DIV = 50;
   var i, ai, si, ci;
   var j, aj, sj, cj;
   var p1, p2;
   var positions = [];
   var indices = [];
   for (j = 0; j <= SPHERE_DIV; j++) {
      aj = j * Math.PI / SPHERE_DIV;
      sj = Math.sin(aj);
      cj = Math.cos(aj);
      for (i = 0; i <= SPHERE_DIV; i++) {
         ai = i * 2 * Math.PI / SPHERE_DIV;
         si = Math.sin(ai);
         ci = Math.cos(ai);
         positions.push(si * sj);  // X
         positions.push(cj);       // Y
         positions.push(ci * sj);  // Z
      }
   }
   for (j = 0; j < SPHERE_DIV; j++) {
      for (i = 0; i < SPHERE_DIV; i++) {
         p1 = j * (SPHERE_DIV+1) + i;
         p2 = p1 + (SPHERE_DIV+1);
         indices.push(p1);
          indices.push(p2);
          indices.push(p1 + 1);
         indices.push(p1 + 1);
         indices.push(p2);
         indices.push(p2 + 1);
      }
   }
   initArrayBuffer(gl, 'p', new Float32Array(positions), 3);
   initArrayBuffer(gl, 'n', new Float32Array(positions), 3);
   gl.bindBuffer(gl.ARRAY_BUFFER, null);
   var indexBuffer = gl.createBuffer();
   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
   gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
   return indices.length;
}
</script>
</head>
<body onload="main()">
   <canvas id="myCanvas" width="500" height="500"></canvas>
</body>
</html>