3

I have a question, probably similar to this one: WebGL shading/lighting too dark on some triangles
I am trying to port one of my old exercises from C++ OpenGL which uses deprecated glaux, to WebGL.
The problem is that cone shows wrong lighting, weak blinks when there are multiple slices:
enter image description here
There is a cone which uses Archimedean spiral as base, and is ok when there is single slice:
enter image description hereenter image description here
On the left it uses 120 sectors, and on the right is 12 sectors, on WebGL is ok.
The problem starts when I use more than one slice, for instance 3 slices: enter image description hereenter image description here
This image looks somehow dirty, see left side image. The upper triangles, highlighted in right side image, looks like showing wrong lighting.
When I am showing the normals, it looks like calculated correctly. Because the normals coincide on the corners of both, upper and lower triangles:
enter image description here
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 verts represents the vertexes, verts_norms the normals and nverts the highlighted normals. Math is simple, two symmetrical Archimedean spirals built on top of each other:
Spiral is described as F(φ) = [φ * cos(φ), φ * sin(φ)]
Calculated normal ∇F(φ) = [sin(φ) + φ * cos(φ), -cos(φ) + φ * sin(φ), φ]


Update 1, the shaders:
<canvas id = "cone1 heart geometry" width = "400" height = "400"  class="yellow">
   <script src="./webgl3/cone1_heart_geometry3.js"></script>
   <script type="x-shader/x-vertex">
       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;
       }
   </script>
   <script type="x-shader/x-fragment">
      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 < 0.5) gl_FragColor = vec4(greenColor.rgb * dot(lightDirection, normalize(nm)), 1.0);
         if (isNormsColor > 0.5) gl_FragColor = vec4(0.5 * (nm + vec3(1.0)), 1.0);
      }
   </script>
</canvas>

<html>
<head>
   <title>webgl test</title>
      <style>
         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}
      </style>
<script>
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];
}

</script>
<script>
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 < 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};
}
</script>

</head>
<body>
   <div class="sample">
      <canvas id = "cone1 heart geometry" width = "400" height = "400"  class="yellow">
<script>
{
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 <= 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 < slices; j++, s++, h -= dh)
   {
      sides[0][j] = [];
      sides[1][j] = [];
      for (let i = 0, i2 = sec2; i <= 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 &  3) throw "Number of sectors must be a multiple of 4: "    + sectors;
   if (sectors <  8) throw "Must have no less than 8 sectors: "             + sectors;
   if (slices  <  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 < slices; j++)
         for (let i = 0; i < sec2; i++)
         {
            let s11, s12; // s11   \      // s11 <- s12
            let s21, s22; // s21 -> 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 = () =>
{
   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);
}</script>
         <script type="x-shader/x-vertex">
             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;
             }
         </script>
         <script type="x-shader/x-fragment">
            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 < 0.5) gl_FragColor = vec4(greenColor.rgb * dot(lightDirection, normalize(nm)), 1.0);
               if (isNormsColor > 0.5) gl_FragColor = vec4(0.5 * (nm + vec3(1.0)), 1.0);
            }
         </script>
      </canvas>
   </div>
</body>
</html>
armagedescu
  • 1,758
  • 2
  • 20
  • 31
  • 1
    I am not able to understand your shader code from your code snippet. However, from the way it looks, I can clearly say that your shaders are doing per-vertex lighting calculations. To get rid of this issue, you need to go for per-pixel lighting calculations – codetiger Aug 28 '20 at 04:05
  • @codetiger I just added Update1 with the shaders alone – armagedescu Aug 28 '20 at 07:46
  • 1
    I understood the problem. You are building the geometry and calculating the normal for the face and assigning the normals to each of the vertex in the face. So each triangle has its own independent copy of the vertex and each vertex will have 3 copies all having their corresponding normals from their faces. You need to cleanup the vertex and calculate the average normal if the vertex has same position coordinates. – codetiger Aug 28 '20 at 07:59
  • 1
    Though you are doing per-pixel lighting, your vertex data has face normals which give this effect. To achieve true per pixel lighting, you cannot have duplicate vertices. – codetiger Aug 28 '20 at 08:00
  • @codetiger Could you please specify more exactly, how to not duplicate the vertices? I mean, I am drawing triangles of a mesh. Do I have to draw TRIANGLE_STRIP instead of TRIANGLES or anything like that? I am little bit puzzled now. About the normals, there are actually six of them duplicating, three from a slice, and three from other slice. Basically all of the six normals coincides, as can be seen in norms visualising. – armagedescu Aug 28 '20 at 14:47
  • One quick check is to find the vertex count you are sending to the GPU. – codetiger Aug 28 '20 at 16:06
  • 1
    What am suspecting is, if there are 2 Triangles sharing the same edge, then you are suppose to have 4 vertex, but you are sending 6 in total. 3 for each triangle. Look at this image, this exactly the issue you have. https://cdn.knowww.eu/59b8e93cd54a862e9d7e4049//EMpxIE91fThb.png – codetiger Aug 28 '20 at 16:08

0 Answers0