WebGL - Drawing a Quad
WebGL - Drawing a Quad
Section titled “WebGL - Drawing a Quad”While WebGL’s fundamental drawing primitives are points, lines, and triangles, we often need to draw more complex shapes like quadrilaterals (quads) or squares. This chapter demonstrates how to draw a quad by composing it from two triangles, using an index buffer.
Constructing a Quad from Triangles
Section titled “Constructing a Quad from Triangles”A quad can be represented as two adjacent triangles sharing an edge. To draw this efficiently using WebGL, we define the four unique vertices of the quad and then use an index buffer (ELEMENT_ARRAY_BUFFER) to specify which vertices form each of the two triangles.
Consider a quad with vertices 0, 1, 2, 3:
0-------3 | \ | | \ | | \ | | \ | | \ | 1-------2We can define the vertices and then use indices to form triangle 1 (vertices 0, 1, 2) and triangle 2 (vertices 0, 2, 3).
Steps to Draw a Quad using Indices
Section titled “Steps to Draw a Quad using Indices”The process is very similar to drawing a single triangle with indices, but with different geometry data:
Step 1: Setup Canvas and WebGL Context
Standard initialization.
Step 2: Define Geometry (Vertices & Indices) and Buffers
Define the 4 unique vertices of the quad in a Float32Array. Define the 6 indices (3 per triangle) in a Uint16Array (or similar). Create two WebGL buffers: one VBO for vertices (gl.ARRAY_BUFFER) and one IBO for indices (gl.ELEMENT_ARRAY_BUFFER). Load the respective data into each buffer using gl.bufferData.
// Define 4 vertices for the quad (X, Y, Z)const quadVertices = new Float32Array([ -0.5, 0.5, 0.0, // Vertex 0: Top-left -0.5, -0.5, 0.0, // Vertex 1: Bottom-left 0.5, -0.5, 0.0, // Vertex 2: Bottom-right 0.5, 0.5, 0.0 // Vertex 3: Top-right]);
// Define indices for two triangles forming the quadconst quadIndices = new Uint16Array([ 0, 1, 2, // Triangle 1 (0 -> 1 -> 2) 0, 2, 3 // Triangle 2 (0 -> 2 -> 3)]);
// ... (Create VBO & IBO, bufferData for both) ...Step 3: Create and Compile Shaders
Basic vertex and fragment shaders are sufficient. The vertex shader needs to take the position attribute and output gl_Position. The fragment shader sets gl_FragColor.
// Vertex Shader (Simple Pass-through)attribute vec3 a_position;void main() { gl_Position = vec4(a_position, 1.0);}// Fragment Shader (Solid Color)precision mediump float;void main() { gl_FragColor = vec4(0.3, 0.8, 0.4, 1.0); // Greenish color}Step 4: Link Shaders and Buffers
Get attribute locations, enable the position attribute array, and configure gl.vertexAttribPointer to read from the VBO.
Step 5: Drawing the Quad
Clear the canvas, set viewport, etc. Crucially, bind the Index Buffer Object (IBO) to the gl.ELEMENT_ARRAY_BUFFER target before drawing. Then, use gl.drawElements(gl.TRIANGLES, count, type, offset).
mode:gl.TRIANGLEScount: The number of indices to draw (6 for a quad made of two triangles).type: The data type of the indices in the IBO (e.g.,gl.UNSIGNED_SHORT).offset: Starting offset in the IBO (usually 0).
// Bind the index buffer before drawinggl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
// Draw the quad using indicesgl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);Example: Drawing a Quadrilateral
Section titled “Example: Drawing a Quadrilateral”This example draws a solid greenish quad using the steps outlined above.
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>WebGL Drawing a Quad</title> <style> body { margin: 0; overflow: hidden; background-color: #444; } canvas { display: block; border: 1px solid lightblue; } </style></head><body> <canvas id="webgl-canvas" width="500" height="500"></canvas>
<script> // --- Shader Sources --- const vsSource = ` attribute vec3 a_position; void main() { gl_Position = vec4(a_position, 1.0); } `; const fsSource = ` precision mediump float; void main() { gl_FragColor = vec4(0.3, 0.8, 0.4, 1.0); // Greenish } `;
// --- Utilities: createShader, createProgram (Assume defined) --- function createShader(gl, type, source) { /* ... */ const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error(`Shader compilation error (${type === gl.VERTEX_SHADER ? 'Vertex' : 'Fragment'}):`, gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } function createProgram(gl, vsSource, fsSource) { /* ... */ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource); const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource); if (!vertexShader || !fragmentShader) return null; const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Program linking error:', gl.getProgramInfoLog(program)); gl.deleteProgram(program); return null; } return program; }
// --- Main WebGL --- const canvas = document.getElementById('webgl-canvas'); const gl = canvas.getContext('webgl'); if (!gl) { alert('WebGL not supported.'); throw new Error('WebGL not supported'); }
const shaderProgram = createProgram(gl, vsSource, fsSource); if (!shaderProgram) throw new Error('Failed program creation.');
// --- Geometry --- const quadVertices = new Float32Array([ -0.5, 0.5, 0.0, // 0: Top-left -0.5, -0.5, 0.0, // 1: Bottom-left 0.5, -0.5, 0.0, // 2: Bottom-right 0.5, 0.5, 0.0 // 3: Top-right ]); const quadIndices = new Uint16Array([ 0, 1, 2, // First triangle 0, 2, 3 // Second triangle ]);
// --- Buffers --- const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, quadVertices, gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, quadIndices, gl.STATIC_DRAW);
// --- Attribute Setup --- const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'a_position'); if (positionAttribLocation < 0) console.error("Attr 'a_position' not found"); gl.enableVertexAttribArray(positionAttribLocation); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // Bind VBO before pointer gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
// --- Drawing --- gl.useProgram(shaderProgram); // Activate program
gl.clearColor(0.25, 0.25, 0.25, 1.0); // Dark gray background gl.enable(gl.DEPTH_TEST); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// ** Bind Index Buffer ** gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// ** Draw using Indices ** gl.drawElements( gl.TRIANGLES, // Primitive type 6, // Number of indices to draw gl.UNSIGNED_SHORT, // Type of indices (matches Uint16Array) 0 // Offset in buffer );
// Good practice: Unbind buffers after drawing if needed elsewhere gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
</script></body></html>Expected Output:
The code will render a solid greenish square centered on the canvas, filling the area from (-0.5, -0.5) to (0.5, 0.5) in clip space.
Further Exploration:
Section titled “Further Exploration:”- Modify the vertex coordinates to create a rectangle instead of a square.
- Change the index order (e.g.,
1, 2, 3, 1, 3, 0) to see how it affects which triangles are drawn (it should still form the same quad). - Apply colors or transformations to the quad using techniques from other chapters.
- Try drawing the quad using
gl.TRIANGLE_STRIPmode withdrawArraysand only 4 vertices ordered correctly (e.g., 1, 0, 2, 3). Compare the setup required.