title: WebGL triangle wrong lighting
body: <p>I have a question, probably similar to this one: <a href="https://stackoverflow.com/questions/29107102/webgl-shading-lighting-too-dark-on-some-triangles">WebGL shading/lighting too dark on some triangles</a><br />
  I am trying to port one of my old exercises from C++ OpenGL which uses deprecated glaux, to WebGL.<br />
  The problem is that cone shows wrong lighting, weak blinks when there are multiple slices:<br/>
  <a href="https://i.stack.imgur.com/myL9E.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/myL9E.png" alt="enter image description here" /></a><br/>
  There is a cone which uses Archimedean spiral as base, and is ok when there is single slice:<br/>
  <a href="https://i.stack.imgur.com/vHRpk.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/vHRpk.png" alt="enter image description here" /></a><a href="https://i.stack.imgur.com/Qqs2B.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/Qqs2B.png" alt="enter image description here" /></a><br/>
  On the left it uses 120 sectors, and on the right is 12 sectors, on WebGL is ok.<br />
  The problem starts when I use more than one slice, for instance 3 slices:
  <a href="https://i.stack.imgur.com/myL9E.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/myL9E.png" alt="enter image description here" /></a><a href="https://i.stack.imgur.com/lk6yW.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/lk6yW.png" alt="enter image description here" /></a><br/>
  This image looks somehow dirty, see left side image. The upper triangles, highlighted in right side image, looks like showing wrong lighting.<br />
  When I am showing the normals, it looks like calculated correctly. Because the normals coincide on the corners of both, upper and lower triangles:<br/>
  <a href="https://i.stack.imgur.com/j8Y0x.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/j8Y0x.png" alt="enter image description here" /></a><br/>
  I wanted to make the code snippet shorter, but math is really long, also could not add the scripts from GitHub, so I included them inline. The <code>verts</code> represents the vertexes, <code>verts_norms</code> the normals and <code>nverts</code> the highlighted normals. Math is simple, two symmetrical Archimedean spirals built on top of each other:<br/>
  Spiral is described as F(φ) = [φ * cos(φ), φ * sin(φ)]<br/>
  Calculated normal ∇F(φ) = [sin(φ) + φ * cos(φ), -cos(φ) + φ * sin(φ), φ]<br/></p>
  <hr/>
  Update 1, the shaders:  
  <pre class="lang-html prettyprint-override"><code>&lt;canvas id = &quot;cone1 heart geometry&quot; width = &quot;400&quot; height = &quot;400&quot;  class=&quot;yellow&quot;&gt;
     &lt;script src=&quot;./webgl3/cone1_heart_geometry3.js&quot;&gt;&lt;/script&gt;
     &lt;script type=&quot;x-shader/x-vertex&quot;&gt;
         attribute vec4 coordinates;
         attribute vec3 inputNormal;
         uniform float isDrawNorms;
  
         varying vec3 nm; //varying mediump
         varying float isNormsColor; //in place of flag
         void main()
         {
            gl_Position = coordinates;
            nm = inputNormal;
            isNormsColor = isDrawNorms;
         }
     &lt;/script&gt;
     &lt;script type=&quot;x-shader/x-fragment&quot;&gt;
        precision mediump float;
        varying vec3 nm; //varying mediump
  
        varying float isNormsColor;
        const vec4  greenColor     = vec4( 0.0,   1.0,   0.0, 1.0);
        const vec3 lightDirection  = -normalize(vec3(-1.0,  -1.0,  -1.0));
        void main()
        {
           if (isNormsColor &lt; 0.5) gl_FragColor = vec4(greenColor.rgb * dot(lightDirection, normalize(nm)), 1.0);
           if (isNormsColor &gt; 0.5) gl_FragColor = vec4(0.5 * (nm + vec3(1.0)), 1.0);
        }
     &lt;/script&gt;
  &lt;/canvas&gt;
  </code></pre>
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="true" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;html&gt;
  &lt;head&gt;
     &lt;title&gt;webgl test&lt;/title&gt;
        &lt;style&gt;
           canvas.cyan    {border:2px solid cyan;}
           canvas.magenta {border:2px solid magenta;}
           canvas.blue    {border:2px solid blue;}
           canvas.red     {border:2px solid red;}
           canvas.yellow  {border:2px solid yellow;}
           canvas.green   {border:1px solid green;}
           div.sample { display: inline-block;white-space: nowrap; border:1px solid magenta}
        &lt;/style&gt;
  &lt;script&gt;
  const RADGRAD    = Math.PI / 180;
  const C2PI       = 2 * Math.PI;
  function wrap(x)
  {
      if (isFinite(x)) return x;
      if (x == Number.POSITIVE_INFINITY) return Number.MAX_VALUE;
      if (x == Number.NEGATIVE_INFINITY) return Number.MIN_VALUE;
      return 0.0;
  }
  function delta3v(p1, p2)
  {
     return {x:(p2.x - p1.x), y:(p2.y - p1.y), z:(p2.z - p1.z)};
  }
  function cross3v(p1, p2, p3)
  {
      let v1 = delta3v(p1, p2);
      let v2 = delta3v(p1, p3);
      return {x:(v1.y * v2.z - v1.z * v2.y), y:(v1.z * v2.x - v1.x * v2.z), z: (v1.x * v2.y - v1.y * v2.x)};
  }
  
  
  function mul3v(v, f)
  {
      return [v[0] * f[0],     v[1] * f[1],     v[2] * f[2]];
  }
  function dot3v(v, f)
  {
      return v[0] * f[0]   +   v[1] * f[1]   +   v[2] * f[2];
  }
  
  &lt;/script&gt;
  &lt;script&gt;
  function buildGlProgram(canvasVar)
  {
     let canvas = null;
     let gl = null;
     if (typeof canvasVar == "string")
         canvas = document.getElementById(canvasVar);
     else if (typeof canvasVar == "object")
         canvas = canvasVar;
     //gl = canvas.getContext('webgl2');
     //gl = canvas.getContext('experimental-webgl2');
     gl = canvas.getContext('experimental-webgl');
     gl.viewport(0, 0, canvas.width, canvas.height);
     
     let canvas_id = canvas.id;
     let codes = getGLShaderCodes (canvas);
  
     let vertCode   = codes.vertCode;
     let vertShader = gl.createShader(gl.VERTEX_SHADER);
     gl.shaderSource(vertShader, vertCode);
     gl.compileShader(vertShader);
  
     let fragCode   = codes.fragCode;
     let fragShader = gl.createShader(gl.FRAGMENT_SHADER);
     gl.shaderSource(fragShader, fragCode);
     gl.compileShader(fragShader);
  
     let shaderProgram = gl.createProgram();
     gl.attachShader (shaderProgram, vertShader);
     gl.attachShader (shaderProgram, fragShader);
     gl.linkProgram  (shaderProgram);
     gl.useProgram   (shaderProgram);
  
     return {canvas:canvas,gl:gl, shaderProgram:shaderProgram};
  }
  
  function getGLShaderCodes (canvas)
  {
      let vertCode  = "";
      let fragCode  = "";
      let vertElement = null;
      let fragElement = null;
      let els = canvas.getElementsByTagName("script");
  
      for (let i = 0; i &lt; els.length; i++)
      {
          let el = els.item(i);
          switch ( el.getAttribute("type") )
          {
          case  "x-shader/x-vertex":
              vertElement = el;
              break;
          case "x-shader/x-fragment":
              fragElement = el;
              break;
          }
      }
      if(vertElement == null) vertElement = document.getElementById(canvas.getAttribute("id") + "_vertex_shader");
      if(fragElement == null) fragElement = document.getElementById(canvas.getAttribute("id") + "_fragment_shader");
      if(vertElement) vertCode = vertElement.innerText;
      if(fragElement) fragCode = fragElement.innerText;
      return {vertCode:vertCode, fragCode:fragCode};
  }
  &lt;/script&gt;
  
  &lt;/head&gt;
  &lt;body&gt;
     &lt;div class="sample"&gt;
        &lt;canvas id = "cone1 heart geometry" width = "400" height = "400"  class="yellow"&gt;
  &lt;script&gt;
  {
  let canvas = document.currentScript.parentElement;
  
  function normalize3v(norm)
  {
      let len = Math.sqrt (norm[0] * norm[0] + norm[1] * norm[1] + norm[2] * norm[2]);
      return [norm[0] / len, norm[1] / len, norm[2] / len];
  
  }
  function buildGeometry (sectors, revealInvisible)
  {
     let dfi = 2 * Math.PI / sectors;
  
     let base = [], norms = [];
     let sec2 = sectors / 2;
     // Calculate from 0 to PI
     for (let i = 0, fi = 0; i &lt;= sec2; i++, fi += dfi)
     {
        //Calculate and normalize geometry: Resize all 0..PI to 0..1
        base[i]  = mul3v([               fi * Math.cos(fi),                    fi * Math.sin(fi),   0.0], [1.0 / Math.PI, 1.0 / Math.PI,  1.0          ]);
        norms[i] = mul3v([Math.sin(fi) + fi * Math.cos(fi),   -(Math.cos(fi) - fi * Math.sin(fi)),   fi], [1.0          , 1.0          ,  1.0 / Math.PI]);
        norms[i] = normalize3v(norms[i]);
     }
     if(revealInvisible) base[0] = [0.5, 0.0, 0];
  
     return {base:base, norms:norms};
  }
  
  function buildSides (geometry, slices, sectors) 
  {
     let sec2 = sectors / 2;
     let dh = 1 / slices;
  
     let sides = [[], []];
     for (let j = 0, s = 1, h = 1 - dh; j &lt; slices; j++, s++, h -= dh)
     {
        sides[0][j] = [];
        sides[1][j] = [];
        for (let i = 0, i2 = sec2; i &lt;= sec2; i++, i2--)
        {
           let bs = geometry.base[i];
           let ns = geometry.norms[i];
           sd = [s * bs[0] / slices, s * bs[1] / slices, h,   ns[0], ns[1], ns[2]]; //[coords.xyz  |  norms.xyz]
           sides[0][j][i]  = [sd[0],  sd[1], sd[2],    sd[3],  sd[4], sd[5]];
           sides[1][j][i2] = [sd[0], -sd[1], sd[2],    sd[3], -sd[4], sd[5]];
        }
     }
     return sides;
  }
  function buildShape ()
  {}
  function buildCone(slices, sectors, revealInvisibles, snlen)
  {
     let nfi = 0;
     if (sectors &amp;  3) throw "Number of sectors must be a multiple of 4: "    + sectors;
     if (sectors &lt;  8) throw "Must have no less than 8 sectors: "             + sectors;
     if (slices  &lt;  1) throw "Must have no less than 1 slices: "              + slices;
  
     let geo = buildGeometry (sectors, revealInvisibles);
  
     let sides = buildSides(geo, slices, sectors);
     let sec2 = sectors / 2;
     let verts = [];
     let verts_norms = [];
     let nverts = [];
     let i0 = 0, i1 = 1, i2 = 2;
     let n0 = 0, n1 = 1, n2 = 2;
     const nv = snlen;
  
     for (let side of sides)
        for (let j = 0; j &lt; slices; j++)
           for (let i = 0; i &lt; sec2; i++)
           {
              let s11, s12; // s11   \      // s11 &lt;- s12
              let s21, s22; // s21 -&gt; s22   //    \   s22
              if (j == 0) //tip of the cone
              {
                 s11 = [0, 0, 1, 0, 0, 0]; //coord:xyz norm:000
                 s21 = side [j]  [i]; s22 = side[j]  [i+1];
              }
              else
              {
                 s11 = side [j-1][i]; s12 = side[j-1][i+1];
                 s21 = side [j]  [i]; s22 = side[j]  [i+1];
              }
              //if (j == 0){
              verts [i0]      =  s21[0];
              verts [i1]      =  s21[1];
              verts [i2]      =  s21[2];
              verts_norms[i0] =  s21[3];
              verts_norms[i1] =  s21[4];
              verts_norms[i2] =  s21[5];
              nverts [n0]     =  s21[0];
              nverts [n1]     =  s21[1];
              nverts [n2]     = -s21[2];
              nverts [n0 + 3] =  s21[0] + s21[3] / nv;
              nverts [n1 + 3] =  s21[1] + s21[4] / nv;
              nverts [n2 + 3] =  s21[2] - s21[5] / nv;
  
              verts [i0 + 3]      =  s22[0];
              verts [i1 + 3]      =  s22[1];
              verts [i2 + 3]      =  s22[2];
              verts_norms[i0 + 3] =  s22[3];
              verts_norms[i1 + 3] =  s22[4];
              verts_norms[i2 + 3] =  s22[5];
              nverts [n0 + 6]     =  s22[0];
              nverts [n1 + 6]     =  s22[1];
              nverts [n2 + 6]     = -s22[2];
              nverts [n0 + 9]     =  s22[0] + s22[3] / nv;
              nverts [n1 + 9]     =  s22[1] + s22[4] / nv;
              nverts [n2 + 9]     =  s22[2] - s22[5] / nv;
  
              verts [i0 + 6]      =  s11[0];
              verts [i1 + 6]      =  s11[1];
              verts [i2 + 6]      =  s11[2];
              verts_norms[i0 + 6] =  s11[3];
              verts_norms[i1 + 6] =  s11[4];  
              verts_norms[i2 + 6] =  s11[5];
              nverts [n0 + 12]    =  s11[0];
              nverts [n1 + 12]    =  s11[1];
              nverts [n2 + 12]    = -s11[2];
              nverts [n0 + 15]    =  s11[0] + s11[3] / nv;
              nverts [n1 + 15]    =  s11[1] + s11[4] / nv;
              nverts [n2 + 15]    =  s11[2] - s11[5] / nv;
  
              i0 += 9;  i1 += 9;  i2 += 9;
              n0 += 18; n1 += 18; n2 += 18;
              //}
  
              // because this is the tip of the cone
              if (j == 0) continue;
              //if(0){
              verts [i0]      =  s22[0];
              verts [i1]      =  s22[1];
              verts [i2]      =  s22[2];
              verts_norms[i0] =  s22[3];
              verts_norms[i1] =  s22[4];
              verts_norms[i2] =  s22[5];
              nverts [n0]     =  s22[0];
              nverts [n1]     =  s22[1];
              nverts [n2]     = -s22[2];
              nverts [n0 + 3] =  s22[0] + s22[3] / nv;
              nverts [n1 + 3] =  s22[1] + s22[4] / nv;
              nverts [n2 + 3] =  s22[2] - s22[5] / nv;
  
              verts [i0 + 3]      =  s12[0];
              verts [i1 + 3]      =  s12[1];
              verts [i2 + 3]      =  s12[2];
              verts_norms[i0 + 3] =  s12[3];
              verts_norms[i1 + 3] =  s12[4];
              verts_norms[i2 + 3] =  s12[5];
              nverts [n0 + 6]     =  s12[0];
              nverts [n1 + 6]     =  s12[1];
              nverts [n2 + 6]     = -s12[2];
              nverts [n0 + 9]     =  s12[0] + s12[3] / nv;
              nverts [n1 + 9]     =  s12[1] + s12[4] / nv;
              nverts [n2 + 9]     =  s12[2] - s12[5] / nv;
  
              verts [i0 + 6]      =  s11[0];
              verts [i1 + 6]      =  s11[1];
              verts [i2 + 6]      =  s11[2];
              verts_norms[i0 + 6] =  s11[3];
              verts_norms[i1 + 6] =  s11[4];
              verts_norms[i2 + 6] =  s11[5];
              nverts [n0 + 12]    =  s11[0];
              nverts [n1 + 12]    =  s11[1];
              nverts [n2 + 12]    = -s11[2];
              nverts [n0 + 15]    =  s11[0] + s11[3] / nv;
              nverts [n1 + 15]    =  s11[1] + s11[4] / nv;
              nverts [n2 + 15]    =  s11[2] - s11[5] / nv;
  
              //}
              i0 += 9;  i1 += 9;  i2 += 9;
              n0 += 18; n1 += 18; n2 += 18;
           }
     return {verts:verts, norms:verts_norms, nverts:nverts};
  
  }
  
  let func = () =&gt;
  {
     let prog = buildGlProgram(canvas);
     let gl = prog.gl;
  
     gl.clearColor(0.5, 0.5, 0.5, 0.9);
     gl.enable(gl.DEPTH_TEST);
     gl.enable(gl.CULL_FACE);
     gl.clear (gl.COLOR_BUFFER_BIT);
     ////////////////////////////////////
     const revealInvisibles = false;
     let ns = 12, nh = 3, show_norms = true;
     let obj;
     try
     {
        obj = buildCone(nh, ns, revealInvisibles, 8);
     }
     catch(err)
     {
        alert(err);
     }
     let verts = obj.verts;
     let norms = obj.norms;
     ////////////////////////////////////
  
     let vertex_buffer = gl.createBuffer();
     gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
     gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
  
     let shaderProgram = prog.shaderProgram;
     let coord = gl.getAttribLocation (shaderProgram, "coordinates");
     gl.vertexAttribPointer     (coord, 3, gl.FLOAT, false, 0, 0);
     gl.enableVertexAttribArray (coord);
  
     let normalBuffer = gl.createBuffer();
     gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
     gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(norms), gl.STATIC_DRAW);
     let noord = gl.getAttribLocation (shaderProgram, "inputNormal");
     gl.vertexAttribPointer     (noord, 3, gl.FLOAT, false, 0, 0);
     gl.enableVertexAttribArray (noord);
  
     let drawNorms = gl.getUniformLocation(prog.shaderProgram, 'isDrawNorms');
     gl.uniform1f(drawNorms, 0.0);
  
     gl.drawArrays(gl.TRIANGLES, 0, ns * 3 + ns * 6 * (nh - 1));
  
     if (show_norms)
     {
        gl.uniform1f(drawNorms, 1.0);
        let nverts = obj.nverts;
        let nvert_buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, nvert_buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(nverts), gl.STATIC_DRAW);
        //coord = gl.getAttribLocation (shaderProgram, "coordinates");
        gl.vertexAttribPointer     (coord, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray (coord);
  
        gl.drawArrays(gl.LINES, 0, 2* ( ns * 3 + ns * 6 * (nh - 1)));
        //gl.drawArrays(gl.LINE_STRIP, 0, ns * 3 + ns * 6 * (nh - 1));
     }
  }
  
  document.addEventListener('DOMContentLoaded', func);
  }&lt;/script&gt;
           &lt;script type="x-shader/x-vertex"&gt;
               attribute vec4 coordinates;
               attribute vec3 inputNormal;
               uniform float isDrawNorms;
  
               varying vec3 nm; //varying mediump
               varying float isNormsColor; //in place of flag
               void main()
               {
                  gl_Position = coordinates;
                  nm = inputNormal;
                  isNormsColor = isDrawNorms;
               }
           &lt;/script&gt;
           &lt;script type="x-shader/x-fragment"&gt;
              precision mediump float;
              varying vec3 nm; //varying mediump
  
              varying float isNormsColor;
              const vec4  greenColor     = vec4( 0.0,   1.0,   0.0, 1.0);
              const vec3 lightDirection  = -normalize(vec3(-1.0,  -1.0,  -1.0));
              void main()
              {
                 if (isNormsColor &lt; 0.5) gl_FragColor = vec4(greenColor.rgb * dot(lightDirection, normalize(nm)), 1.0);
                 if (isNormsColor &gt; 0.5) gl_FragColor = vec4(0.5 * (nm + vec3(1.0)), 1.0);
              }
           &lt;/script&gt;
        &lt;/canvas&gt;
     &lt;/div&gt;
  &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>