WebGL Month. Day 16. Colorizing cube: depth buffer and array uniforms
Andrei Lesnitsky
Posted on July 16, 2019
Join mailing list to get new posts right to your inbox
Built with
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
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);
}
}
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
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
Join mailing list to get new posts right to your inbox
Built with
Posted on July 16, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.