WebGL - Geometry
WebGL - Defining Geometry and Buffers
Section titled “WebGL - Defining Geometry and Buffers”To render any object (or primitive) in WebGL, you first need to define its geometry. This typically involves specifying the positions of its vertices and, optionally, how those vertices connect to form shapes (using indices). Other per-vertex data like colors or texture coordinates can also be included.
This geometric data originates in your JavaScript code, often stored in Typed Arrays, and must be transferred to the GPU’s memory for the shaders to access. This transfer is done using WebGL Buffer Objects.
Defining Geometry Data in JavaScript
Section titled “Defining Geometry Data in JavaScript”You define the vertices (and optionally indices) for your shapes using JavaScript Typed Arrays, which provide raw binary data views essential for WebGL.
Example: A Simple 2D Triangle
Section titled “Example: A Simple 2D Triangle”Vertices define the corner points. For a triangle with coordinates (0.0, 0.5), (-0.5, -0.5), and (0.5, -0.5):
const triangleVertices = new Float32Array([ 0.0, 0.5, // Vertex 0 (Top) -0.5, -0.5, // Vertex 1 (Bottom Left) 0.5, -0.5 // Vertex 2 (Bottom Right)]);If drawing this triangle directly with gl.drawArrays, you don’t strictly need indices. However, if using gl.drawElements or defining more complex shapes, indices are used.
Example: A Square Using Indices
Section titled “Example: A Square Using Indices”A square can be made from two triangles sharing vertices. Vertices: 0=(-0.5, 0.5), 1=(0.5, 0.5), 2=(0.5, -0.5), 3=(-0.5, -0.5).
const squareVertices = new Float32Array([ -0.5, 0.5, // Vertex 0 0.5, 0.5, // Vertex 1 0.5, -0.5, // Vertex 2 -0.5, -0.5 // Vertex 3]);
// Indices define two triangles: (0, 3, 1) and (1, 3, 2)const squareIndices = new Uint16Array([ 0, 3, 1, 1, 3, 2]);Using indices (Uint16Array or Uint32Array) allows reusing vertex data, saving memory and potentially boosting performance.
Buffer Objects (VBOs and IBOs)
Section titled “Buffer Objects (VBOs and IBOs)”WebGL buffer objects are memory allocations on the GPU used to store rendering data. They are essential for efficiently transferring your geometry (and other data) from the CPU (JavaScript) to the GPU (Shaders).
The two primary types for geometry are:
- Vertex Buffer Object (VBO): Stores per-vertex data like coordinates, colors, texture coordinates, normals. Bound to the
gl.ARRAY_BUFFERtarget. - Index Buffer Object (IBO): Stores vertex indices for indexed drawing (
gl.drawElements). Bound to thegl.ELEMENT_ARRAY_BUFFERtarget.
Workflow for Using Buffers:
- Create Buffer: Use
gl.createBuffer()to get an empty buffer object reference. - Bind Buffer: Use
gl.bindBuffer(target, buffer)to make a buffer active on a specific target (gl.ARRAY_BUFFERfor VBOs,gl.ELEMENT_ARRAY_BUFFERfor IBOs). All subsequent operations on that target affect the bound buffer. - Upload Data: Use
gl.bufferData(target, data, usage)to allocate memory and copy data from your TypedArray (data) into the currently bound buffer. Theusagehint (e.g.,gl.STATIC_DRAW,gl.DYNAMIC_DRAW) tells WebGL how you intend to use the data, potentially allowing for optimizations. - Unbind Buffer (Optional but Recommended): Use
gl.bindBuffer(target, null)to unbind the buffer from the target. This prevents accidental modification. - Configure Attributes (for VBOs): Later, you’ll bind the VBO again and tell WebGL how vertex attributes in your shader should read data from it using
gl.vertexAttribPointer.
Buffer Usage Hints (gl.bufferData)
Section titled “Buffer Usage Hints (gl.bufferData)”The usage parameter in gl.bufferData helps the WebGL driver optimize memory placement and access:
gl.STATIC_DRAW: Data is set once and used many times (e.g., static model geometry). Most common.gl.DYNAMIC_DRAW: Data is updated frequently and used many times (e.g., vertices for an animated character).gl.STREAM_DRAW: Data is set once and used only a few times (e.g., particle effects data).
These are hints; the implementation might treat them similarly, but providing the correct hint is good practice.
Example: Creating and Populating Buffers
Section titled “Example: Creating and Populating Buffers”// Assume 'gl' is the WebGLRenderingContext// Assume 'triangleVertices' and 'squareIndices' TypedArrays exist
// --- Vertex Buffer (VBO) ---const vertexBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);gl.bufferData(gl.ARRAY_BUFFER, triangleVertices, gl.STATIC_DRAW);// gl.bindBuffer(gl.ARRAY_BUFFER, null); // Optional unbind
// --- Index Buffer (IBO) ---const indexBuffer = gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, squareIndices, gl.STATIC_DRAW);// gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); // Optional unbindVertex Array Objects (VAOs)
Section titled “Vertex Array Objects (VAOs)”Setting up vertex attributes involves binding the correct VBO and calling gl.vertexAttribPointer and gl.enableVertexAttribArray for each attribute. This setup can be complex, especially for models with multiple attributes.
A Vertex Array Object (VAO) stores all this configuration state: which VBOs are bound, how each attribute reads from its VBO, and which attributes are enabled.
Workflow with VAOs:
- Create VAO:
const vao = gl.createVertexArray();(Note: In WebGL 1, requiresOES_vertex_array_objectextension:gl.createVertexArrayOES()) - Bind VAO:
gl.bindVertexArray(vao); - Setup Buffers and Attributes: Perform all
gl.bindBuffer,gl.bufferData,gl.vertexAttribPointer,gl.enableVertexAttribArraycalls needed for your object. Also bind the IBO (gl.ELEMENT_ARRAY_BUFFER) if using one. - Unbind VAO:
gl.bindVertexArray(null);(to prevent accidental changes) - To Draw: Simply bind the VAO (
gl.bindVertexArray(vao);) and then make your draw call (gl.drawArraysorgl.drawElements). All associated buffer bindings and attribute configurations are restored automatically.
VAOs significantly simplify rendering loops and often improve performance by reducing the number of WebGL state changes per frame. They are standard and required in WebGL 2.
WebGL Buffer API Methods
Section titled “WebGL Buffer API Methods”Here’s a summary of key buffer-related methods:
| Method | Description |
|---|---|
gl.createBuffer() | Creates a new, empty buffer object. |
gl.deleteBuffer(buffer) | Deletes a previously created buffer object, freeing GPU memory. |
gl.bindBuffer(target, buffer) | Binds a buffer object to a specific target (gl.ARRAY_BUFFER or gl.ELEMENT_ARRAY_BUFFER). Pass null to unbind. |
gl.bufferData(target, dataOrSize, usage) | Allocates buffer memory and optionally uploads data from a TypedArray or ArrayBuffer (dataOrSize). If dataOrSize is a number, it allocates that many bytes without initializing. |
gl.bufferSubData(target, offset, data) | Updates a subset of an existing buffer’s data without reallocating memory. offset is the byte offset to start writing. |
gl.isBuffer(buffer) | Returns true if the provided object is a valid WebGL buffer. |
gl.getBufferParameter(target, pname) | Retrieves information about the buffer currently bound to target. pname can be gl.BUFFER_SIZE or gl.BUFFER_USAGE. |