Let's go to our main.cpp file in Visual Studio or Xcode, and let's get started. Start typing the following code in your editor:
- Begin by adding some header files to our code:
#include <iostream>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
iostream is just the input/output stream built into C++. Then, with GLEW_STATIC, we statically linked GLEW. If you don't want to statically link it, just omit the #define line.
- Next, we'll create some constants, and these will be used to store the width and height of our window:
// Window dimensions
const GLint WIDTH = 800, HEIGHT = 600;
You might be thinking, why are we using GLint instead of a regular int? The reason for that is the issues with a regular int on different operating systems; for example, in a different compiler, it might have a different length. With GLint, it is consistent on any compiler, so this is a great way of ensuring maximum compatibility.
- Now, we'll set up our main entry point with int main and then we'll initialize GLFW:
// The MAIN function, from here we start the application and run the game loop
int main()
{
// Init GLFW
glfwInit();
- Next, we'll set up some window hints, which are essentially some properties that we'll set for our window:
// Set all the required options for GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
The reason we choose 3.3 is because after version 3.1, the code was deprecated in the old version of OpenGL. This was done to prohibit developers from using the older version of OpenGL. Since 3.3, the OpenGL version matches the shader version. So for 3.3, the OpenGL shader language version is also 3.3; it helps in keeping things consistent, neat, and tidy. But if you need a new feature, feel free to use something like 4.3.
- Next, we'll type in some more window hints:
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
In this project, we'll be using CORE_PROFILE. Actually, there are two main profiles that are available: the core profile and the compatibility profile, COMPAT_PROFILE. The reason we are using CORE_PROFILE in our project is that CORE_PROFILE uses the new OpenGL stuff, whereas the compatibility profile uses the old way of doing things, thus ensuring maximum compatibility. You probably might be thinking even if it ensures maximum compatibility, why is it recommended not to use COMPAT_PROFILE? The reason for that is in this book you're learning OpenGL in general, so we don't want to learn the old, outdated way of doing things. Rather, we want to learn the new, modern OpenGL using vertex objects and vertex arrays to actually store stuff on a graphics card. So if you use the compatibility mode, you're just getting into bad practices when using stuff like glBegin. So, that is the reason why we are setting it to the core profile.
- Once we've set the profile, we set the window hint to get forward compatibility:
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
This window hint is actually required in macOS because otherwise it will crash, but there's no harm in having it on Windows as well.
- In WindowHint, we'll set GLFW_RESIZABLE, and we'll set this to FALSE so that it prevents the window from being resized. If you want it to be resized, just set it as TRUE:
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
- Next, we're going to create our window. For that, we'll add the following code:
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow *window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
In the preceding code, we call the values of the variables WIDTH and HEIGHT. These terms define the window's size and "LearnOpenGL" sets the title of our window. The window and the monitor variables are defined as null pointers and we'll deal with those in later chapters.
- Next, we'll define variables for our screen's width and height because this will be the actual resolution that we want the window set to:
int screenWidth, screenHeight;
Then, in the following line of code with glfwGetFramebufferSize, we pass the references to the screen width and the screen height:
glfwGetFramebufferSize( window, &screenWidth, &screenHeight );
What this line of code actually does is it gets the actual width of the screen window itself, relative to the density of the screen. You could effectively omit these lines when you create an OpenGL viewport and just use screenWidth and screenHeight values only. But if you have something like a Mac or a Retina Mac, which is not natively 1920 x 1080, or, for example, a higher-density screen with a resolution like 3840 x 2160, the window would just get messed up. The content would be displayed in either the bottom-left of the screen or in the top-left. The previous line of code helps us in getting the actual width and height of our window, relative to any pixel density changes. So it's recommended to have it, as it will ensure maximum compatibility in the future, as more and more high resolution screens are coming out.
- Now, we would want to check the window was created successfully, and we'll do that as follows:
if (nullptr == window)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return EXIT_FAILURE;
}
In the preceding code, we check the nullptr == window condition and we let the user know that something has gone wrong. Then, we just terminate anything that has been initialized with glfwTerminate();, and finally exit:
glfwMakeContextCurrent(window);
- Next, we need to enable GLEW and we'll do that as follows:
// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
glewExperimental = GL_TRUE;
Looking at glewExperimental in the code, you might wonder are we using experimental features? And, why have we to set it to TRUE? The reason for that is GLEW knows to use a modern approach to retrieve functions, pointers, and extensions. Basically, it's just a way of saying we're using GLEW the new and the modern way, but it's not necessarily an experimental function.
- Then, we're going to initialize GLEW and make sure it's successfully initialized in one go:
// Initialize GLEW to setup the OpenGL Function pointers
if (GLEW_OK != glewInit())
{
std::cout << "Failed to initialize GLEW" << std::endl;
return EXIT_FAILURE;
}
You can also use return -1 instead of return EXIT_FAILURE; for Xcode.
- Next, we'll set up the OpenGL viewport:
// Define the viewport dimensions
glViewport(0, 0, screenWidth, screenHeight);
What we did in the preceding line of code is that we set the initial coordinates from 0, 0 to screenWidth and screenHeight. The values that you'll retrieve here will be an accurate representation of what our window is relative to the screen, as you might have a higher or a lower pixel density screen.
- So now that we have set up the view port, we'll create our game loop:
// Game loop
while (!glfwWindowShouldClose(window))
{
// Check if any events have been activiated (key pressed,
//mouse moved etc.) and call corresponding response functions
glfwPollEvents();
// Render
// Clear the colorbuffer
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Draw OpenGL
glfwSwapBuffers(window);
}
In the preceding code, we created a While loop and initialized it to check whether the window is open; if it is, then run the loop. In the loop, we are clearing colorbuffer with the help of the glClearColor function. ClearColor is actually an optional line of code, but the reason we are adding it is if we don't add this, we might just get a blank, black background because we haven't drawn anything yet. So instead of a black background, we tried to spruce it up with some color. We defined colors in a range between 0 and 1, which is quite similar to a range between 0 and 255, where 0 is of no value and 1 is the full intensity of red, green, blue, and alpha.
- Then, we added glClear to clear our window so that we're ready to draw the next frame and put in GL_COLOR_BUFFER_BIT;. Here is where you would draw your OpenGL stuff. As we are not going to draw anything in this chapter, we'll add glfwSwapBuffers and provide it to the window. Then, we'll add glfwTerminate to close the window after the while loop is executed:
// Terminate GLFW, clearing any resources allocated by GLFW.
glfwTerminate();
return EXIT_SUCCESS;
}
You can also use return -1 instead of return EXIT_FAILURE; for Xcode.
Now, let's run this code and check the output. You will get a similar OpenGL window on your screen:
OpenGL rendering window for Windows