WebGL - Drawing a Triangle
WebGL - Drawing a Triangle
Section titled “WebGL - Drawing a Triangle”Drawing a simple shape like a triangle is often the “Hello, World!” of graphics programming. It involves all the fundamental steps of a WebGL application: setting up the canvas, defining geometry, writing shaders, sending data to the GPU, and triggering the draw call.
This chapter demonstrates drawing a triangle using an index buffer (drawElements), which is a common and efficient approach, especially for more complex models where vertices are shared between multiple triangles.
Core Steps to Draw a Triangle
Section titled “Core Steps to Draw a Triangle”Here’s a breakdown of the necessary steps:
Step 1: Prepare the Canvas and Get WebGL Context
Section titled “Step 1: Prepare the Canvas and Get WebGL Context”First, you need an HTML <canvas> element. Then, use JavaScript to get a reference to it and request a WebGL rendering context.
<canvas id="glCanvas" width="640" height="480"></canvas>const canvas = document.getElementById('glCanvas');// Try getting the standard WebGL context firstconst gl = canvas.getContext('webgl');
if (!gl) { console.error("Unable to initialize WebGL. Your browser or machine may not support it."); // Optionally try 'experimental-webgl' for older browsers // gl = canvas.getContext('experimental-webgl'); // if (!gl) { ... handle error ... }}
// If context is obtained, proceed...Step 2: Define Geometry and Store in Buffers
Section titled “Step 2: Define Geometry and Store in Buffers”Define the vertex positions and the indices that form the triangle. Then, create WebGL buffer objects (ARRAY_BUFFER for vertices, ELEMENT_ARRAY_BUFFER for indices) and load the data into them.
// Define vertex positions (X, Y, Z)const positions = [ -0.5, -0.5, 0.0, // Vertex 0: Bottom-left 0.5, -0.5, 0.0, // Vertex 1: Bottom-right 0.0, 0.5, 0.0 // Vertex 2: Top-center];
// Define the indices that form the triangle (using vertex indices 0, 1, 2)const indices = [0, 1, 2];
// --- Create and load position buffer ---const positionBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);// Note: It's good practice to unbind after configuration, but not strictly necessary// gl.bindBuffer(gl.ARRAY_BUFFER, null);
// --- Create and load index buffer ---const indexBuffer = gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);// gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);gl.STATIC_DRAW is a hint to WebGL that the data will not change often.
Step 3: Create, Compile, and Link Shader Programs
Section titled “Step 3: Create, Compile, and Link Shader Programs”Write the vertex and fragment shader code in GLSL, then use WebGL functions to compile them and link them into a usable shader program. Remember to include error checking!
Vertex Shader (GLSL):
attribute vec3 a_position; // Receive vertex position attribute
void main() { // Pass the position directly to the output (no transformation) // We use vec4 because gl_Position requires (x, y, z, w) gl_Position = vec4(a_position, 1.0);}Fragment Shader (GLSL):
precision mediump float; // Necessary precision declaration
void main() { // Set the fragment color to opaque orange gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0);}JavaScript (Compilation/Linking - Conceptual, use helper functions):
// Assume vsSource and fsSource contain the GLSL code strings// Assume loadShader and initShaderProgram helper functions exist (as shown previously)const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);const shaderProgram = initShaderProgram(gl, vertexShader, fragmentShader);
// If shaderProgram is null, an error occurred during setupif (!shaderProgram) { // Handle error - likely reported by helper functions return;}Step 4: Associate Shader Attributes with Buffer Data
Section titled “Step 4: Associate Shader Attributes with Buffer Data”Get the location of the attribute variable (a_position) in the shader program. Then, tell WebGL how to pull data from the currently bound ARRAY_BUFFER (our positionBuffer) for that attribute.
// Get the location of the attributeconst positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
// Bind the position buffer again (if not already bound)gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell WebGL how to pull data out of the position bufferconst numComponents = 3; // (X, Y, Z)const type = gl.FLOAT; // Data typeconst normalize = false; // Don't normalizeconst stride = 0; // Bytes to skip between elements (0 = tightly packed)const offset = 0; // Bytes offset from the beginninggl.vertexAttribPointer( positionAttributeLocation, numComponents, type, normalize, stride, offset);
// Enable the vertex attribute arraygl.enableVertexAttribArray(positionAttributeLocation);Step 5: Set Up Render State and Draw the Triangle
Section titled “Step 5: Set Up Render State and Draw the Triangle”Clear the canvas, set the viewport, tell WebGL to use the shader program, ensure the index buffer is bound, and finally, call drawElements.
// Set clear color to light gray and clear the canvasgl.clearColor(0.8, 0.8, 0.8, 1.0);gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Set the viewport to match the canvas sizegl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Tell WebGL to use our shader programgl.useProgram(shaderProgram);
// Bind the index buffer (important before drawElements)gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// Draw the triangle!const primitiveType = gl.TRIANGLES; // Draw trianglesconst count = indices.length; // Number of indices to drawconst indexType = gl.UNSIGNED_SHORT; // Type of data in index bufferconst drawOffset = 0; // Start at the beginning of the index buffergl.drawElements(primitiveType, count, indexType, drawOffset);Complete Example (Conceptual Structure)
Section titled “Complete Example (Conceptual Structure)”Below is the conceptual structure of the HTML and JavaScript file. You would fill this in with the code snippets from the steps above, including robust error checking.
<!doctype html><html><head> <title>WebGL Triangle</title> <meta charset="utf-8"></head><body> <canvas id="glCanvas" width="640" height="480"></canvas> <script> // --- Helper function: loadShader --- function loadShader(gl, type, source) { /* ... see previous chapter ... */ }
// --- Helper function: initShaderProgram --- function initShaderProgram(gl, vsSource, fsSource) { /* ... see previous chapter ... */ }
// --- Main function --- function main() { // Step 1: Get Canvas & Context const canvas = document.getElementById('glCanvas'); const gl = canvas.getContext('webgl'); if (!gl) { /* handle error */ return; }
// Step 3: Define Shader Sources const vsSource = `...`; // Vertex shader GLSL code const fsSource = `...`; // Fragment shader GLSL code
// Step 3: Compile and Link Shaders const shaderProgram = initShaderProgram(gl, vsSource, fsSource); if (!shaderProgram) { /* handle error */ return; }
// Step 2: Define Geometry const positions = [/* ... vertex data ... */]; const indices = [/* ... index data ... */];
// Step 2: Create and Load Buffers const positionBuffer = gl.createBuffer(); /* ... bind and bufferData for positions ... */ const indexBuffer = gl.createBuffer(); /* ... bind and bufferData for indices ... */
// Step 4: Associate Shaders and Buffers const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position'); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.vertexAttribPointer(/* ... parameters ... */); gl.enableVertexAttribArray(positionAttributeLocation);
// Step 5: Set Up Render State and Draw gl.clearColor(0.8, 0.8, 0.8, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); gl.useProgram(shaderProgram); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0); }
// Run the main function when the page loads window.onload = main; </script></body></html>Running this code in a WebGL-enabled browser should display a single, solid orange triangle centered horizontally, pointing upwards, against a light gray background.