WebGL VBO (Vertex Buffer Object)
Related Topics: OpenGL VBO
- Creating VBO (Vertex Buffer Object)
- Drawing VBO
- VAO (Vertex Array Object)
- Example: Drawing a Triangle
In order to draw a 3D geometry, the vertex data such as vertex positions, normals, texture coordinates, and colors must be transferred to GPU's buffer objects first. This buffer object is called VBO (Vertex Buffer Object). Once all vertex data are stored in a VBO, each vertex data will be processed by the vertex shader program during the vertex operation one by one.
Creating VBO
Creating a VBO requires 3 steps;
- Generate a new buffer object with gl.genBuffer().
- Bind the buffer object with gl.bindBuffer().
- Copy vertex data to the buffer object with gl.bufferData() or gl.bufferSubData()().
There are 2 types of VBO; gl.ARRAY_BUFFER for vertex attributes (positions, normals, texture coordinates), and gl.ELEMENT_ARRAY_BUFFER for indices.
Since WebGL VBO requires a fixed-size of data types, you must use typed arrays in JavaScript, typically Float23Array for vertex arrays and Uint16Array for index arrays.
// allocate typed arrays for 3 vertices in a triangle
let vertices = new Float32Array(3 * 3);
let normals = new Float32Array(3 * 3);
let colors = new Float32Array(3 * 4);
let indices = new Uint16Array([0,1,2]);
// compute total # of bytes and offsets
let dataSize = vertices.byteLength + normals.byteLength + colors.byteLength;
let vertexOffset = 0;
let normalOffset = vertices.byteLength;
let colorOffest = vertices.byteLength + normals.byteLength;
// set values for positions, normals, colors
...
// create vbo and copy vertex data
let vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo); // bind
gl.bufferData(gl.ARRAY_BUFFER, dataSize, gl.STATIC_DRAW); // allocate total size
gl.bufferSubData(gl.ARRAY_BUFFER, vertexOffset, vertices); // copy positions
gl.bufferSubData(gl.ARRAY_BUFFER, normalOffset, normals); // copy normals
gl.bufferSubData(gl.ARRAY_BUFFER, colorOffset, colors); // copy colors
gl.bindBuffer(gl.ARRAY_BUFFER, null); // unbind
// create vbo for indices and copy index data
let ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo); // bind
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices); // copy indices
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); // unbind
Drawing VBO
The vertex shader reads each vertex data from a VBO and transforms one by one during the rendering process. Therefore, the required vertex arrays in the vertex shader must be enabled and referenced by the vertex shader.
The steps to draw a VBO are;
- Bind the VBO with gl.bindBuffer().
- Activate vertex arrays using gl.enableVertexAttribArray().
- Set the offsets to the vertex arrays using gl.vertexAttribPointer().
- Draw VBO using gl.drawArrays() or gl.drawElements().
gl.enableVertexAttribArray() activates the vertex attributes that are used in the vertex shader. The parameter of this function is the location ID of the input attributes. gl.vertexAttribPointer() is to reference the vertex array; the size, data type and offset of the array.
WebGL provides 2 core drawing functions. gl.drawArrays() is to draw the geometry straight through the VBO without hopping or skipping. gl.drawElements() is to access the VBO by hopping around vertex arrays using the associated array indices.
// activate attributes for shader program
program.attribute.position = gl.getAttribLocation(program, "vertexPosition");
program.attribute.normal = gl.getAttribLocation(program, "vertexNormal");
program.attribute.color = gl.getAttribLocation(gl.program, "vertexColor");
gl.enableVertexAttribArray(program.attribute.position);
gl.enableVertexAttribArray(program.attribute.normal);
gl.enableVertexAttribArray(program.attribute.color);
...
// set attribute pointers before drawing
// PARAMS: size, type, normalized, stride, offset
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.vertexAttribPointer(program.attribute.position, 3, gl.FLOAT, false, 0, vertexOffset);
gl.vertexAttribPointer(program.attribute.normal, 3, gl.FLOAT, false, 0, normalOffset);
gl.vertexAttribPointer(program.attribute.color, 4, gl.FLOAT, false, 0, colorOffset);
// draw with drawArrays()
// PARAMS: primitive type, offset, # of vertices
gl.drawArrays(gl.TRIANGLES, 0, 3);
// draw with indices
// PARAMS: primitive type, # of indices, type, offset
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);
VAO (Vertex Array Object)
WebGL 2 supports VAO (Vertex Array Object) to remember all VBO related calls into a VAO. So, it simplifies the drawing by reducing function calls.
The steps to store VBO calls in a VAO are;
- Create a VAO using gl.createVertexArray().
- Bind the VAO with gl.bindVertexArray().
- Calls VBO related functions to be stored in the VAO.
- Unbind the VAO.
// create VAO and bind
let vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// store VBO calls into VAO
vbo = gl.createBuffer(); // vbo for vertices
gl.bindBuffer(gl.ARRAY_BUFFER, vbo); // bind vbo
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // copy data to vbo
gl.enableVertexAttribArray(program.attribute.position);
gl.vertexAttribPointer(program.attribute.position, 3, gl.FLOAT, false, 0, 0);
// unbind VAO
gl.bindVertexArray(null);
...
// draw with VAO
// NOTE: no need to call vertexAttribPointer()s
gl.bindVertexArray(vao);
gl.drawArrays(gl.TRIANGLES, 0, 3);
Example: Drawing a Triangle
This example is drawing a triangle using gl.drawArrays(). Note that we only provide 3 vertex attributes to WebGL. The rest of pixels (fragments) inside the triangle will be computed by interpolating the known 3 points during WebGL's rasterization operation.
// minimal JS implementation
// See the full JS code from the download link
// define vertex attribs: position, normal, color
// v0
// / \
// v1---v2
gl.v0 = new Vector3(0, 1, 0); // v0
gl.v1 = new Vector3(-1, -1, 0); // v1
gl.v2 = new Vector3(1, -1, 0); // v2
gl.n0 = new Vector3(0, 0, 1); // n0
gl.n1 = new Vector3(0, 0, 1); // n1
gl.n2 = new Vector3(0, 0, 1); // n2
gl.c0 = new Vector4(1, 0, 0, 1); // c0, red
gl.c1 = new Vector4(0, 1, 0, 1); // c1, green
gl.c2 = new Vector4(0, 0, 1, 1); // c2, blue
// init VBO
// interleaved vertex array of a triangle: V - N - C
let v = new Float32Array(3 * 10);
// v0
v[0] = gl.v0.x; v[1] = gl.v0.y; v[2] = gl.v0.z; // position
v[3] = gl.n0.x; v[4] = gl.n0.y; v[5] = gl.n0.z; // normal
v[6] = gl.c0.x; v[7] = gl.c0.y; v[8] = gl.c0.z; v[9] = gl.c0.w; // color
// v1
v[10] = gl.v1.x; v[11] = gl.v1.y; v[12] = gl.v1.z; // position
v[13] = gl.n1.x; v[14] = gl.n1.y; v[15] = gl.n1.z; // normal
v[16] = gl.c1.x; v[17] = gl.c1.y; v[18] = gl.c1.z; v[19] = gl.c1.w; // color
// v2
v[20] = gl.v2.x; v[21] = gl.v2.y; v[22] = gl.v2.z; // position
v[23] = gl.n2.x; v[24] = gl.n2.y; v[25] = gl.n2.z; // normal
v[26] = gl.c2.x; v[27] = gl.c2.y; v[28] = gl.c2.z; v[29] = gl.c2.w; // color
// create interleaved vertex buffer
gl.vbo = gl.createBuffer(); // create
gl.bindBuffer(gl.ARRAY_BUFFER, gl.vbo); // bind
gl.bufferData(gl.ARRAY_BUFFER, v, gl.STATIC_DRAW); // copy data to vbo
gl.bindBuffer(gl.ARRAY_BUFFER, null); // unbind
...
// set shader attributes
gl.enableVertexAttribArray(gl.program.attribute.vertexPosition);
gl.enableVertexAttribArray(gl.program.attribute.vertexNormal);
gl.enableVertexAttribArray(gl.program.attribute.vertexColor);
...
// draw triangle
gl.bindBuffer(gl.ARRAY_BUFFER, gl.vbo);
gl.vertexAttribPointer(gl.program.attribute.vertexPosition, 3, gl.FLOAT, false, 40, 0);
gl.vertexAttribPointer(gl.program.attribute.vertexNormal, 3, gl.FLOAT, false, 40, 3*4);
gl.vertexAttribPointer(gl.program.attribute.vertexColor, 4, gl.FLOAT, false, 40, 6*4);
gl.drawArrays(gl.TRIANGLES, 0, 3);
Fullscreen Demo: test_vbo.html, test_vbo_min.html, test_vao.html
GitHub Repo: test_vbo.html