DEV Community

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

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

lesnitsky profile image Andrei Lesnitsky ・5 min read

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);


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);


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);

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

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

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();

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);
    }
}

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([

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;

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)];
  }

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();

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]);
...

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);

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

Discussion (0)

pic
Editor guide