OpenGL Window
Learn how get started with OpenGL and SFML
Code Repository
- The code for this tutorial can be found on Github : Learn SFML – OpenGL Window
SFML and OpenGL
OpenGL stands for Open Graphics Library, it’s a collection of functions and algorithms that a computer uses to calculate and render 3D (or 2D) Images like a 3D Cube. Most commonly it is called a Graphics API. Other popular graphics API are Direct3D from Windows, Metal from MacOS, and Vulkan the successor or OpenGL.
This is not an OpenGL tutorial, you can find really awesome OpenGL tutorials here (Learn OpenGL) and here (OpenGL).
In this tutorial, I will show you how to get started your Journey with OpenGL and SFML. In the previous tutorial Graphics Window, you’ve learned about the SFML Graphics module and the RenderWindow. What if you don’t want to use the Graphics module ? What if you want to use SFML to build a 3D Scene / 3D Game ? In those cases, you can use the class sf::Window to create a basic OpenGL Window.
The code below shows how to draw a triangle using SFML and OpenGL. The details on how the triangle is created and drawn using OpenGL will not be covered. Expand the tab (Utility Functions – OpenGL Code) below to see the implementation of the following functions :
- struct TriangleState : a structure that holds the OpenGL state of our triangle (VBO, VAO, and EBO)
- sf::Vector2f pixelToNDC(vertex, resolution): SFML and OpenGL use different coordinate system. This function converts the SFML pixels position into NDC (Normalized Device Coordinate)
- int createShaderProgram() : this function creates and return an OpenGL shader program
- void createTriangle(triangleState, resolution, vertex1, vertex2, vertex3) : this function creates a triangle from three points (vertex).
- void drawTriangle() : this function draws our triangle
- void destroyTriangle() : this function removes the triangle data from memory
//Convert SFML pixel coordinates into OpenGL Normalized Device coordinates (NDC) sf::Vector2f pixelToNDC(const sf::Vector2f& vertex, sf::Vector2u resolution) { return sf::Vector2f(-1.f + vertex.x/resolution.x * 2.f, 1.f - vertex.y/resolution.y * 2.f); } int createShaderProgram() { const char *vertexShaderSource = "#version 330 core\n" "layout (location = 0) in vec3 aPos;\n" "void main()\n" "{\n" " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" "}\0"; const char *fragmentShaderSource = "#version 330 core\n" "out vec4 FragColor;\n" "void main()\n" "{\n" " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" "}\n\0"; // load resources, initialize the OpenGL states, ... // build and compile our shader program // ------------------------------------ // vertex shader int vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); // check for shader compile errors int success; char infoLog[512]; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; } // fragment shader int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); // check for shader compile errors glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; } // link shaders int shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); // check for linking errors glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); return shaderProgram; } void createTriangle(TriangleState& triangle, sf::Vector2u resolution, const sf::Vector2f& vertex1, const sf::Vector2f& vertex2, const sf::Vector2f& vertex3) { // set up vertex data (and buffer(s)) and configure vertex attributes // ------------------------------------------------------------------ float vertices[] = { pixelToNDC(vertex1, resolution).x, pixelToNDC(vertex1, resolution).y, 0.0f, // left pixelToNDC(vertex2, resolution).x, pixelToNDC(vertex2, resolution).y, 0.0f, // right pixelToNDC(vertex3, resolution).x, pixelToNDC(vertex3, resolution).y, 0.0f // top }; glGenVertexArrays(1, &triangle.VAO); glGenBuffers(1, &triangle.VBO); // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). glBindVertexArray(triangle.VAO); glBindBuffer(GL_ARRAY_BUFFER, triangle.VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind glBindBuffer(GL_ARRAY_BUFFER, 0); // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. glBindVertexArray(0); } void drawTriangle(TriangleState& triangle, int shaderProgram) { glUseProgram(shaderProgram); glBindVertexArray(triangle.VAO); glDrawArrays(GL_TRIANGLES, 0, 3); } void destroyTriangle(TriangleState& triangle) { glDeleteVertexArrays(1, &triangle.VAO); glDeleteBuffers(1, &triangle.VBO); glDeleteBuffers(1, &triangle.EBO); }
//////////////////////////////////////////////////////////// // Nero Game Engine - SFML Tutorials //////////////////////////////////////////////////////////// //Glew #include <GL/glew.h> //SFML #include <SFML/Window.hpp> #include <SFML/OpenGL.hpp> // #include <iostream> // Forward Declaration : Utility functions struct TriangleState{unsigned int VBO, VAO, EBO;}; sf::Vector2f pixelToNDC(const sf::Vector2f& vertex, sf::Vector2u resolution); int createShaderProgram(); void createTriangle(TriangleState& triangle, sf::Vector2u resolution, const sf::Vector2f& vertex1, const sf::Vector2f& vertex2, const sf::Vector2f& vertex3); void drawTriangle(TriangleState& triangle, int shaderProgram); void destroyTriangle(TriangleState& triangle); int main() { //Create OpenGL Window //window parameters (OpenGL 3.3) sf::Vector2u window_resolution(1080, 720); std::string window_title = "OpenGL Window"; sf::ContextSettings contextSettings; contextSettings.depthBits = 24; contextSettings.stencilBits = 8; contextSettings.antialiasingLevel = 4; contextSettings.majorVersion = 3; contextSettings.minorVersion = 3; //create the window sf::Window openGLWindow(sf::VideoMode(window_resolution.x, window_resolution.y), window_title, sf::Style::Default, contextSettings); //setup the window openGLWindow.setVerticalSyncEnabled(true); openGLWindow.setActive(true); //Initialize GLEW (OpenGL Extension Wrangler Library) and check if OpenGL is available bool openGlAvailable = true; glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { openGlAvailable = false; } //If OpenGL is not available let's stop everything and exit the program. if(!openGlAvailable) return 0; //Initialize your game here, let's create a simple triangle //create shader program int shaderProgram = createShaderProgram(); //build a triangle TriangleState triangle; //a structure to hold the OpenGL state of the triangle createTriangle(triangle, window_resolution, sf::Vector2f(540.f, 100.f), sf::Vector2f(340.f, 360.f), sf::Vector2f(740.f, 360.f)); //Game loop bool gameRunning = true; while(gameRunning) { // handle events sf::Event event; while(openGLWindow.pollEvent(event)) { if(event.type == sf::Event::Closed) { // end the program gameRunning = false; } else if (event.type == sf::Event::Resized) { // adjust the viewport when the window is resized glViewport(0, 0, event.size.width, event.size.height); } } //update your game here //we have noting to update, so it's empty // clear the buffers glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawTriangle(triangle, shaderProgram); // end the current frame (internally swaps the front and back buffers) openGLWindow.display(); } //Destroy triangle state destroyTriangle(triangle); //Destroy shader Program glDeleteProgram(shaderProgram); return 0; }
Step 1 : Includes and Main
Using the Engine SDK, create an empty project or a console project, then replace the code inside main.cpp with the code below. In order to use OpenGL with SFML you will have to include the following headers
- GL/glew.h : GLEW stands for OpenGL Extension Wrangler Library, it is a library that loads OpenGL Extensions for you. The OpenGL API is made of a Core API plus many Extensions that are OS or GPU specific. Those Extensions are located somewhere inside your computer. GLEW can find them and make them usable in your C++ code
- SFML/Window.hpp : This header contains the class sf::Window which is the most basic window that SFML offers. In the tutorial Graphics window, you’ve learned about the class sf::RenderWindow. sf::RenderWindow is an extension of sf::Window created to take advantage of the Graphics module. Since we don’t want to use the Graphics module, we’ll use sf::Window.
- SFML/OpenGL.hpp : This header includes the OpenGL Core API (functions).
//Glew #include <GL/glew.h> //SFML #include <SFML/Window.hpp> #include <SFML/OpenGL.hpp> //CPP #include <iostream> //Create an empty main function int main() { //Our code will be here return 0; }
Step 2 : Create a Window
The code below shows how to create a window configured to use OpenGL 3.3. In OpenGL terms, that’s equivalent to creating an OpenGL Context. The OpenGL version is specified through the attributes majorVersion and minorVersion of the parameter contextSettings. If you would for example use OpenGL 4.5, the majorVersion will be 4 and the minorVersion will be 5. The window is created using 4 parameters
- The window width
- The window height
- The window title
- The Context Settings that define some OpenGL data
- The window width
//Create OpenGL Window //window parameters (OpenGL 3.3) sf::Vector2u window_resolution(1080, 720); std::string window_title = "OpenGL Window"; sf::ContextSettings contextSettings; contextSettings.depthBits = 24; contextSettings.stencilBits = 8; contextSettings.antialiasingLevel = 4; contextSettings.majorVersion = 3; //OpenGL major version contextSettings.minorVersion = 3; //OpenGL minor version //create the window sf::Window openGLWindow(sf::VideoMode(window_resolution.x, window_resolution.y), window_title, sf::Style::Default, contextSettings); //setup the window openGLWindow.setVerticalSyncEnabled(true); openGLWindow.setActive(true);
Step 3 : Initialize GLEW and Check OpenGL Availability
Now that we have our OpenGL window, let’s make sure OpenGL is working properly before we continue. The code below shows how to load the OpenGL extensions with GLEW. When loading the extensions fails, we consider that OpenGL is not available and we exit the program. Not all computers come with OpenGL or the latest version of OpenGL, you can use OpenGL Extensions Viewer on Windows to check the OpenGL installed on your computer.
//Initialize GLEW (OpenGL Extension Wrangler Library) and check if OpenGL is available bool openGLAvailable = true; glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { openGLAvailable = false; } //If OpenGL is not available let's stop everything and exit the program. if(!openGLAvailable) return 0;
Step 4 : Maintain the Window visible
The way we maintain the window visible is by creating an Infinite Loop using a simple Boolean and a While Loop. Pretty simple isn’t it ? Before you try this code, read the next step to learn how to close the window.
bool gameRunning = true; while(gameRunning) { //Game Loop }
Step 5 : Close Event and Resized Event
- Close Event : To get the window closed when you click on the upper right button, we need to detect your click by catching the right Event and then end the Infinite loop by setting the boolean gameRunning to False. Events have already been briefly explained in the previous tutorial Graphics Window – Step 4.
- Resize Event : When you resize the window by dragging its borders with the mouse, you need to inform OpenGL of that change. Our window has been created with a resolution of 1080 by 720, these are the values that OpenGL knows, when you resize the window, OpenGL has no way to know that the resolution has changed. You need to inform it manually by calling the function glViewport(origin x, origin y, new width, new height). Like clicking on the close button, changing the window size with the mouse generates an Event called Resized Event. The code below shows how to catch that event and retrieve the new size of the window.
bool gameRunning = true; while(gameRunning) { // handle events sf::Event event; while(openGLWindow.pollEvent(event)) { if(event.type == sf::Event::Closed) { // end the program, close the window gameRunning = false; } else if (event.type == sf::Event::Resized) { // adjust the OpenGL viewport when the window is resized, the values are in pixel glViewport(0, 0, event.size.width, event.size.height); } } }
Step 6 : Create a Shader Program and a Triangle
The code below is used just before the Game Loop to create a simple Shader Program and a Triangle. The triangle is created using three coordinates (540, 100), (340, 360), and (740, 360).
//Initialize your game here, let's create a simple triangle //create shader program int shaderProgram = createShaderProgram(); //build a triangle TriangleState triangle; //a structure to hold the OpenGL state of the triangle createTriangle(triangle, window_resolution, sf::Vector2f(540.f, 100.f), sf::Vector2f(340.f, 360.f), sf::Vector2f(740.f, 360.f));
Step 7 : Draw our Triangle
Like in the tutorial Graphics Window – Step 6, the drawing happens in three steps
- Clear the screen
- Draw what we want
- Display the result to the viewer
// clear the buffers glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawTriangle(triangle, shaderProgram); // end the current frame (internally swaps the front and back buffers) openGLWindow.display();

Step 8 : Destroy our Triangle
When you close the window and stop the Game Loop, just before exiting the program, there is one last thing to do : Clear the GPU of all data we have created. We use our function destroyTriangle to clear the triangle data and the OpenGL function glDeleteProgram to clear the shaderProgram’s data.
//Destroy triangle state destroyTriangle(triangle); //Destroy shader Program glDeleteProgram(shaderProgram);