Initialize a Window and OpenGL
Creating a Window
The first essential step of a graphics program is to be able to display a window in which the visual results can be displayed.
This step is, to a large extent, independent of the approach put in place to achieve the graphic rendering. However, the window must be able to interact
and synchronize with the graphics API used (in our case, it will be a OpenGL context).
Creating a window from scratch is a complex operation heavily depending on the operating system. In most case, we use external cross-platform libraries
to handle this step.
In our case, we will use the library GLFW. GLFW is a lightweight C library able to create window compatible with an
OpenGL context. GLFW can also handle user events such as the use of the keyboard and mouse.
- - With Linux/MacOS, GLFW can be installed from your standard package manager of your distribution.
- - With Windows, the pre-compiled library files are provided with the codes. They are compatible with Visual Studio 2019. If you want to use another version of Visual Studio, you may need to replace them by the adapted pre-compiled files that can be found on GLFW website.
> Download, compile and execute the following code [ create_window.zip ]
- Rem. Once executed, a window should appear. Note that its content may be arbitrary (black or colored window) and may depends on your system.
Comments on code
// Include for GLFW headers - library handling the window #include <GLFW/glfw3.h> #include <iostream> int main() { // Initialize GLFW // This step is required before creating the window glfwInit(); // Create a new Window of size 500x500 with title "My Window" // The two last parameters are // - the monitor for full screen mode (or nullptr for windowed mode in this case) // - another pointer to a window in the case where ressources are shared (or nullptr to not share ressources). // The function returns a pointer to the newly created window (or nullptr in case of error). GLFWwindow* window = glfwCreateWindow(500, 500, "My Window", nullptr, nullptr); if( window==nullptr ) { std::cerr<<"GLFW Failed to create a Window"<<std::endl; glfwTerminate(); exit(1); } // User defined loop // This loop will be active until the user close the window while( !glfwWindowShouldClose(window) ) { glfwSwapBuffers(window); // Double buffering (will be used to avoid flickering when animating a scene) glfwPollEvents(); // Handle GLFW events (ex. mouse clicks, etc) } glfwTerminate(); // Close the GLFW Window return 0; }
- glfwInit(); GLFW function call initializing the library.
- glfwCreateWindow(...) allows to initialize a graphical window. The function returns a pointer of type GLFWwindow*
-
- - GLFWwindow is the type of a class defined in the GLFW library. Its content can be complex, but you do not need to know it to use the graphics window.
- - GLFWwindow* (note the star) is a pointer to a variable of type GLFWwindow. This is not directly the content of the
variable, but its address in memory. This notion will be detailed later.
- The lines that are following consist of a loop which regularly updates the graphics window as long as no event requires the window to be closed (for the moment nothing is displayed in the window).
Windows and OpenGL
Introduction to OpenGL
In the following, we will draw inside this window through the use of OpenGL.
OpenGL is a set of standardized functions (called an API) which allows to implement in a very efficient and generic way the "graphics rendering pipeline". More precisely, OpenGL consists of a set of functionalities allowing to communicate directly with the graphics card (GPU - Graphics Processing Unit), thus offering an optimal approach to display 3D data in real time.
Rem. OpenGL is not a "code library", it is only a standard for calling functions and types. The actual implementation of these functions depends on your system, your graphics card, and the driver you have installed. OpenGL calls are said to be "low level": They allow direct communication with your graphics card very efficient, but require a significant work of setting up.
Programm using OpenGL
> Download, compile and execute the following code: [ create_window_opengl_context.zip ].- This code still does not display anything on the window, but a few lines allowing OpenGL initialization have been added.
#include "../external/glad/include/glad/glad.hpp" // glad.h should be included before glfw or any OpenGL related include #include <GLFW/glfw3.h> #include <iostream> #include <cmath> int main() { glfwInit(); // Indicate to GLFW to setup context compatible with OpenGL 3.3 core profile glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 1); glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1); glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE); GLFWwindow* window = glfwCreateWindow(500, 500, "My Window", nullptr, nullptr); if( window==nullptr ) { std::cerr<<"GLFW Failed to create a Window"<<std::endl; glfwTerminate(); exit(1); } // Enable the window to handle OpenGL Context glfwMakeContextCurrent(window); // Load OpenGL Functions gladLoadGL(); // Print OpenGL Information std::cout << "OpenGL information: VENDOR : " << glGetString(GL_VENDOR) <<std::endl; std::cout << " RENDERDER : " << glGetString(GL_RENDERER) <<std::endl; std::cout << " VERSION : " << glGetString(GL_VERSION) <<std::endl; std::cout << " GLSL VERSION: " << glGetString(GL_SHADING_LANGUAGE_VERSION)<<std::endl; while( !glfwWindowShouldClose(window) ) { glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; }
- First, the external/glad directory contains the link library GLAD which allows you to load the OpenGL functions corresponding to the version used.
- Second, a number of initialization functions are called in the main function
-
- glfwWindowHint: used to indicate to GLFW in which OpenGL context the window should be used (here OpenGL 3.3)
- gladLoadGL: allows the use of OpenGL calls afterwards.
- The program then displays on the terminal some information on the driver used and the current OpenGL version.
In a classic OpenGL scene, containing an animation or a user interaction, the display of the image is refreshed several times per second. This regular display request is made by the loop contained in the instruction.
while( !glfwWindowShouldClose(window) ){ ... }
This loop is thus executed as long as the user does not close the window. At each iteration of this loop, it is common practice to completely erase the screen to redisplay the scene in its entirety. To obtain a smooth animation impression, each new frame must be recalculated in, at most, 40ms (or 25 frames per second - fps).
Modify the loop code as follows
while( !glfwWindowShouldClose(window) ) { // Set the (R,G,B,A) color to clear the screen glClearColor(1.0f, 1.0f, 0.5f, 1.0f); // Clear the screen (designated by the color buffer) glClear(GL_COLOR_BUFFER_BIT); glfwSwapBuffers(window); glfwPollEvents(); }
Check that the window is now displayed in yellow.
Explanation
- - First, a color (clearColor) is chosen from these RGBA (Red-Green-Blue-Alpha) components using the function glClearColor. Each component is between 0 (darker) and 1 (lightest). RGB values have an impact on the resulting color. Component A is not used here and has no impact (component A can be used, in particular, to manage transparency in certain configurations).
-
- Note that each number is displayed with a comma followed by an f. This explicitly indicates the use of a single precision floating point number (float).
- - Secondly, the color buffer, i.e. the image displayed in the window, is erased by the function glClear with the argument GL_COLOR_BUFFER_BIT. More precisely, glClear allow here to fill the entire image with the color previously defined by glClearColor.
Rem. The glClearColor and glClear functions are OpenGL functions. We will find the same naming convention for all OpenGL functions and variables.:
- - OpenGL functions are named gl FunctionName
- - The constant values predefined by OpenGL are words in upper case, separated by underscore (eg GL_COLOR_BUFFER_BIT). These "constants" are stored internally as integers, and their names are replaced at compile time by their numeric value.
Temporal modification
Now consider the following code for the animation loop:
int counter = 0; while( !glfwWindowShouldClose(window) ) { counter = (counter+1)%100; float u = counter/99.0f; // Set the (R,G,B,A) color to clear the screen glClearColor(0.5+std::cos(2*3.14f*u)/2.0f, 1.0f, 0.5f, 1.0f); // Clear the screen (designated by the color buffer) glClear(GL_COLOR_BUFFER_BIT); glfwSwapBuffers(window); glfwPollEvents(); }
- > Try to understand the code
-
- What values does the counter variable take?
- What values does the variable u take?
- What values does the first parameter (Red) of the glClearColor function take?
Note that you can experiment with other functions to get other color change effects.
Rem: Note the explicit syntax "counter/99.0f" and not "counter/99"
- - This syntax (with the comma and the f) makes it possible to clarify the fact that the calculation must be carried out on floating numbers (one of the parameter is a floating number)
- - Otherwise: "counter/99" would perform a calculation on integers numbers in C++. The corresponding result would therefore only be 0 or 1.