VertexArray
The in
variables of a vertex shader must be bound to the vertex buffers. In WebGL, this binding is called a vertex array object (VAO). The abbreviation—pronounced vow by some—is apt because a VAO is a marriage between the VertexAttributes
and the ShaderProgram
. As you might guess, managing a VAO requires low-level operations whose yuck we will put behind an abstraction.
Create the file lib/vertex-array.ts
and copy in this code:
import {ShaderProgram} from './shader-program.js';
import {VertexAttributes} from './vertex-attributes.js';
export class VertexArray {
attributes: VertexAttributes;
vertexArray: WebGLVertexArrayObject;
isBound: boolean;
constructor(program: ShaderProgram, attributes: VertexAttributes) {
this.isBound = false;
this.vertexArray = gl.createVertexArray()!;
this.attributes = attributes;
gl.bindVertexArray(this.vertexArray);
for (let attribute of attributes) {
let location = program.getAttributeLocation(attribute.name);
if (location < 0) {
console.debug(`${attribute.name} is not used in the shader.`);
} else {
gl.bindBuffer(gl.ARRAY_BUFFER, attribute.buffer);
gl.vertexAttribPointer(location, attribute.ncomponents, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(location);
}
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, attributes.indexBuffer);
this.unbind();
}
bind() {
gl.bindVertexArray(this.vertexArray);
this.isBound = true;
}
destroy() {
gl.deleteVertexArray(this.vertexArray);
}
unbind() {
gl.bindVertexArray(null);
this.isBound = false;
}
drawSequence(mode: number) {
gl.drawArrays(mode, 0, this.attributes.vertexCount);
}
drawIndexed(mode: number) {
gl.drawElements(mode, this.attributes.indexCount, gl.UNSIGNED_INT, 0);
}
}
import {ShaderProgram} from './shader-program.js'; import {VertexAttributes} from './vertex-attributes.js'; export class VertexArray { attributes: VertexAttributes; vertexArray: WebGLVertexArrayObject; isBound: boolean; constructor(program: ShaderProgram, attributes: VertexAttributes) { this.isBound = false; this.vertexArray = gl.createVertexArray()!; this.attributes = attributes; gl.bindVertexArray(this.vertexArray); for (let attribute of attributes) { let location = program.getAttributeLocation(attribute.name); if (location < 0) { console.debug(`${attribute.name} is not used in the shader.`); } else { gl.bindBuffer(gl.ARRAY_BUFFER, attribute.buffer); gl.vertexAttribPointer(location, attribute.ncomponents, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(location); } } gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, attributes.indexBuffer); this.unbind(); } bind() { gl.bindVertexArray(this.vertexArray); this.isBound = true; } destroy() { gl.deleteVertexArray(this.vertexArray); } unbind() { gl.bindVertexArray(null); this.isBound = false; } drawSequence(mode: number) { gl.drawArrays(mode, 0, this.attributes.vertexCount); } drawIndexed(mode: number) { gl.drawElements(mode, this.attributes.indexCount, gl.UNSIGNED_INT, 0); } }
The marriage ceremony is short and sweet, consisting only of a constructor call:
vao = new VertexArray(shaderProgram, attributes);
vao = new VertexArray(shaderProgram, attributes);
Add this assignment after creating the shader program in main.ts
. Declare it as a global with this statement:
let vao: VertexArray;
let vao: VertexArray;
To draw a model, you must activate or bind both the shader program and the VAO and then tell the GPU how to assemble the vertices from the vertex buffers into geometric primitives.
This code draws the vertices as individual points:
shaderProgram.bind();
vao.bind();
vao.drawSequence(gl.POINTS);
vao.unbind();
shaderProgram.unbind();
shaderProgram.bind(); vao.bind(); vao.drawSequence(gl.POINTS); vao.unbind(); shaderProgram.unbind();
Add it to the end of the render
function.
When you run your application, do you see two dots? The dots may be too small to see. On some browsers, you can increase their size by adding this line to main
in the vertex shader, near the assignment to gl_Position
:
gl_PointSize = 10.0;
gl_PointSize = 10.0;
Once you can see the points, try the following experiments:
-
Resize the window. Does the browser re-render your scene? It should thanks to the
resize
event listener. -
Replace
gl.POINTS
withgl.LINES
. Your vertex shader will be called just twice, but the fragment shader will be called to fill in the pixels between them. -
Add more vertices to
positions
andcolors
and updating the vertex count you pass to theaddAttribute
calls. -
Replace
gl.LINES
withgl.LINE_STRIP
andgl.LINE_LOOP
.
We're rendering stuff! The rest of the course is just complicating the stuff with more vertices, colors, and motion.