Back

Quad without vertex buffer

While I was looking through some code on GitHub, I found a repo , that draws quads without having to populate a vertex buffer with data.

The repo uses D3D11 and HLSL, so there are some small differences with the WebGL version of this post, but the core concept is the same.

Arbitrary Sized

The way to achieve a no-buffer quad is a combination of simple math, gl_VertexID and, gl.TRIANGLE_STRIP.

In Javascript land the only thing that changes from a hello-triangle WebGL program is calling gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4) in the draw function. Right now, interpret this as sending 4 nonexistent vertices to the vertex shader.

Note that we don't even need to bind a VAO in WebGL. In OpenGL you do need to bind it, but you can just create an empty one, and it will work.

gl_VertexID is the zero-based integer index of the current vertex that is being processed in the vertex shader. By interpreting the index as a 2-bit binary number we get:

  • Index 0 = 0b00 = bottom-left
  • Index 1 = 0b01 = bottom-right
  • Index 2 = 0b10 = top-left
  • Index 3 = 0b11 = top-right

We can easily get the UV coordinates from this data by using a binary AND with 1 to compute the u and a RIGHT SHIFT by 1 to compute the v.

2---3    0 = 00  | u = (00 & 01) = 0   |  v = (00 >> 1) = 0
|\  |    1 = 01  | u = (01 & 01) = 1   |  v = (01 >> 1) = 0
| \ |    2 = 10  | u = (10 & 01) = 0   |  v = (10 >> 1) = 1
0---1    3 = 11  | u = (11 & 01) = 1   |  v = (11 >> 1) = 1

Remapping the uv to a [-1, 1] range computes the 4 vertices of a fullscreen quad.

vec2 uv = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1));
vec2 pos = uv * 2.0 - 1.0;

gl.TRIANGLE_STRIP is the key to making this work. This triangle primitive will use vertices [0, 1, 2] and vertices [1, 2, 3] to draw the triangles.

Vertices [1, 2, 3] do not respect the correct winding order, luckily gl.TRIANGLE_STRIP draw order will be reversed for every triangle beside the first, effectively drawing in this case [2, 1, 3].

Other modes do not follow the same rules and vertex order, so it won't work. Check out the triangle primitives section on the wiki for more details.

Note that this is a full-fledged quad, we can apply any transform we want to the newly created vertices. It also works with any number of quads, just make sure to use the right amount of vertices in gl.drawArrays().

Source code here .

Full screen only

If we are only ever going to need a fullscreen quad, there is an even simpler version that uses only 3 vertices to create a big triangle and computes the uv coordinates so that they form a fullscreen quad.

vec2 uv = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1)) * 2.0;
vec2 pos = uv * 2.0 - 1.0;

Since we only need 3 vertices for a triangle, it's not necessary to use gl.TRIANGLE_STRIP, the classic gl.drawArrays(gl.TRIANGLES, 0, 3) works.

Source code here .