-
Notifications
You must be signed in to change notification settings - Fork 36
2.6.5 Basics of modern OpenGL (Part II)
And off we go with the second part of the series on basic modern OpenGL.
In the last part we shortly introduced to the basic objects that you would use when using modern OpenGL. We also began telling something about buffers, also known as buffer objects. We figured that these are nothing more than plain old memory on the GPU, and that you can fill them with whatever data you like.
Now in this part of the tutorial series on basic modern OpenGL we are going to use a buffer object to render something.
You know that in modern OpenGL you cannot specify the properties of the vertices of your model with glVertex, glNormal and glTexCoord (and such) anymore.
Instead, you would be using a buffer containing the attributes of each vertex neatly packed in sequential GPU memory.
So, if you want to draw a triangle, the simplest thing you can do is pack the positions of the three vertices tightly into a buffer, so that the layout of the buffer would look like so:
XYZXYZXYZ
With X, Y and Z being the respective dimension of the coordinate of each vertex as IEEE 754 floating-point value.
That's just your good old Java float value. The Java virtual machine, your processor (most likely with x86 architecture) and the GPU all adhere to the basic bit layout of a float value.
So, when you put -1.0f in the buffer then the GPU is going to read -1.0 out of it, just like you intended.
So to recapitulate:
First, you would create a new buffer object in OpenGL.
Then you would allocate a Java ByteBuffer using BufferUtils.createByteBuffer.
Afterwards you would use the ByteBuffer.putFloat method (or FloatBuffer.put on a float view of that buffer) to put your vertex positions inside that ByteBuffer.
You then ByteBuffer.flip() the ByteBuffer (not needed when using a FloatBuffer view).
And the final step would be to upload the data in that ByteBuffer to the OpenGL buffer object via glBufferData.
That's it. You now have an OpenGL buffer object filled with the positions of your three vertices.
Like we said in the last part of the tutorial series, the data in an OpenGL buffer is completely arbitrary and is not interpreted per se. So, after you created your buffer object and filled your vertex positions into it, OpenGL has no clue about the data in your buffer actually being vertex positions. It also does not even know that these values are floats.
Now comes the part where we must tell OpenGL about this. Specifially, we need to tell OpenGL the following two facts:
- the data inside the buffer are floats
- the floats should be interpreted as tuples of three elements each
We do this with generic vertex attributes in modern OpenGL. You probably know that we used to use glVertexPointer to tell OpenGL about the position of the vertices (and likewise glNormalPointer for normals).
But modern OpenGL provides a better way with generic vertex attributes.
This is because we are not using the fixed-function pipeline in OpenGL anymore but instead do everything with shaders. And within a shader (vertex shader more specifically), you can read attributes of the current vertex.
So, to actually use the vertex attributes (the position in our case) inside of a vertex shader, we need to connect the data in our previously created buffer object to the vertex shader.
And that is done via glVertexAttribPointer. This function takes the buffer object currently bound to GL_ARRAY_BUFFER (see previous article) and the layout of the data in that buffer and binds that information to a generic vertex attribute index.
To reiterate, glVertexAttribPointer connects your currently bound buffer object to a generic vertex attribute index, together with information on how that data is layed-out in the buffer.
By default, generic vertex attributes are disabled and can be enabled using glEnableVertexAttribArray. This must be done before actually trying to issue a draw call that should source vertex data.
So, we created our buffer, filled it with vertex positions and bound that to a generic vertex attribute. Modern OpenGL (>= 3.2 core profile) now requires us to capture all of this state information inside of a new object called Vertex Array Object, or VAO for short.
A VAO is nothing more than a snapshot of the state of generic vertex attributes. It encapsulates all the state changes done by calls to glVertexAttribPointer and glEnableVertexAttribArray and a few others. As such, the VAO holds onto the bound buffer object and all additional properties (type, size and stride from the glVertexAttribPointer among others) of each generic vertex attribute.
So, once you called these functions after a VAO has been bound, you no longer have to call them again. Just simply bind the VAO again before rendering something and it will take care of all the state changes that need to be applied.
You create a VAO with glGenVertexArrays and bind it with glBindVertexArray. After you bound it, all state changes you do will be tracked by that VAO and will not affect global context state anymore (which is also forbidden in 3.2 core profile OpenGL).
This part was about how we let OpenGL know that a buffer contains vertex attributes, such as the position. We used generic vertex attributes and captured these state information inside of a vertex array object.
The next part will be about how we setup a simple shader to actually let OpenGL source vertex data from our buffer and render something with it.