DEV Community

Cover image for WebGL Month. Day 16. Colorizing cube: depth buffer and array uniforms
Andrei Lesnitsky
Andrei Lesnitsky

Posted on

WebGL Month. Day 16. Colorizing cube: depth buffer and array uniforms

GitHub stars

Twitter Follow

Join mailing list to get new posts right to your inbox

Built with

Git Tutor Logo

Day 16. Colorizing cube and exploring depth buffer

Hey πŸ‘‹

Welcome to WebGL month

Yesterday we've rendered a cube, but all faces are of the same color, let's change this.

Let's define face colors

πŸ“„ src/3d.js

      20, 21, 22, 20, 22, 23, // left
  ]);

+ const faceColors = [
+     [1.0, 1.0, 1.0, 1.0], // Front face: white
+     [1.0, 0.0, 0.0, 1.0], // Back face: red
+     [0.0, 1.0, 0.0, 1.0], // Top face: green
+     [0.0, 0.0, 1.0, 1.0], // Bottom face: blue
+     [1.0, 1.0, 0.0, 1.0], // Right face: yellow
+     [1.0, 0.0, 1.0, 1.0], // Left face: purple
+ ];
+ 
  const vertexBuffer = new GLBuffer(gl, gl.ARRAY_BUFFER, cubeVertices, gl.STATIC_DRAW);
  const indexBuffer = new GLBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);


Enter fullscreen mode Exit fullscreen mode

Now we need to repeat face colors for each face vertex

πŸ“„ src/3d.js

      [1.0, 0.0, 1.0, 1.0], // Left face: purple
  ];

+ const colors = [];
+ 
+ for (var j = 0; j < faceColors.length; ++j) {
+     const c = faceColors[j];
+     colors.push(
+         ...c, // vertex 1
+         ...c, // vertex 2
+         ...c, // vertex 3
+         ...c, // vertex 4
+     );
+ }
+ 
+ 
  const vertexBuffer = new GLBuffer(gl, gl.ARRAY_BUFFER, cubeVertices, gl.STATIC_DRAW);
  const indexBuffer = new GLBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);


Enter fullscreen mode Exit fullscreen mode

and create a webgl buffer

πŸ“„ src/3d.js



  const vertexBuffer = new GLBuffer(gl, gl.ARRAY_BUFFER, cubeVertices, gl.STATIC_DRAW);
+ const colorsBuffer = new GLBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
  const indexBuffer = new GLBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

  vertexBuffer.bind(gl);

Enter fullscreen mode Exit fullscreen mode

Next we need to define an attribute to pass color from js to vertex shader, and varying to pass it from vertex to fragment shader

πŸ“„ src/shaders/3d.v.glsl

  attribute vec3 position;
+ attribute vec4 color;

  uniform mat4 modelMatrix;
  uniform mat4 viewMatrix;
  uniform mat4 projectionMatrix;

+ varying vec4 vColor;
+ 
  void main() {
      gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
+     vColor = color;
  }

Enter fullscreen mode Exit fullscreen mode

and use it instead of hardcoded red in fragment shader

πŸ“„ src/shaders/3d.f.glsl

  precision mediump float;

+ varying vec4 vColor;
+ 
  void main() {
-     gl_FragColor = vec4(1, 0, 0, 1);
+     gl_FragColor = vColor;
  }

Enter fullscreen mode Exit fullscreen mode

and finally setup vertex attribute in js

πŸ“„ src/3d.js

  vertexBuffer.bind(gl);
  gl.vertexAttribPointer(programInfo.attributeLocations.position, 3, gl.FLOAT, false, 0, 0);

+ colorsBuffer.bind(gl);
+ gl.vertexAttribPointer(programInfo.attributeLocations.color, 4, gl.FLOAT, false, 0, 0);
+ 
  const modelMatrix = mat4.create();
  const viewMatrix = mat4.create();
  const projectionMatrix = mat4.create();

Enter fullscreen mode Exit fullscreen mode

Ok, colors are there, but something is wrong

Rotating colors cube

Let's see what is going on in more details by rendering faces incrementally

let count = 3;

function frame() {
    if (count <= index.data.length) {
        gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_BYTE, 0);
        count += 3;

        setTimeout(frame, 500);
    }
}
Enter fullscreen mode Exit fullscreen mode

Incremental rendering

Seems like triangles which rendered later overlap the ones which are actually closer to the viewer πŸ˜•
How do we fix it?

πŸ“„ src/3d.js

  gl.linkProgram(program);
  gl.useProgram(program);

+ gl.enable(gl.DEPTH_TEST);
+ 
  const programInfo = setupShaderInput(gl, program, vShaderSource, fShaderSource);

  const cubeVertices = new Float32Array([

Enter fullscreen mode Exit fullscreen mode

After vertices are assembled into primitives (triangles) fragment shader paints each pixel inside of triangle, but before calculation of a color fragment passes some "tests". One of those tests is depth and we need to manually enable it.

Other types of tests are:

  • gl.SCISSORS_TEST - whether a fragment inside of a certain triangle (don't confuse this with viewport, there is a special scissor[https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/scissor] method)
  • gl.STENCIL_TEST – similar to a depth, but we can manually define a "mask" and discard some pixels (we'll work with stencil buffer in next tutorials)
  • pixel ownership test – some pixels on screen might belong to other OpenGL contexts (imagine your browser is overlapped by other window), so this pixels get discarded (not painted)

Cool, we now have a working 3d cube, but we're duplicating a lot of colors to fill vertex buffer, can we do it better?
We're using a fixed color palette (6 colors), so we can pass these colors to a shader and use just index of that color.

Let's drop color attrbiute and introduce a colorIndex instead

πŸ“„ src/shaders/3d.v.glsl

  attribute vec3 position;
- attribute vec4 color;
+ attribute float colorIndex;

  uniform mat4 modelMatrix;
  uniform mat4 viewMatrix;

Enter fullscreen mode Exit fullscreen mode

Shaders support "arrays" of uniforms, so we can pass our color palette to this array and use index to get a color out of it

πŸ“„ src/shaders/3d.v.glsl

  uniform mat4 modelMatrix;
  uniform mat4 viewMatrix;
  uniform mat4 projectionMatrix;
+ uniform vec4 colors[6];

  varying vec4 vColor;

  void main() {
      gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
-     vColor = color;
+     vColor = colors[int(colorIndex)];
  }

Enter fullscreen mode Exit fullscreen mode

We need to make appropriate changes to setup color index attribute

πŸ“„ src/3d.js

  const colors = [];

  for (var j = 0; j < faceColors.length; ++j) {
-     const c = faceColors[j];
-     colors.push(
-         ...c, // vertex 1
-         ...c, // vertex 2
-         ...c, // vertex 3
-         ...c, // vertex 4
-     );
+     colors.push(j, j, j, j);
  }


  gl.vertexAttribPointer(programInfo.attributeLocations.position, 3, gl.FLOAT, false, 0, 0);

  colorsBuffer.bind(gl);
- gl.vertexAttribPointer(programInfo.attributeLocations.color, 4, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribPointer(programInfo.attributeLocations.colorIndex, 1, gl.FLOAT, false, 0, 0);

  const modelMatrix = mat4.create();
  const viewMatrix = mat4.create();

Enter fullscreen mode Exit fullscreen mode

To fill an array uniform, we need to set each \"item\" in this array individually, like so

gl.uniform4fv(programInfo.uniformLocations[`colors[0]`], color[0]);
gl.uniform4fv(programInfo.uniformLocations[`colors[1]`], colors[1]);
gl.uniform4fv(programInfo.uniformLocations[`colors[2]`], colors[2]);
...
Enter fullscreen mode Exit fullscreen mode

Obviously this can be done in a loop.

πŸ“„ src/3d.js

      colors.push(j, j, j, j);
  }

+ faceColors.forEach((color, index) => {
+     gl.uniform4fv(programInfo.uniformLocations[`colors[${index}]`], color);
+ });

  const vertexBuffer = new GLBuffer(gl, gl.ARRAY_BUFFER, cubeVertices, gl.STATIC_DRAW);
  const colorsBuffer = new GLBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

Enter fullscreen mode Exit fullscreen mode

Nice, we have the same result, but using 4 times less of data in attributes.

This might seem as an unnecessary optimisation, but it might help when you have to update large buffers frequently

Rotating cube fixed

That's it for today!

See you in next tutorials πŸ‘‹


This is a series of blog posts related to WebGL. New post will be available every day

GitHub stars
Twitter Follow

Join mailing list to get new posts right to your inbox

Source code available here

Built with

Git Tutor Logo

Top comments (0)