2

How can I add a direction light to a transforming cube in webGL.

I know it requires normals and i've add them in the snippet (i've commented them out)

It will also require some math in the vertex shader. unfortunatly this code doesnt work when i uncomment.

attribute vec4 coords;
  uniform mat4 transformMatrix;
  attribute vec3 normal;
  attribute vec4 colors;
  uniform vec3 lightColor;
  uniform vec3 lightDirection;
  varying vec4 varyingColors;
  uniform mat4 perspectiveMatrix;
  void main(void) {
    vec3 norm = normalize(normal);
    vec3 ld = normalize(lightDirection);
    float dotProduct = max(dot(norm, ld), 0.0);
    vec3 vertexColor = lightColor * colors * dotProduct;
    varyingColors = vec4(vertexColor, 1);
    gl_Position = perspectiveMatrix * transformMatrix  * coords;
  }

var gl,
    shaderProgram,
    vertices,
    matrix = mat4.create(),
    vertexCount,
    indexCount,
    q = quat.create(),
    translate =[-3, 0, -10],
    scale = [1,1,1],
    pivot = [0,0,0];
    
    translate2 = [0, 0, -8],
    scale2 = [3,3,3],
    pivot2 = [1,1,1]


initGL();
createShaders();
createVertices();
createIndices();
draw();

function initGL() {
  var canvas = document.getElementById("canvas");
  gl = canvas.getContext("webgl");
  gl.enable(gl.DEPTH_TEST);
  gl.viewport(0, 0, canvas.width, canvas.height);
  gl.clearColor(1, 1, 1, 1);
}

function createShaders() {
  var vertexShader = getShader(gl, "shader-vs");
  var fragmentShader = getShader(gl, "shader-fs");

  shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);
  gl.useProgram(shaderProgram);
}

function createVertices() {
  vertices = [
    -1, -1, -1,     1, 0, 0, 1,     // 0
     1, -1, -1,     1, 1, 0, 1,     // 1
    -1,  1, -1,     0, 1, 1, 1,     // 2
     1,  1, -1,     0, 0, 1, 1,     // 3
    -1,  1,  1,     1, 0.5, 0, 1,   // 4
     1,  1,  1,     0.5, 1, 1, 1,   // 5
    -1, -1,  1,     1, 0, 0.5, 1,   // 6
     1, -1,  1,     0.5, 0, 1, 1,   // 7
  ];

  vertexCount = vertices.length / 7;

  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

  var coords = gl.getAttribLocation(shaderProgram, "coords");
  gl.vertexAttribPointer(coords, 3, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 7, 0);
  gl.enableVertexAttribArray(coords); 


/**
var normals = [
    0, 0, 1,   0, 0, 1,    0, 0, 1,   0, 0, 1,
    0, 1, 0,   0, 1, 0,    0, 1, 0,   0, 1, 0,
    0, 0, -1,  0, 0, -1,   0, 0, -1,  0, 0, -1,
    0, -1, 0,  0, -1, 0,   0, -1, 0,  0, -1, 0,
    -1, 0, 0,  -1, 0, 0,   -1, 0, 0,  -1, 0, 0,
    1, 0, 0,   1, 0, 0,    1, 0, 0,   1, 0, 0
  ];
  
  var normalBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
  
  var normalLocation = gl.getAttribLocation(shaderProgram, "normal");
  gl.vertexAttribPointer(normalLocation, 3, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(normalLocation);  
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  
  var lightColor = gl.getUniformLocation(shaderProgram, "lightColor");
  gl.uniform3f(lightColor, 1, 1, 1);
  
  var lightDirection = gl.getUniformLocation(shaderProgram, "lightDirection");
  gl.uniform3f(lightDirection, 0.5, 1, 0);
 */


  var colorsLocation = gl.getAttribLocation(shaderProgram, "colors");
  gl.vertexAttribPointer(colorsLocation, 4, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 7, Float32Array.BYTES_PER_ELEMENT * 3);

  gl.enableVertexAttribArray(colorsLocation);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  

  var perspectiveMatrix = mat4.create();
  mat4.perspective(perspectiveMatrix, 1, canvas.width / canvas.height, 0.1, 11);
  var perspectiveLoc = gl.getUniformLocation(shaderProgram, "perspectiveMatrix");
  gl.uniformMatrix4fv(perspectiveLoc, false, perspectiveMatrix);

}

function createIndices() {
  var indices = [
    0, 1, 2,   1, 2, 3,
    2, 3, 4,   3, 4, 5,
    4, 5, 6,   5, 6, 7,
    6, 7, 0,   7, 0, 1,
    0, 2, 6,   2, 6, 4,
    1, 3, 7,   3, 7, 5
  ];
  indexCount = indices.length;

  var indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array(indices), gl.STATIC_DRAW);


}

function draw(timeMs) {
  requestAnimationFrame(draw);

  let interval = timeMs / 3000
  let t = interval - Math.floor(interval); 

  let trans_t = vec3.lerp([], translate, translate2, t);
  let scale_t = vec3.lerp([], scale, scale2, t);
  let pivot_t = vec3.lerp([], pivot, pivot2, t);

  mat4.fromRotationTranslationScaleOrigin(matrix, q, trans_t, scale_t, pivot_t);

  var transformMatrix = gl.getUniformLocation(shaderProgram, "transformMatrix");
  gl.uniformMatrix4fv(transformMatrix, false, matrix);
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_BYTE, 0);
}


  /*
   * https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context
   */
  function getShader(gl, id) {
    var shaderScript, theSource, currentChild, shader;

    shaderScript = document.getElementById(id);

    if (!shaderScript) {
      return null;
    }

    theSource = "";
    currentChild = shaderScript.firstChild;

    while (currentChild) {
      if (currentChild.nodeType == currentChild.TEXT_NODE) {
        theSource += currentChild.textContent;
      }

      currentChild = currentChild.nextSibling;
    }
    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 {
      // Unknown shader type
      return null;
    }
    gl.shaderSource(shader, theSource);

// Compile the shader program
    gl.compileShader(shader);

// See if it compiled successfully
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
      return null;
    }

    return shader;
  }
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec4 coords;
attribute float pointSize;
uniform mat4 transformMatrix;
attribute vec4 colors;
varying vec4 varyingColors;
uniform mat4 perspectiveMatrix;
void main(void) {
  gl_Position = perspectiveMatrix * transformMatrix  * coords;
  gl_PointSize = pointSize;
  varyingColors = colors;
}
</script>

<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 color;
varying vec4 varyingColors;
void main(void) {
  gl_FragColor = varyingColors;
}
</script>

<canvas id="canvas" width="600" height="600"></canvas>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>
Armeen Harwood
  • 15,195
  • 30
  • 106
  • 210
  • Consider a vertex coordinate and its attribute as a tuple. If a vertex coordinate is used twice (or 3 times for in three sides) and each time it is associated to a different normal vector, then the coordinate has to be duplicated. Each vertex coordinate is associated to exactly 1 normal vector. – Rabbid76 Apr 30 '19 at 05:01
  • I think I've accounted for each side of the normals? It something wrong I think in the VertexShader :( – Armeen Harwood Apr 30 '19 at 05:24
  • No you didn't. [Rendering meshes with multiple indices](https://stackoverflow.com/questions/11148567/rendering-meshes-with-multiple-indices/11148568#11148568). – Rabbid76 Apr 30 '19 at 05:27

1 Answers1

1

There is an issue in your shader code:

vec3 vertexColor = lightColor * colors * dotProduct;

The type of color is vec4, but the type of vertexColor is vec3, so it has to be:

vec3 vertexColor = lightColor * colors.rgb * dotProduct;

You can't assigne a variable of type vec4 to a variable of type vec3.


When gl.vertexAttribPointer is called, then the proper vertex buffer has to be bound before. At the point where you specify the colorsLocation attribute, the buffer with the normals is bound, rather than than the buffer with the vertices and colors (vertices).


A vertex coordinate and its attributes are tuples. If a vertex coordinate is used twice (or 3 times for in three sides) and each time it is associated to a different normal vector, then the coordinate has to be "duplicated". Each vertex coordinate is associated to exactly 1 normal vector.
See Rendering meshes with multiple indices.

Imagine a 3 dimensional vertex coordinate and a 3 dimensional normal vector as 1, but 6 dimensional attribute.

Create a linearised array of attributes:

vertices = [
  [-1, -1, -1,     1, 0, 0, 1],     // 0
  [ 1, -1, -1,     1, 1, 0, 1],     // 1
  [-1,  1, -1,     0, 1, 1, 1],     // 2
  [ 1,  1, -1,     0, 0, 1, 1],     // 3
  [-1,  1,  1,     1, 0.5, 0, 1],   // 4
  [1,  1,  1,      0.5, 1, 1, 1],   // 5
  [-1, -1,  1,     1, 0, 0.5, 1],   // 6
  [1, -1,  1,      0.5, 0, 1, 1],   // 7
];

var normals = [
  [0, 0, 1], [0, 1, 0], [0, 0, -1],
  [0, -1, 0], [-1, 0, 0], [1, 0, 0] ];

var indices = [
  [0, 1, 2,   1, 2, 3],
  [2, 3, 4,   3, 4, 5],
  [4, 5, 6,   5, 6, 7],
  [6, 7, 0,   7, 0, 1],
  [0, 2, 6,   2, 6, 4],
  [1, 3, 7,   3, 7, 5]
];

var attributes = []
for(let side=0; side < indices.length; ++side) {
    for(let vi=0; vi < indices[side].length; ++vi) {
        attributes.push(...vertices[indices[side][vi]]);
        attributes.push(...normals[side]);
    }
}

vertexCount = attributes.length / 10;

Create the buffer and define the generic arrays of vertex attributes:

var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(attributes), gl.STATIC_DRAW);

var coords = gl.getAttribLocation(shaderProgram, "coords");
gl.vertexAttribPointer(coords, 3, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 10, 0);
gl.enableVertexAttribArray(coords); 

var colorsLocation = gl.getAttribLocation(shaderProgram, "colors");
gl.vertexAttribPointer(colorsLocation, 4, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 10, Float32Array.BYTES_PER_ELEMENT * 3);
gl.enableVertexAttribArray(colorsLocation);

var normalLocation = gl.getAttribLocation(shaderProgram, "normal");
gl.vertexAttribPointer(normalLocation, 3, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 10, Float32Array.BYTES_PER_ELEMENT * 7);
gl.enableVertexAttribArray(normalLocation);  

Use .drawArrays rather than .drawElements to draw the mesh:

//gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_BYTE, 0);
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);

See the example, where I applied the suggestions to your code:

var gl,
    shaderProgram,
    vertices,
    matrix = mat4.create(),
    vertexCount,
    indexCount,
    q = quat.create(),
    translate =[-3, 0, -10],
    scale = [1,1,1],
    pivot = [0,0,0];
    
    translate2 = [0, 0, -8],
    scale2 = [3,3,3],
    pivot2 = [1,1,1]


initGL();
createShaders();
createVertices();
draw();

function initGL() {
  var canvas = document.getElementById("canvas");
  gl = canvas.getContext("webgl");
  gl.enable(gl.DEPTH_TEST);
  gl.viewport(0, 0, canvas.width, canvas.height);
  gl.clearColor(1, 1, 1, 1);
}

function createShaders() {
  var vertexShader = getShader(gl, "shader-vs");
  var fragmentShader = getShader(gl, "shader-fs");

  shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);
  gl.useProgram(shaderProgram);
}

function createVertices() {
vertices = [
  [-1, -1, -1,     1, 0, 0, 1],     // 0
  [ 1, -1, -1,     1, 1, 0, 1],     // 1
  [-1,  1, -1,     0, 1, 1, 1],     // 2
  [ 1,  1, -1,     0, 0, 1, 1],     // 3
  [-1,  1,  1,     1, 0.5, 0, 1],   // 4
  [1,  1,  1,      0.5, 1, 1, 1],   // 5
  [-1, -1,  1,     1, 0, 0.5, 1],   // 6
  [1, -1,  1,      0.5, 0, 1, 1],   // 7
];

var normals = [
  [0, 0, 1], [0, 1, 0], [0, 0, -1],
  [0, -1, 0], [-1, 0, 0], [1, 0, 0] ];

var indices = [
  [0, 1, 2,   1, 2, 3],
  [2, 3, 4,   3, 4, 5],
  [4, 5, 6,   5, 6, 7],
  [6, 7, 0,   7, 0, 1],
  [0, 2, 6,   2, 6, 4],
  [1, 3, 7,   3, 7, 5]
];

var attributes = []
for(let side=0; side < indices.length; ++side) {
    for(let vi=0; vi < indices[side].length; ++vi) {
        attributes.push(...vertices[indices[side][vi]]);
        attributes.push(...normals[side]);
    }
}

  vertexCount = attributes.length / 10;

  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(attributes), gl.STATIC_DRAW);

  var coords = gl.getAttribLocation(shaderProgram, "coords");
  gl.vertexAttribPointer(coords, 3, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 10, 0);
  gl.enableVertexAttribArray(coords); 

  var colorsLocation = gl.getAttribLocation(shaderProgram, "colors");
  gl.vertexAttribPointer(colorsLocation, 4, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 10, Float32Array.BYTES_PER_ELEMENT * 3);
  gl.enableVertexAttribArray(colorsLocation);

  var normalLocation = gl.getAttribLocation(shaderProgram, "normal");
  gl.vertexAttribPointer(normalLocation, 3, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 10, Float32Array.BYTES_PER_ELEMENT * 7);
  gl.enableVertexAttribArray(normalLocation);  
  
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  
  var lightColor = gl.getUniformLocation(shaderProgram, "lightColor");
  gl.uniform3f(lightColor, 1, 1, 1);
  
  var lightDirection = gl.getUniformLocation(shaderProgram, "lightDirection");
  gl.uniform3f(lightDirection, 0.5, 0.5, -1);


  var perspectiveMatrix = mat4.create();
  mat4.perspective(perspectiveMatrix, 1, canvas.width / canvas.height, 0.1, 11);
  var perspectiveLoc = gl.getUniformLocation(shaderProgram, "perspectiveMatrix");
  gl.uniformMatrix4fv(perspectiveLoc, false, perspectiveMatrix);

}

function draw(timeMs) {
  requestAnimationFrame(draw);

  let interval = timeMs / 3000
  let t = interval - Math.floor(interval); 

  let trans_t = vec3.lerp([], translate, translate2, t);
  let scale_t = vec3.lerp([], scale, scale2, t);
  let pivot_t = vec3.lerp([], pivot, pivot2, t);

  mat4.fromRotationTranslationScaleOrigin(matrix, q, trans_t, scale_t, pivot_t);

  var transformMatrix = gl.getUniformLocation(shaderProgram, "transformMatrix");
  gl.uniformMatrix4fv(transformMatrix, false, matrix);
  gl.clear(gl.COLOR_BUFFER_BIT);

  //gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_BYTE, 0);
  gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
}


  /*
   * https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context
   */
  function getShader(gl, id) {
    var shaderScript, theSource, currentChild, shader;

    shaderScript = document.getElementById(id);

    if (!shaderScript) {
      return null;
    }

    theSource = "";
    currentChild = shaderScript.firstChild;

    while (currentChild) {
      if (currentChild.nodeType == currentChild.TEXT_NODE) {
        theSource += currentChild.textContent;
      }

      currentChild = currentChild.nextSibling;
    }
    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 {
      // Unknown shader type
      return null;
    }
    gl.shaderSource(shader, theSource);

// Compile the shader program
    gl.compileShader(shader);

// See if it compiled successfully
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
      return null;
    }

    return shader;
  }
<canvas id="canvas" width="600" height="600"></canvas>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>

<script id="shader-vs" type="x-shader/x-vertex">
attribute vec4 coords;
uniform mat4 transformMatrix;
attribute vec3 normal;
attribute vec4 colors;
uniform vec3 lightColor;
uniform vec3 lightDirection;
varying vec4 varyingColors;
uniform mat4 perspectiveMatrix;
void main(void) {
    vec3 norm = normalize(normal);
    vec3 ld = normalize(lightDirection);
    float dotProduct = max(dot(norm, ld), 0.0);
    vec3 vertexColor = lightColor * colors.rgb * dotProduct;
    varyingColors = vec4(vertexColor, 1);
    gl_Position = perspectiveMatrix * transformMatrix  * coords;
}
</script>

<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 color;
varying vec4 varyingColors;
void main(void) {
  gl_FragColor = varyingColors;
}
</script>
Rabbid76
  • 142,694
  • 23
  • 71
  • 112
  • You're right about the first part, my bad. The second though the answer is misleading. Telling people the coordinate has to be duplicated leads people to mis-understand what vertex shaders really do. I'm not sure what you mean by *hijack the question*. I just don't want people to be told wrong things. – gman Apr 30 '19 at 06:58
  • @gman Maybe my wording is ambiguous because of my bad English. What I tried to point out is, that it is not possible to uns indices and to associate a normal vector to each index rather than each vertex. – Rabbid76 Apr 30 '19 at 07:02