-
Notifications
You must be signed in to change notification settings - Fork 35
2.6.4 Basics of modern OpenGL (Part I)
In this tutorial series I want to address modern OpenGL in general. Not ray tracing or compute shaders, like I did in the last tutorial series. But instead I try to talk about the basics of modern OpenGL.
It is not the intention of this tutorial to provide you with copy-and-paste code examples, but instead touch on the things that you come in contact with when using modern OpenGL.
The target audience are people slightly familiar with legacy OpenGL using commands such as glVertex, glTexCoord, glNormal, glBegin and such.
In order for you to learn modern OpenGL as easily as possible, I try to structure the articles in a way that I would have liked reading them when first learning about OpenGL.
To tell you the truth, I went through many years of learning traditional, fixed-function pipeline OpenGL first. You know, with glBegin, glVertex, glLight and whatnot. When I first started learning about modern OpenGL, it was hard to grasp. It required a good amount of practice.
But you can believe me, when I say, that although modern OpenGL might first seem like a complex thing to do, it actually is easier than learning traditional OpenGL first.
And I tell you why: Because of the versatility that shaders bring you which better fit the rendering problems you encounter and trying to solve.
With traditional OpenGL you had to think of all kinds of mind-twisting state changes when trying to achieve something:
Stencil here, scissor there, light settings, matrix and state pushing and popping, finding the right order in which to call glRotate, glTranslate.
From the standpoint of modern OpenGL, it really makes you shake your head in disbelief that anyone would want to do that anymore. :-)
Modern OpenGL frees your mind about the things that are cluttered and unnecessarily complicated to do and keeps you with the essentials, in the form of light abstractions of the graphics hardware. You see, OpenGL is not a render engine, whose job is to make it particularly easy to write games. It rather is a means for enabling hardware accelerated graphical and computing applications.
With more modern OpenGL you only have the following things together with explicit operations connecting and operating on them:
- buffers
- vertex array objects (VAO)
- shader programs
- textures, images and samplers
- framebuffer objects (FBO)
- query objects
- few global state
Additionally, there are commands for the following:
- Create-Read-Update-Delete (CRUD)- and bind-operations for the above mentioned objects
- issuing draw calls
- copying memory from one area to another
- changing few global states
And that's it! You can say that OpenGL has evolved into an almost general purpose language for doing hardware accelerated graphical applications (such as games). General purpose in a sense that you are handling with thin abstractions over bare-metal hardware concepts, most important of these being the buffers.
A buffer is simply and plainly a section of raw memory that the GPU can read from and write to for various operations. As simple as that. There really is nothing else to add to that.
Buffers are created with glGenBuffers (or starting with OpenGL 4.5 using glCreateBuffers). This gives you a handle to a new empty buffer object in the form of a 32-bit integer. The integer's value is not the actual GPU or CPU address that this buffer resides on, but just an opaque handle. Opaque in the sense that it is not obvious (i.e. transparent) to the programmer what the actual structure of that handle is behind the scenes. It has just an identity which is equal to its integer value, in order to identify it.
So, glGenBuffers gives you the name (i.e. integer handle) of an empty buffer.
You can then use commands to allocate data in that buffer and optionally fill the buffer with data from host-memory (in the form of Java ByteBuffer for LWJGL), using the commands glBufferData and glBufferSubData. In OpenGL 3.1 there are also ways to copy data between two buffers using glCopyBufferSubData directly on the GPU without the CPU or host-memory intervening.
To recapitulate, a buffer is a plain memory area in which you can put arbitrary data (bytes, floats, integers, matrices, complex structures, the bytes of a PNG-encoded image, you name it).
In the next section we are going to see how a buffer is referenced/used with buffer commands/functions.
Most of the OpenGL functions working with buffers do not use a buffer's 32-bit integer handle to identify the buffer but rather use an indirection called buffer binding targets.
So, you can bind a buffer to one (or many) of several possible targets using glBindBuffer. A very frequently used binding target is GL_ARRAY_BUFFER. Once you bind a buffer via glBindBuffer, OpenGL knows that any further command also using this binding target will use that buffer.
These binding targets are used for various operations on those buffers, such as telling OpenGL about the buffer containing vertex indexes when using indexed drawing with glDrawElements or updating a texture storage from a buffer using glTexSubImage2D.
But for operations letting the host program interface with a buffer, such as filling it with data or querying the data, these binding targets have no use anymore.
For these, certain extensions allow completely bindless operations on buffers, such as EXT_direct_state_access and ARB_direct_state_access.
The former is widely available on drivers. With these you do not have to bind a buffer to a buffer binding target, but can use the 32-bit integer handle directly as argument to an OpenGL function, such as glNamedBufferData.
Being just some versatile general purpose memory area, a buffer has many uses in modern OpenGL. For example, it can be used to source vertex data from it when performing a draw call (glDrawArrays, glDrawElements, etc.). It can also be used to update a texture (which we will get to later). It can just as well be used to read from and write to it from within a shader. There are many other not mentioned ways in which buffers are useful, just because they provide a way for OpenGL and the GPU to read from and write to any kind of data.
You see, buffers are the most versatile thing there is in modern OpenGL and understanding how OpenGL uses those buffers is crucial in understanding modern OpenGL.
While reading all of that above you probably thought "Wait, aren't those buffers rather called Vertex Buffer Objects instead of just Buffers?" They were. That is because the original OpenGL extension that introduced buffers in 2003, which was ARB_vertex_buffer_object, called them like this.
And the reason for that is simply that those buffers at that time were only used to store vertex data (position, normal, ...) which the fixed function pipeline would then interpret when rendering the vertices.
There was simply no reason you would use a buffer in any other way back then. But that has changed and as such we now just call them buffer objects or just buffers.
The naming process of that extension is nicely stated on the site of the extension in the following two paragraphs:
RESOLVED: By unanimous consent among the working group members,
the name was chosen to be "ARB_vertex_buffer_object". A large
number of other names were considered throughout the lifetime of
the proposal, especially "vertex_array_object" (originally),
"buffer_object" (later on), and "memory_object" (near the end),
but the name "vertex_buffer_object" was ultimately chosen.
In particular, this name emphasizes not only that we have created
a new type of object that encapsulates arbitrary data (buffer
objects), but also, in particular, that these objects are used in
this extension to source vertex data. The name also is
intentionally similar to "vertex buffers", although it should be
emphasized that there is no such thing as a "vertex buffer" in
the terminology of this extension. The term "buffer object" is
the correct noun.
Buffers in itself are really simple to use, because they are nothing but plain raw byte-array memory without any interpretation of its contents whatsoever. You can assume that a buffer resides on the GPU, that is, OpenGL and the GPU is able to read from and write to that buffer.
This was a rather short tutorial about buffer objects in OpenGL. The next parts will explain in more detail how buffers and the other mentioned things are used when rendering something. There we will also see for the first time how OpenGL actually interprets the data within a buffer to source vertex data from it.