Skip to content

WebGL - Drawing a Model

Once vertex data is loaded into buffers and associated with shader attributes, the final step is rendering the geometry. WebGL provides two primary methods for this: drawArrays() and drawElements(). These functions instruct the GPU to draw primitives based on the bound buffer data and the currently active shader program.

drawArrays() is used when your vertex data is ordered sequentially in the buffer, and you don’t need separate index data.

Syntax:

gl.drawArrays(mode, first, count);

This method takes three parameters:

  • mode: Specifies the type of primitive to render. Common values include gl.POINTS, gl.LINE_STRIP, gl.LINE_LOOP, gl.LINES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN, and gl.TRIANGLES. The choice depends on how you want WebGL to interpret your vertex data to form shapes.
  • first: An integer specifying the starting index in the enabled vertex attribute arrays. Usually 0.
  • count: An integer specifying the number of vertices to be rendered.

When using drawArrays(), WebGL processes the vertices in the order they appear in the bound ARRAY_BUFFER. For example, with gl.TRIANGLES, every three consecutive vertices form a triangle.

To draw a single triangle using drawArrays(), you provide three vertices and specify 3 for the count parameter.

// Assuming 'gl' is your WebGL rendering context
// Assuming vertex buffer with these coordinates is already bound and configured
const vertices = [
// X, Y, Z
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0
];
// Draw the triangle
gl.drawArrays(gl.TRIANGLES, 0, 3); // 3 vertices form one triangle

This code, combined with appropriate vertex and fragment shaders, would render a single triangle on the canvas.

To draw two separate triangles using gl.TRIANGLES, you need six vertices in total.

// Assuming 'gl' is your WebGL rendering context
// Assuming vertex buffer with these coordinates is already bound and configured
const vertices = [
// Triangle 1
-0.9, -0.5, 0.0,
-0.1, -0.5, 0.0,
-0.5, 0.5, 0.0,
// Triangle 2
0.1, -0.5, 0.0,
0.9, -0.5, 0.0,
0.5, 0.5, 0.0
];
// Draw two triangles
gl.drawArrays(gl.TRIANGLES, 0, 6); // 6 vertices form two separate triangles

This would render two distinct triangles side-by-side.

drawElements() is used when you want to reuse vertices by specifying their order using indices. This is generally more efficient for complex models as it reduces the amount of vertex data needed.

Syntax:

gl.drawElements(mode, count, type, offset);

This method takes four parameters:

  • mode: Same as in drawArrays(), specifying the primitive type (e.g., gl.TRIANGLES).
  • count: An integer specifying the number of elements (indices) to be rendered from the index buffer.
  • type: Specifies the data type of the values in the element array buffer (index buffer). Must be gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT, or gl.UNSIGNED_INT (the latter requires the OES_element_index_uint extension in WebGL 1 or is standard in WebGL 2). gl.UNSIGNED_SHORT is common.
  • offset: A byte offset into the element array buffer. Usually 0.

Using drawElements() requires you to create and bind an ELEMENT_ARRAY_BUFFER containing the indices. WebGL uses these indices to look up vertices in the ARRAY_BUFFER. This allows vertices shared between multiple primitives (like triangles in a mesh) to be stored only once.

To draw a single triangle using indices, you define the vertices and an index array specifying which vertices form the triangle.

// Assuming 'gl' is your WebGL rendering context
// Assuming vertex buffer is bound to gl.ARRAY_BUFFER
// Assuming index buffer is bound to gl.ELEMENT_ARRAY_BUFFER
const vertices = [
// X, Y, Z (Vertex 0)
-0.5, -0.5, 0.0,
// X, Y, Z (Vertex 1)
0.5, -0.5, 0.0,
// X, Y, Z (Vertex 2)
0.0, 0.5, 0.0
];
const indices = [0, 1, 2]; // Use vertices 0, 1, and 2 to form the triangle
// Create and bind buffers, load data (details omitted for brevity)
// ... buffer setup code ...
// Draw the triangle using indices
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

This renders the same triangle as the drawArrays example, but demonstrates the use of indices.

Example: Drawing Two Contiguous Triangles (forming a Quad)

Section titled “Example: Drawing Two Contiguous Triangles (forming a Quad)”

drawElements() shines when sharing vertices. To draw two triangles forming a quad, you only need four vertices.

// Assuming 'gl' is your WebGL rendering context
// Assuming vertex and index buffers are set up and bound
const vertices = [
// X, Y, Z
-0.5, 0.5, 0.0, // Top-left (Vertex 0)
-0.5, -0.5, 0.0, // Bottom-left (Vertex 1)
0.5, -0.5, 0.0, // Bottom-right(Vertex 2)
0.5, 0.5, 0.0 // Top-right (Vertex 3)
];
// Indices for two triangles sharing vertices 1 and 3 (or 0 and 2 depending on order)
const indices = [
0, 1, 2, // First triangle (top-left, bottom-left, bottom-right)
0, 2, 3 // Second triangle (top-left, bottom-right, top-right)
];
// Create and bind buffers, load data (details omitted for brevity)
// ... buffer setup code ...
// Draw the quad (two triangles) using indices
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

This renders a quadrilateral composed of two triangles, efficiently reusing the corner vertices.

Before calling drawArrays() or drawElements(), several setup steps are typically required in each rendering frame:

Set the color WebGL will use to clear the drawing buffer using clearColor(). This effectively sets the background color. It takes four arguments: red, green, blue, and alpha (all ranging from 0.0 to 1.0).

// Set clear color to a medium gray
gl.clearColor(0.5, 0.5, 0.5, 1.0);

You then perform the actual clearing using the clear() method (see step 3).

For 3D rendering, enable depth testing to ensure objects closer to the viewer correctly obscure objects farther away. This uses the depth buffer.

gl.enable(gl.DEPTH_TEST);

Without depth testing, objects might be drawn in the order they are submitted, leading to incorrect visual overlap.

Before drawing a new frame, clear the relevant buffers. Typically, you clear the color buffer and the depth buffer.

// Clear the color buffer and the depth buffer
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

The | (bitwise OR) operator combines the flags to clear multiple buffers in one call.

Define the rectangular area of the canvas where WebGL should draw. This is typically set to the full size of the canvas.

// Assuming 'canvas' is your HTML canvas element
gl.viewport(0, 0, canvas.width, canvas.height);

The parameters are (x, y, width, height), defining the bottom-left corner and the dimensions of the viewport within the canvas.

After performing these setup steps, binding the necessary buffers, configuring vertex attributes, and selecting the shader program, you can call drawArrays() or drawElements() to render your scene.