The terminology is a little confusing, yes.
Let’s say that you have a vertex buffer, with maybe 1mb (or whatever, the size isn’t important for this explanation) worth of vertex data in it. This buffer contains vertices for all of the meshes used by your program. Taking it from the top.
BindVertexBuffer specifies the starting position in this buffer to draw from. Where this is useful is if you have two different vertex formats in the same buffer. So you might have a position-only format, and a more heavyweight position/normal/texcoord format (maybe you decided to burn some extra memory in exchange for saving bandwidth on shadow passes), and storing them in the same buffer means that the driver may be able to detect that only the offset needs to change and optimize accordingly.
The offset in VertexAttribFormat is used in the second case: an interleaved vertex format. Assuming a standard 3-float position, 3-float normal and 2-float texcoord, these offsets would be 0, 12 and 24. So you’ve now defined the offsets from the starting position where each vertex attribute is at.
Where the “first” param comes in is that you may be drawing a mesh with 2 textures. Everything else is the same but the textures are different so you need to make 2 draw calls. The first 200 vertices are for texture 0, the next 100 are for texture 1, so your draw calls are:
glDrawArrays (GL_WHATEVER, 0, 200); // draw first 200 vertices
glDrawArrays (GL_WHATEVER, 200, 100); // draw next 100 vertices; the first 200 have already been drawn so don't draw them again
This is also useful for vertex streaming, where you only want to draw a subrange of the buffer each time but don’t want to respecify the entire vertex layout for each draw call. Here the pseudocode for a typical vertex batch might look something like:
SetupVertexFormat ();
first = 0;
while (DrawingBatches ())
{
if (BatchWillOverflowBuffer (count))
{
OrphanBuffer ();
first = 0;
}
AddVerticesToBuffer (count);
glDrawArrays (GL_WHATEVER, first, count);
first += count;
}