Sequenced Triangles
Earlier we learned how to render line segments by enumerating pairs of vertices in a vertex buffer and then issuing a call to drawSequence(gl.LINES)
. Triangle sequences are rendered similarly. We enumerate triplets of vertices in a vertex buffer and then issue a call to drawSequence(gl.TRIANGLES)
. This code, which is adapted from the hello-cornflower
renderer, draws a single triangle:
function initialize() {
// ...
const positions = [
-0.5, -0.5, 0, // 0
0.5, -0.5, 0, // 1
-0.5, 0.5, 0, // 2
];
attributes = new VertexAttributes();
attributes.addAttribute('position', 3, 3, positions);
// ...
}
function render() {
// ...
array.drawSequence(gl.TRIANGLES);
// ...
}
function initialize() { // ... const positions = [ -0.5, -0.5, 0, // 0 0.5, -0.5, 0, // 1 -0.5, 0.5, 0, // 2 ]; attributes = new VertexAttributes(); attributes.addAttribute('position', 3, 3, positions); // ... } function render() { // ... array.drawSequence(gl.TRIANGLES); // ... }
The addAttribute
call indicates that there are three vertices, each with a 3-component position. The vertex and fragment shaders used to render lines can be used without modification to render triangles. The resulting triangle looks like this:
The same vertex buffer can be interpreted using the point and line primitives we learned about earlier.
Backface Culling
The vertices of the triangle wind around in a particular order. What happens when you move the vertices so that the triangle winds around in the opposite order?
We see the triangle disappear. While the triangle is gone, switch to a line loop to confirm that the three vertices are still there. Unlike points and lines, triangles have two faces: a front face and a back face. The front face is considered to be the one around which the vertices appear in counter-clockwise order from the viewer's perspective. A front-facing triangle is a viewer-facing triangle. To speed up rendering, graphics developers enable backface culling. Any back-facing triangles are discarded because the viewer can't see them.
Backface culling is enabled with these two WebGL calls:
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
gl.enable(gl.CULL_FACE); gl.cullFace(gl.BACK);
Backface culling is an important optimization, but it is hard to appreciate when we are rendering only a few triangles in a 2D scene. Imagine, however, an immersive 3D cityscape full of four-sided buildings. How many sides of each building might we see at any given moment? In other words, how many sides are front-facing? At most, two. The back-facing sides will be occluded by the front-facing sides, so rasterizing them would be wasteful.
We'll revisit culling later on when we fully enter the third dimension. For the time being, just make sure the triangles are enumerated in counter-clockwise order or leave culling disabled.
Multiple Triangles
Complex models are made by piecing triangles together. A simple but expensive way to render multiple triangles is to add additional triplets to our attributes, like this:
function initialize() {
const positions = [
// Triangle 0
-0.5, -0.5, 0,
0.5, -0.5, 0,
-0.5, 0.5, 0,
// Triangle 1
0.5, 0.5, 0,
1.0, 0.5, 0,
0.0, 1.0, 0,
];
attributes = new VertexAttributes();
attributes.addAttribute('position', 6, 3, positions);
// ...
}
function initialize() { const positions = [ // Triangle 0 -0.5, -0.5, 0, 0.5, -0.5, 0, -0.5, 0.5, 0, // Triangle 1 0.5, 0.5, 0, 1.0, 0.5, 0, 0.0, 1.0, 0, ]; attributes = new VertexAttributes(); attributes.addAttribute('position', 6, 3, positions); // ... }
The addAttribute
call indicates that there are six vertices, each with three components.
The two triangles in this list do not touch, and enumerating them as separate triplets is reasonable. However, many models are made of triangles that stitch together to form a solid surface. Consider this wireframe of a triangulated circle:

All of the vertices in this circle are shared amongst several triangles. If we enumerate the triangles separately, we will be wasting storage with duplicate information. Each of the duplicated vertices will also be wastefully passed through the vertex shader, always producing the same result.
If we need to render a shape that has a common central vertex that all its triangles share, we are better off using a triangle fan. In a triangle fan, the center position is listed first in the positions array. All remaining positions are the vertices on the fan's perimeter.
When we tell the graphics card to draw a triangle fan, it grabs the central vertex and runs it through the vertex shader exactly once. For the first triangle, it grabs the next two vertices from the vertex buffer, runs them through the vertex shader, and combines them with the central vertex. For each remaining triangle, it grabs the next vertex from the buffer, runs it through the vertex shader, and combines it with the central vertex and a vertex from the preceding triangle. No space is wasted on redundant vertices, and the vertex shader runs just once per vertex.
A triangle fan is another geometric primitive supported by WebGL and is drawn just as we draw points, lines, and triangles. We load up a vertex buffer with the vertices and then draw it using drawSequence(gl.TRIANGLE_FAN)
. This code draws a four-pointed star:
function initialize() {
const positions = [
0.0, 0.0, 0, // Vertex 0
1.0, 0.0, 0, // Vertex 1
0.1, 0.1, 0, // Vertex 2
0.0, 1.0, 0, // Vertex 3
-0.1, 0.1, 0, // Vertex 4
-1.0, 0.0, 0, // Vertex 5
-0.1, -0.1, 0, // Vertex 6
0.0, -1.0, 0, // Vertex 7
0.1, -0.1, 0, // Vertex 8
1.0, 0.0, 0, // Vertex 9
];
attributes = new VertexAttributes();
attributes.addAttribute('position', 10, 3, positions);
// ...
}
function render() {
// ...
array.drawSequence(gl.TRIANGLE_FAN);
// ...
}
function initialize() { const positions = [ 0.0, 0.0, 0, // Vertex 0 1.0, 0.0, 0, // Vertex 1 0.1, 0.1, 0, // Vertex 2 0.0, 1.0, 0, // Vertex 3 -0.1, 0.1, 0, // Vertex 4 -1.0, 0.0, 0, // Vertex 5 -0.1, -0.1, 0, // Vertex 6 0.0, -1.0, 0, // Vertex 7 0.1, -0.1, 0, // Vertex 8 1.0, 0.0, 0, // Vertex 9 ]; attributes = new VertexAttributes(); attributes.addAttribute('position', 10, 3, positions); // ... } function render() { // ... array.drawSequence(gl.TRIANGLE_FAN); // ... }
This renderer displays the star using this code but lets us change which primitive is used to interpret the vertex buffer:
Triangle fans in WebGL are not implicitly closed to form a radial shape. That is, no triangle is automatically generated between the last vertex and vertices 0 and 1. To close this star, vertex 9 is explicitly added as a duplicate of vertex 1. This adds the triangle (0, 9, 8) into the fan. One duplicated vertex is a small redundancy, but it can be eliminated with indexed geometry.