Существует довольно много библиотек, которые способны отображать окна для Opengl - SDL, Glut (freeglut), QT, SFML, можно даже использовать нативные API операционной системы (вроде Winapi для Windows). Но SFML имеет ряд важных преимуществ, это мультимедийная библиотека, представляющая из себя объектно ориентированный аналог SDL, что делает её гораздо удобнее последней, но при этом более многофункциональной чем, например, Glut. В частности, SFML имеет интерфейс для управления текстурами, тогда как при использовании glut пришлось бы подключать дополнительные библиотеки, такие как SOIL2. Помимо этого, у SFML очень хорошая и удобная документация на официальном сайте.

Установка

Установить SFML и Opengl можно разными способами, но удобно воспользоваться для этого пакетными менеджерами. Для работы в Visual Studio можно воспользоваться nuget, но мне для c++ проектов показался удобнее vcpkg, относительно недавно появившийся мультиплатформенный пакетный менеджер от Microsoft.

Установка vcpkg производится слегка необычным образом, для этого необходимо клонировать репозиторий vcpkg (желательно чтобы в путях установки не было кириллицы, иначе могут возникнуть проблемы)

git clone https://github.com/Microsoft/vcpkg.git

И затем вызвать скрипт загрузки

.\vcpkg\bootstrap-vcpkg.bat

После этого можно устанавливать библиотеки

vcpkg install sfml
vcpkg install opengl
vcpkg install glew

Установка занимает довольно продолжительное время

Чтобы установленные пакеты были видны из Visual Studio, надо написать следующее

vcpkg integrate install

Обратите внимание, что по умолчанию устанавливаются пакеты 32-битной разрядности, для установки 64-битной версии пакета необходимо явно задать версию.

vcpkg install sfml:x64-windows
vcpkg install opengl:x64-windows
vcpkg install glew:x64-windows

Работа с SFML и OpenGL

Простая программа на SFMl выглядит следующим образом

#include <SFML/Graphics.hpp>

int main()
{
    // Создаём окно
    sf::RenderWindow window(sf::VideoMode(200, 200), "SFML window");

    // Главный цикл
    while (window.isOpen())
    {
        sf::Event event;
        // Цикл обработки событий
        while (window.pollEvent(event))
        {
            // Событие закрытия окна, 
            if (event.type == sf::Event::Closed)
                window.close();
        }

        // Перерисовка окна
        window.display();
    }

    return 0;
}

Здесь просто создаётся окно, и до тех пор, пока оно открыто, происходит перерисовка.

Теперь запустим простую программу для работы с OpenGL, которая выводит на экран окно с разноцветным крутящимся квадратом.

ВАЖНО!!! СЛЕДУЮЩИЙ КОД НАПИСАН С ИСПОЛЬЗОВАНИЕМ УСТАРЕВШЕГО API OPENGL И СЛУЖИТ ИСКЛЮЧИТЕЛЬНО ДЛЯ ПРОВЕРКИ РАБОТОСПОСОБНОСТИ OPENGL И SFML. ОН НЕ ЯВЛЯЕТСЯ ПРИМЕРОМ ИЛИ ЗАГОТОВКОЙ ДЛЯ ВЫПОЛНЕНИЯ ЛАБОРАТОРНЫХ РАБОТ!

#include <SFML/OpenGL.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>


void SetIcon(sf::Window& wnd);
void Init();
void Draw();


int main() {
    // Создаём окно
    sf::Window window(sf::VideoMode(600, 600), "My OpenGL window", sf::Style::Default, sf::ContextSettings(32));
    // Ставим иконку (окна с дефолтной картинкой это некрасиво)
    SetIcon(window);
    // Включаем вертикальную синхронизацию (синхронизация частоты отрисовки с частотой кадров монитора, чтобы картинка не фризила, делать это не обязательно)
    window.setVerticalSyncEnabled(true);

    // Активируем окно
    window.setActive(true);

    // Инициализация
    Init();

    // Главный цикл окна
    while (window.isOpen()) {
        sf::Event event;
        // Цикл обработки событий окна, тут можно ловить нажатия клавиш, движения мышки и всякое такое
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                // Окно закрыто, выходим из цикла
                window.close();
            }
            else if (event.type == sf::Event::Resized) {
                // Изменён размер окна, надо поменять и размер области Opengl отрисовки
                glViewport(0, 0, event.size.width, event.size.height);
            }
        }

        // Очистка буферов
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Рисуем сцену
        Draw();

        // Отрисовывает кадр - меняет передний и задний буфер местами
        window.display();
    }

    return 0;
}

// В момент инициализации разумно произвести загрузку текстур, моделей и других вещей
void Init() {
    // Очистка буфера тёмно жёлтым цветом
    glClearColor(0.5f, 0.5f, 0.0f, 1.0f);
}

// Глобальные переменные это плохо, тут это сделано просто для примера
GLfloat rotate_z = 0;

// Функция непосредственно отрисовки сцены
void Draw() {
    rotate_z += 0.5;

    // Используем устаревшую функциональность установки единичной матрицы преобразования
    glLoadIdentity();
    // С использованием устаревшей функции glRotate домножаем на матрицу поворота
    glRotatef(rotate_z, 0.0, 0.0, 1.0);

    // Используем устаревшую конструкцию glBegin-glEnd для рисования квадрата
    glBegin(GL_QUADS);
    glColor3f(1.0, 0.0, 0.0); glVertex2f(-0.5f, -0.5f);
    glColor3f(0.0, 1.0, 0.0); glVertex2f(-0.5f, 0.5f);
    glColor3f(0.0, 0.0, 1.0); glVertex2f(0.5f, 0.5f);
    glColor3f(1.0, 1.0, 1.0); glVertex2f(0.5f, -0.5f);
    glEnd();

    // Отправляем набор команд отрисовываться
    glFlush();
}


void SetIcon(sf::Window& wnd)
{
    sf::Image image;

    // Вместо рисования пикселей, можно загрузить иконку из файла (image.loadFromFile("icon.png"))
    image.create(16, 16);
    for (int i = 0; i < 16; ++i)
        for (int j = 0; j < 16; ++j)
            image.setPixel(i, j, {
                (sf::Uint8) (i * 16), (sf::Uint8)(j * 16), 0 });

    wnd.setIcon(image.getSize().x, image.getSize().y, image.getPixelsPtr());
}