Search code examples
javascriptglslwebglfragment-shadervertex-shader

same JS code in 2 diiferent WebGL applications, works in 1 but fails in other: problem with vertexAttribute?


I have created 2 snippets here, starting from https://interactivecomputergraphics.com/8E/Code/06/shadedCube.html. The 1st snippet works as expected but the second one does not. When I run the 2nd snippet in a browser, I get these errors on the javascript console:

WebGL: INVALID_VALUE: vertexAttribPointer: index out of range
WebGL: INVALID_VALUE: enableVertexAttribArray: index out of range

The errors correspond to these statements in the javascript:

const normalLoc = gl.getAttribLocation(program, 'aNormal');
gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(normalLoc);
console.log('normalLoc', normalLoc);

And the output of the console.log statement above for 2nd snippet is normalLoc -1. (Note: the snippet console includes this line of output, but doesn't show the error messages that come up in browsers (chrome, safari).)

Declaration of aNormal in vertex shader from 1st snippet:

in vec4 aPosition;
in vec3 aNormal;
out vec4 vColor;

Declaration of aNormal in vertex shader from 2nd snippet:

in vec4 aPosition;
in vec3 aNormal;
out vec3 N, L, V;

I have looked at WEBGL: INVALID_VALUE: vertexAttribPointer: index out of range, INVALID_VALUE: enableVertexAttribArray: index out of range and Webgl's getAttribLocation oddly returns -1.

I don't see what I am missing. Any help is greatly appreciated!

1st Snippet (works)

'use strict';

function shadedCube() {
  let gl;
  let program;

  const numPositions = 36;

  const positionsArray = [];
  const normalsArray = [];

  const vertices = [
    vec4(-0.5, -0.5, 0.5, 1.0),
    vec4(-0.5, 0.5, 0.5, 1.0),
    vec4(0.5, 0.5, 0.5, 1.0),
    vec4(0.5, -0.5, 0.5, 1.0),
    vec4(-0.5, -0.5, -0.5, 1.0),
    vec4(-0.5, 0.5, -0.5, 1.0),
    vec4(0.5, 0.5, -0.5, 1.0),
    vec4(0.5, -0.5, -0.5, 1.0)];

  const viewerPosition = vec4(0.0, 0.0, 20.0, 0.0);
  const lightPosition = vec4(0.0, 2.0, 2.0, 0.0);
  const lightAmbient = vec4(0.2, 0.2, 0.2, 1.0);
  const lightDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
  const lightSpecular = vec4(1.0, 1.0, 1.0, 1.0);

  const materialAmbient = vec4(0.0, 0.0, 0.8, 1.0);
  const materialDiffuse = vec4(0.8, 0.8, 0.0, 1.0);
  const materialSpecular = vec4(0.4, 0.4, 0.4, 1.0);
  const materialShininess = 100.0;

  const xAxis = 0;
  const yAxis = 1;
  const zAxis = 2;
  const axis = 0;
  const theta = vec3(0, 0, 0);

  const flag = true;

  function quad(a, b, c, d) {
    const t1 = subtract(vertices[b], vertices[a]);
    const t2 = subtract(vertices[c], vertices[b]);
    let normal = cross(t1, t2);
    normal = vec3(normal);
    console.log('cube face normal', normal[0], normal[1], normal[2]);

    positionsArray.push(vertices[a]);
    normalsArray.push(normal);
    positionsArray.push(vertices[b]);
    normalsArray.push(normal);
    positionsArray.push(vertices[c]);
    normalsArray.push(normal);
    positionsArray.push(vertices[a]);
    normalsArray.push(normal);
    positionsArray.push(vertices[c]);
    normalsArray.push(normal);
    positionsArray.push(vertices[d]);
    normalsArray.push(normal);
  }

  function colorCube() {
    quad(1, 0, 3, 2);
    quad(2, 3, 7, 6);
    quad(3, 0, 4, 7);
    quad(6, 5, 1, 2);
    quad(4, 5, 6, 7);
    quad(5, 4, 0, 1);
  }

  window.onload = function init() {
    const canvas = document.getElementById('gl-canvas');
    gl = canvas.getContext('webgl2');
    if (!gl) {
      alert( 'WebGL 2.0 is not available');
    }
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(0.8, 0.8, 0.8, 1.0);
    gl.enable(gl.DEPTH_TEST);

    //
    //  Load shaders and initialize attribute buffers
    //
    program = initShaders(gl, 'vertex-shader', 'fragment-shader');
    gl.useProgram(program);

    // generate the data needed for the cube
    colorCube();
    console.log('number of normals', normalsArray.length);

    const vBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(positionsArray), gl.STATIC_DRAW);
    const positionLoc = gl.getAttribLocation(program, 'aPosition');
    gl.vertexAttribPointer(positionLoc, 4, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(positionLoc);
    console.log('positionLoc', positionLoc);

    const nBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, nBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(normalsArray), gl.STATIC_DRAW);
    const normalLoc = gl.getAttribLocation(program, 'aNormal');
    gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(normalLoc);
    console.log('normalLoc', normalLoc);

    const projectionMatrix = ortho(-1, 1, -1, 1, -100, 100);

    const ambientProduct = mult(lightAmbient, materialAmbient);
    const diffuseProduct = mult(lightDiffuse, materialDiffuse);
    const specularProduct = mult(lightSpecular, materialSpecular);
    /*
    document.getElementById('ButtonX').onclick = function() {
      axis = xAxis;
    };
    document.getElementById('ButtonY').onclick = function() {
      axis = yAxis;
    };
    document.getElementById('ButtonZ').onclick = function() {
      axis = zAxis;
    };
    document.getElementById('ButtonT').onclick = function() {
      flag = !flag;
    }; */

    gl.uniform4fv(
        gl.getUniformLocation(program, 'uAmbientProduct'), ambientProduct);
    gl.uniform4fv(
        gl.getUniformLocation(program, 'uDiffuseProduct'), diffuseProduct);
    gl.uniform4fv(
        gl.getUniformLocation(program, 'uSpecularProduct'), specularProduct);
    gl.uniform1f(
        gl.getUniformLocation(program, 'uShininess'), materialShininess);
    gl.uniform4fv(
        gl.getUniformLocation(program, 'uLightPosition'), lightPosition);
    gl.uniform4fv(
        gl.getUniformLocation(program, 'uViewerPosition'), viewerPosition);

    gl.uniformMatrix4fv(
        gl.getUniformLocation(program, 'uProjectionMatrix'),
        false, flatten(projectionMatrix));
    render();
  };
  function render() {
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    if (flag) {
      theta[axis] += 2.0;
    }
    let modelViewMatrix = mat4();
    modelViewMatrix = mult(modelViewMatrix,
        rotate(theta[xAxis], vec3(1, 0, 0)));
    modelViewMatrix = mult(modelViewMatrix,
        rotate(theta[yAxis], vec3(0, 1, 0)));
    modelViewMatrix = mult(modelViewMatrix,
        rotate(theta[zAxis], vec3(0, 0, 1)));

    gl.uniformMatrix4fv(
        gl.getUniformLocation(program, 'uModelViewMatrix'),
        false, flatten(modelViewMatrix));
    gl.drawArrays(gl.TRIANGLES, 0, numPositions);
    requestAnimationFrame(render);
  };
};

shadedCube();
<!DOCTYPE html>
<html>
    <head>
        <script id="vertex-shader" type="x-shader/x-vertex">
            #version 300 es

            in vec4 aPosition;
            in vec3 aNormal;
            out vec4 vColor;

            uniform vec4 uAmbientProduct, uDiffuseProduct, uSpecularProduct;
            uniform float uShininess;
            uniform mat4 uModelViewMatrix, uProjectionMatrix;
            uniform vec4 uLightPosition, uViewerPosition;
            
            void main()
            {
                vec4 NN = vec4(aNormal, 0);
                vec3 N = normalize((uModelViewMatrix * NN).xyz);
                vec3 pos = (uModelViewMatrix * aPosition).xyz;
                vec3 light = uLightPosition.xyz;
                vec3 L = normalize(light - pos);

                vec3 V = normalize(uViewerPosition).xyz;
                vec3 H = normalize(L + V);

                // Compute terms in the illumination equation
                // ambient 
                vec4 ambient = uAmbientProduct;
                // diffuse
                float Kd = max(dot(L, N), 0.0);
                vec4 diffuse = Kd * uDiffuseProduct;
                // specular
                float Ks = pow(max(dot(N, H), 0.0), uShininess);
                vec4 specular = Ks * uSpecularProduct;

                vColor = ambient + diffuse + specular;
                vColor.a = 1.0;

                gl_Position = uProjectionMatrix * uModelViewMatrix * aPosition;
            }
        </script>
        <script id="fragment-shader" type="x-shader/x-fragment">
            #version 300 es

            precision mediump float;

            in vec4 vColor;
            out vec4 fColor;

            void
            main()
            {
                fColor = vColor;
            }
        </script>
        <script text/javascript" src="https://interactivecomputergraphics.com/8E/Code/Common/initShaders.js"></script>
        <script type="text/javascript" src="https://interactivecomputergraphics.com/8E/Code/Common/MVnew.js"></script>

        <script type="text/javascript" src="shaded-cube.js"></script>
    </head>
    <body>
        <canvas id="gl-canvas" width="512" height="512">
            Your browser does not support the HTML5 canvas element
        </canvas>
    </body>
</html>

2nd snippet (fails)

'use strict';

function shadedCube() {
  let gl;
  let program;

  const numPositions = 36;

  const positionsArray = [];
  const normalsArray = [];

  const vertices = [
    vec4(-0.5, -0.5, 0.5, 1.0),
    vec4(-0.5, 0.5, 0.5, 1.0),
    vec4(0.5, 0.5, 0.5, 1.0),
    vec4(0.5, -0.5, 0.5, 1.0),
    vec4(-0.5, -0.5, -0.5, 1.0),
    vec4(-0.5, 0.5, -0.5, 1.0),
    vec4(0.5, 0.5, -0.5, 1.0),
    vec4(0.5, -0.5, -0.5, 1.0)];

  const viewerPosition = vec4(0.0, 0.0, 20.0, 0.0);
  const lightPosition = vec4(0.0, 2.0, 2.0, 0.0);
  const lightAmbient = vec4(0.2, 0.2, 0.2, 1.0);
  const lightDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
  const lightSpecular = vec4(1.0, 1.0, 1.0, 1.0);

  const materialAmbient = vec4(0.0, 0.0, 0.8, 1.0);
  const materialDiffuse = vec4(0.8, 0.8, 0.0, 1.0);
  const materialSpecular = vec4(0.4, 0.4, 0.4, 1.0);
  const materialShininess = 100.0;

  const xAxis = 0;
  const yAxis = 1;
  const zAxis = 2;
  const axis = 0;
  const theta = vec3(0, 0, 0);

  const flag = true;

  function quad(a, b, c, d) {
    const t1 = subtract(vertices[b], vertices[a]);
    const t2 = subtract(vertices[c], vertices[b]);
    let normal = cross(t1, t2);
    normal = vec3(normal);
    console.log('cube face normal', normal[0], normal[1], normal[2]);

    positionsArray.push(vertices[a]);
    normalsArray.push(normal);
    positionsArray.push(vertices[b]);
    normalsArray.push(normal);
    positionsArray.push(vertices[c]);
    normalsArray.push(normal);
    positionsArray.push(vertices[a]);
    normalsArray.push(normal);
    positionsArray.push(vertices[c]);
    normalsArray.push(normal);
    positionsArray.push(vertices[d]);
    normalsArray.push(normal);
  }

  function colorCube() {
    quad(1, 0, 3, 2);
    quad(2, 3, 7, 6);
    quad(3, 0, 4, 7);
    quad(6, 5, 1, 2);
    quad(4, 5, 6, 7);
    quad(5, 4, 0, 1);
  }

  window.onload = function init() {
    const canvas = document.getElementById('gl-canvas');
    gl = canvas.getContext('webgl2');
    if (!gl) {
      alert( 'WebGL 2.0 is not available');
    }
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(0.8, 0.8, 0.8, 1.0);
    gl.enable(gl.DEPTH_TEST);

    //
    //  Load shaders and initialize attribute buffers
    //
    program = initShaders(gl, 'vertex-shader', 'fragment-shader');
    gl.useProgram(program);

    // generate the data needed for the cube
    colorCube();
    console.log('number of normals', normalsArray.length);

    const vBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(positionsArray), gl.STATIC_DRAW);
    const positionLoc = gl.getAttribLocation(program, 'aPosition');
    gl.vertexAttribPointer(positionLoc, 4, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(positionLoc);
    console.log('positionLoc', positionLoc);

    const nBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, nBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(normalsArray), gl.STATIC_DRAW);
    const normalLoc = gl.getAttribLocation(program, 'aNormal');
    gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(normalLoc);
    console.log('normalLoc', normalLoc);

    const projectionMatrix = ortho(-1, 1, -1, 1, -100, 100);

    const ambientProduct = mult(lightAmbient, materialAmbient);
    const diffuseProduct = mult(lightDiffuse, materialDiffuse);
    const specularProduct = mult(lightSpecular, materialSpecular);
   
    gl.uniform4fv(
        gl.getUniformLocation(program, 'uAmbientProduct'), ambientProduct);
    gl.uniform4fv(
        gl.getUniformLocation(program, 'uDiffuseProduct'), diffuseProduct);
    gl.uniform4fv(
        gl.getUniformLocation(program, 'uSpecularProduct'), specularProduct);
    gl.uniform1f(
        gl.getUniformLocation(program, 'uShininess'), materialShininess);
    gl.uniform4fv(
        gl.getUniformLocation(program, 'uLightPosition'), lightPosition);
    gl.uniform4fv(
        gl.getUniformLocation(program, 'uViewerPosition'), viewerPosition);

    gl.uniformMatrix4fv(
        gl.getUniformLocation(program, 'uProjectionMatrix'),
        false, flatten(projectionMatrix));
    render();
  };
  function render() {
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    if (flag) {
      theta[axis] += 2.0;
    }
    let modelViewMatrix = mat4();
    modelViewMatrix = mult(modelViewMatrix,
        rotate(theta[xAxis], vec3(1, 0, 0)));
    modelViewMatrix = mult(modelViewMatrix,
        rotate(theta[yAxis], vec3(0, 1, 0)));
    modelViewMatrix = mult(modelViewMatrix,
        rotate(theta[zAxis], vec3(0, 0, 1)));

    gl.uniformMatrix4fv(
        gl.getUniformLocation(program, 'uModelViewMatrix'),
        false, flatten(modelViewMatrix));
    gl.drawArrays(gl.TRIANGLES, 0, numPositions);
    requestAnimationFrame(render);
  };
};

shadedCube();
<!DOCTYPE html>
<html>
    <head>
        <script id="vertex-shader" type="x-shader/x-vertex">
            #version 300 es

            in vec4 aPosition;
            in vec3 aNormal;
            out vec3 N, L, V;

            uniform mat4 uModelViewMatrix, uProjectionMatrix;
            uniform vec4 uLightPosition, uViewerPosition;

            void main()
            {
                vec3 pos = (uModelViewMatrix * aPosition).xyz;
                vec3 light = uLightPosition.xyz;
                vec4 NN = vec4(aNormal,0.0);

                vec3 N = normalize((uModelViewMatrix * NN).xyz);
                vec3 L = normalize(light - pos);
                vec3 V = normalize(uViewerPosition).xyz;

                gl_Position = uProjectionMatrix * uModelViewMatrix * aPosition;
            }
        </script>
        <script id="fragment-shader" type="x-shader/x-fragment">
            #version 300 es

            precision mediump float;
            
            in vec3 N, L, V;
            out vec4 fColor;

            uniform vec4 uAmbientProduct, uDiffuseProduct, uSpecularProduct;
            uniform float uShininess;

            void main()
            {
                vec3 H = normalize(L + V);
                vec4 ambient = uAmbientProduct;

                float Kd = max( dot(L, N), 0.0 );
                vec4 diffuse = Kd * uDiffuseProduct;

                float Ks = pow(max(dot(N, H), 0.0), uShininess);
                vec4 specular = Ks * uSpecularProduct;

                fColor = ambient + diffuse + specular;
                fColor.a = 1.0;
            }
        </script>
        <script type="text/javascript" src="https://interactivecomputergraphics.com/8E/Code/Common/initShaders.js"></script>
        <script type="text/javascript" src="https://interactivecomputergraphics.com/8E/Code/Common/MVnew.js"></script>
        <script type="text/javascript" src="shaded-cube.js"></script>
    </head>
    <body>
        <canvas id="gl-canvas" width="512" height="512">
            Your browser does not support the HTML5 canvas element
        </canvas>
    </body>
</html>


Solution

  • It's happening because you have a typo on these lines:

                    vec3 N = normalize((uModelViewMatrix * NN).xyz);
                    vec3 L = normalize(light - pos);
                    vec3 V = normalize(uViewerPosition).xyz;
    

    Instead of setting to the out variable, you're creating new local variables. You need to remove vec3 from the start of all of these lines.

    These local variables are never used, so NN is never used, so aNormal is never used, and the GLSL compiler optimizes away those variables, so aNormal isn't found when you call gl.vertexAttribPointer.

    Searching for the warning/error shows most questions are from this same problem: unused attributes getting removed from the GLSL.