1. Introduction to OpenGL
OpenGL (Open Graphics Library) is a cross-platform API for rendering 2D and 3D vector graphics. The API is used to interact with a GPU, allowing developers to create graphics programs, primarily for real-time rendering in games, simulations, and other visualizations.
2. Context Creation
OpenGL requires a context to function, which is typically created by a windowing library such as GLFW, SDL, or native platform APIs. OpenGL context holds the state that affects rendering and is necessary for OpenGL commands to work.
3. OpenGL Shading Language (GLSL)
3.1 Shaders
OpenGL uses shaders written in GLSL for programmable rendering. The main types of shaders are:
- Vertex Shader: Processes vertex data and applies transformations.
- Fragment Shader: Computes the color and other attributes for each fragment (pixel).
3.2 Shader Compilation
Shaders are written in GLSL and must be compiled and linked into a program object:
- glCreateShader(GLenum shaderType): Creates a new shader object.
- glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length): Loads the shader source code into the shader object.
- glCompileShader(GLuint shader): Compiles the shader source.
- glCreateProgram(): Creates a new program object.
- glAttachShader(GLuint program, GLuint shader): Attaches a compiled shader to the program.
- glLinkProgram(GLuint program): Links all attached shaders into an executable.
- glUseProgram(GLuint program): Activates the shader program.
Use case: When creating shaders for your graphics pipeline, you will first create shader objects, load the source, compile them, link them into a program, and finally use that program for rendering.
4. Buffers
4.1 Vertex Buffer Object (VBO)
A VBO stores vertex data in GPU memory:
- glGenBuffers(GLsizei n, GLuint *buffers): Generates buffer object names.
- glBindBuffer(GLenum target, GLuint buffer): Binds a buffer to a target (e.g., GL_ARRAY_BUFFER).
- glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage): Creates and initializes a buffer object's data store.
Use case: When setting up vertex data for rendering, a VBO is created to store the data on the GPU. It can be used for efficient real-time rendering, particularly in 3D graphics.
4.2 Element Buffer Object (EBO)
An EBO stores indices for drawing with vertex array objects:
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, GLuint buffer): Binds the EBO to the array buffer.
Use case: When using indexed drawing (i.e., sharing vertices between primitives), an EBO can store indices that reference the vertex data in a VBO.
4.3 Vertex Array Object (VAO)
VAOs store state related to vertex attribute configuration:
- glGenVertexArrays(GLsizei n, GLuint *arrays): Generates VAO object names.
- glBindVertexArray(GLuint array): Binds the VAO for use.
- glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer): Specifies the layout of the vertex data.
- glEnableVertexAttribArray(GLuint index): Enables a generic vertex attribute array.
Use case: VAOs are crucial for organizing vertex attribute state, especially when working with multiple objects and buffers. They make switching between different sets of vertex attributes easy.
5. Texturing
5.1 Texture Creation
Textures are used to map images onto geometric surfaces:
- glGenTextures(GLsizei n, GLuint *textures): Generates texture object names.
- glBindTexture(GLenum target, GLuint texture): Binds a texture to a target (e.g., GL_TEXTURE_2D).
- glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels): Specifies a 2D texture image.
- glTexParameteri(GLenum target, GLenum pname, GLint param): Sets texture parameters (e.g., wrapping, filtering).
what kind of targets are there?
1. GL_TEXTURE_2D
Description: Represents a 2D texture with width and height (e.g., an image or texture used in most 3D applications).
Usage Example: Standard texture for mapping on a 3D model surface.
glBindTexture(GL_TEXTURE_2D, textureID);
2. GL_TEXTURE_1D
Description: Represents a 1D texture, which has only a width (e.g., a gradient or color ramp).
Usage Example: Rarely used in modern applications.
glBindTexture(GL_TEXTURE_1D, textureID);
3. GL_TEXTURE_3D
Description: Represents a 3D texture, which has width, height, and depth. Useful for volume textures.
Usage Example: Used in volumetric rendering, like for simulating fog or 3D data fields.
glBindTexture(GL_TEXTURE_3D, textureID);
4. GL_TEXTURE_CUBE_MAP
Description: Represents a cube map texture, which consists of six square 2D textures arranged into a cube. Useful for environment mapping, reflections, and skyboxes.
Usage Example: Used for reflective surfaces like water or mirrors.
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
5. GL_TEXTURE_RECTANGLE
Description: Represents a non-power-of-two texture (i.e., dimensions don't have to be powers of 2). Texture coordinates are not normalized, meaning they map directly to pixel coordinates.
Usage Example: Used for special cases where non-power-of-two textures are needed.
glBindTexture(GL_TEXTURE_RECTANGLE, textureID);
6. GL_TEXTURE_2D_ARRAY
Description: Represents an array of 2D textures stacked together. Each element in the array is a 2D texture.
Usage Example: Used for texture atlases or handling multiple textures in a single uniform.
glBindTexture(GL_TEXTURE_2D_ARRAY, textureID);
7. GL_TEXTURE_BUFFER
Description: A buffer texture, which allows you to use a buffer object's data (like a vertex buffer) as a texture. This is useful for reading large amounts of data directly into a shader.
Usage Example: For large data sets like particle systems.
glBindTexture(GL_TEXTURE_BUFFER, textureID);
Use case: When applying textures to objects, you'll first create a texture, bind it to the correct target, load the texture image, and set parameters for how it should be applied.
GLuint texture_map;
glGenTextures(1, &texture_map);
glBindTexture(GL_TEXTURE_2D, texture_map);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
5.2 Texture Units
One way to think about texture units are as picture frames on a wall somewhere, the picture frames are futuristic, and have options, for example if you slide a picture into a picture frame and it doesn't take up the full frame, then you can choose options for that picture frame so that it either repeats the image or tries to extend it in a certain direction, the picture frames support different types of images (texture targets), with their own custom options.
Now drawing the textures is like an art exhibit, at first you have a bunch of empty frames, so we go up to a certain frame glActiveTexture(GL_TEXTURE0)
, slide our art into it
glBindTexture(GL_TEXTURE_2D, texture_map);
, in this context shaders are like the art buyers, they go around and sample (bind the texture unit number to the sampler)
the artwork then they select what art they like and buy it (draw it), now that they own the art they can do anything they want with it
more on this whole process
Texture Target
Texture targets refer to the different types of textures (2D, 3D, etc.). You can bind multiple textures, one of each type, to the same texture unit. For example:
glBindTexture(GL_TEXTURE_2D, texId1); glBindTexture(GL_TEXTURE_3D, texId2);
Both texId1
and texId2
are bound to the same texture unit, as they are bound to different targets. It is recommended to bind different textures to different texture units to avoid confusion.
Texture Object
Texture objects are created with glGenTextures()
and bound with glBindTexture()
. A texture object contains:
- Texture data.
- State for sampling the texture, such as filtering attributes set with
glTexParameteri()
. - Information about the texture format/type specified with the data.
Texture Unit
Texture units allow multiple textures to be bound at once, supporting multi-texturing. You use glActiveTexture()
to specify the active texture unit. For example:
glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, texId);
This binds texId
to texture unit 3.
Sampler Object
Sampler objects, introduced in OpenGL 3.3, decouple texture data from sampling parameters. This allows one texture to be sampled with different parameters (e.g., LINEAR and NEAREST) in the same shader without needing multiple copies of the texture data.
Texture View
Texture views, available in OpenGL 4.3, decouple raw texture data from its format. This allows the same texture data to be used with different formats.
Connecting Textures to Shaders
To connect textures to shaders, texture units bridge shaders and texture objects. A shader samples from texture units using sampler uniform variables.
Example
If "MyFirstTexture" is a sampler variable in the shader, this code associates it with texture unit 3:
GLint loc = glGetUniformLocation(prog, "MyFirstTexture"); glUniform1i(loc, 3);
Then, bind the texture object to the texture unit:
glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, texId);
The shader will sample from the texture bound to unit 3.
Multi-Texturing Example
For two textures, using texture units 0 and 1:
glUseProgram(prog); GLint loc = glGetUniformLocation(prog, "MyFirstTexture"); glUniform1i(loc, 0); loc = glGetUniformLocation(prog, "MySecondTexture"); glUniform1i(loc, 1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texId0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texId1);
The shader will read data from the textures plugged into the corresponding texture units.
OpenGL allows multiple textures to be bound at once, each to a different texture unit:
- glActiveTexture(GLenum texture): Specifies the active texture unit, this occurs within the context of an active texture unit, when not specified this is usually
GL_TEXTURE0
Use case: When using multiple textures in a shader, texture units are used to assign textures to different slots, which can be sampled in the shader.
You can get by without understanding texture units, but it will catch up to you, for example the example above when we talked about regular textures showed code that seemed to never use texture units but still works,
this is because by default the shader uniform varaible of the sampler is the value 0, moreover when you bind a texture usually GL_TEXTURE0
is already bound, and thus it just so happens that by chance that
the uniform is the correct matching value, and the texture shows up, but if you then bind a different texture it will overwrite it and you might start getting confused, so better to understand texture units sooner rather than later.
6. Drawing
6.1 Primitive Drawing
OpenGL supports various drawing primitives such as points, lines, and triangles:
- glDrawArrays(GLenum mode, GLint first, GLsizei count): Renders primitives from array data.
- glDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices): Renders primitives using indices.
Use case: When drawing shapes in OpenGL, these functions are used to render arrays or elements (indexed drawing) as primitives like triangles, lines, or points.
7. Framebuffers
7.1 Default Framebuffer
By default, OpenGL renders to a window provided by the OS.
7.2 Framebuffer Object (FBO)
FBOs allow off-screen rendering for post-processing, shadow maps, etc.:
- glGenFramebuffers(GLsizei n, GLuint *framebuffers): Generates FBO object names.
- glBindFramebuffer(GLenum target, GLuint framebuffer): Binds the FBO.
- glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level): Attaches a texture to the FBO.
- glCheckFramebufferStatus(GLenum target): Checks if the FBO is complete.
Use case: FBOs are used when you want to render off-screen, such as for effects like shadow mapping or post-processing (e.g., bloom, HDR).
8. State Management
8.1 Enabling and Disabling Capabilities
OpenGL has many state variables that control rendering behavior:
- glEnable(GLenum cap): Enables a feature (e.g., GL_DEPTH_TEST, GL_BLEND).
- glDisable(GLenum cap): Disables a feature.
- glDepthFunc(GLenum func): Specifies the depth comparison function.
- glBlendFunc(GLenum sfactor, GLenum dfactor): Specifies how source and destination colors are combined.
Use case: Proper state management is critical in OpenGL. Depth testing, blending, and other features need to be explicitly enabled or disabled based on the rendering requirements.
9. Conclusion
OpenGL is a powerful API for creating 2D and 3D graphics applications. By understanding its core concepts such as context creation, shaders, buffers, texturing, and drawing, developers can effectively utilize OpenGL to create high-performance, real-time visualizations and simulations.