← На главную

Простой кроссплатформенный OpenGL-проект на C++

В свободное время я потихоньку раскуриваю OpenGL. Это у меня что-то вроде нового хобби. Как кто-то из вас может помнить, когда-то я уже пробовал играться с OpenGL на Haskell. Однако использовать тамошний ни на что не похожий и практически не документированный DSL оказалось слишком сложной задачей для меня, и от затеи пришлось отказаться.

Надо отметить, что в этот раз я чуть было не совершил ту же ошибку, выбрав поначалу язык Scala и библиотеку LWJGL. С документацией у LWJGL все отлично, никакого своего DSL не накручено, и примеров в сети очень много. Но проблема заключается в том, что в большинстве туториалов и книг по OpenGL все же используется язык C++. И если ты столкнешься с какой-то проблемой, никто ни в каком IRC или на StackOverflow не станет разбираться в твоем коде на Scala. Плюс неизвестно еще, ты просто неправильно используешь OpenGL, или может это какая-то ошибка в LWJGL, или может это какой-то хитрый баг, вызванный взаимодействием кода на Scala с LWJGL. Сообщество вокруг LWJGL достаточно велико само по себе, но чтобы кто-то из этого сообщества тебе помог, код должен быть на Java, а с Java связываться ну очень не хотелось. Кроме того, у LWJGL на данный момент есть стабильная, но постепенно теряющая свою актуальность версия 2.9, а также совершенно новая, ни с чем не совместимая, и все еще находящаяся в альфе версия 3.0.

В силу всех этих причин на время изучения OpenGL я решил использовать C++. Когда у меня будет больше опыта в самом OpenGL, можно будет попробовать использовать и альтернативные языки, например, Scala или Go. В качестве системы сборки был выбран CMake. Дело в том, что в Linux, активным пользователем которого я являюсь, по всей видимости, пока что наиболее приличной IDE для C++ является CLion. Я реально проводил серьезный ресерч на эту тему. А CLion ничего кроме CMake пока не поддерживает. Кроме того, у меня есть очень приятный опыт использования продукции JetBrains в прошлом, да и в настоящем.

Интересно, как культура и традиции могут определять выбор технологий, не правда ли?

Теперь, когда инструментарий выбран, можно создать новый Git-репозиторий, открыть CLion и создать в этом репозитории новый проект. В проекте нам понадобятся кое-какие зависимости. И тут начинается интересное, так как общепринятого способа управления зависимостями в C++ вроде как нет. Довольно перспективно выглядит Biicode, но, насколько я могу судить, пока что он не получил широкого распространения. Не то, чтобы я много работал с C++ в последнее время, но похоже, что неплохое решение заключается в следующем. Зависимости, которые можно поставить через менеджер пакетов ОС, например, apt-get, ставятся через него. Прочие же зависимости подтягиваются в виде исходных кодов через механизм сабмодулей в Git.

Например, нам в нашем проекте понадобятся библиотеки GLM и GLFW. Первая предназначена для работы с матрицами и векторами. Вторая – для рисования окошек, реагирования на движения мыши, нажатия клавиш клавиатуры и так далее. Еще для решения той же задачи есть GLUT. В одних туториалах используется GLFW, в других GLUT, и в чем принципиальное различие между ними на данный момент мне не до конца понятно. Так что, по сути, тут я выбирал наугад. Итак, используя сабмодули Git, подключаем эти библиотеки к проекту:

git submodule add https://github.com/glfw/glfw glfw git submodule add https://github.com/Groovounet/glm glm

Если вы склонируете репозиторий на другую машину, подтянуть зависимости можно так:

git submodule init git submodule update

Интересный факт о сабмодулях – они смотрят на конкретный коммит. Изменить его можно, перейдя в каталог сабмодуля и сделав checkout интересующей ветки или тэга. После этого git diff вашего собственного репозитория покажет, что в нем есть изменения, который можно закоммитить. Также в какой-то момент вы можете обнаружить, что удаляются сабмодули Git очень непросто. Делается это так (почти рабочий рецепт был найден на gist, приведенная ниже инструкция тестировалась на Git 1.9.1):

  1. Скажите git rm --cached имя_сабмодуля;
  2. Удалите соответствующие строчки из файла .gitmodules;
  3. Также грохните соответствующую секцию в .git/config;
  4. Сделайте коммит;
  5. Удалите файлы сабмодуля;
  6. Удалите каталог .git/modules/имя_сабмодуля;

Теперь правим файл CMakeList.txt нашего проекта:

cmake_minimum_required(VERSION 2.8) project(red_triangle) find_package(OpenGL REQUIRED) add_subdirectory(glfw) include_directories(glfw/include) include_directories(glm) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -Wall -Wextra -std=c++11") set(SOURCE_FILES main.cpp) add_executable(red_triangle ${SOURCE_FILES}) target_link_libraries(red_triangle glfw ${GLFW_LIBRARIES} ${OPENGL_LIBRARY})

Как видите, здесь просто указывается минимальная версия cmake, название проекта, его зависимости и флаги компиляции. Ничего сложного.

В main.cpp пишем следующий код:

#include <glm/glm.hpp> #include <GLFW/glfw3.h> #include <glm/gtc/type_ptr.hpp> #include <glm/gtx/transform.hpp> #include <iostream> int main() { if(!glfwInit()) { std::cerr << "Failed to initialize GLFW" << std::endl; return -1; } glfwDefaultWindowHints(); GLFWwindow* window = glfwCreateWindow(300, 300, "Red Triangle", nullptr, nullptr); if(window == nullptr) { std::cerr << "Failed to open GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSwapInterval(1); glfwShowWindow(window); // Важно! Не эквивалентно glEnable(GL_DEPTH_TEST | GL_DOUBLEBUFFER) glEnable(GL_DEPTH_TEST); glEnable(GL_DOUBLEBUFFER); glDepthFunc(GL_LESS); glClearColor(0, 0, 0, 1); glm::mat4 m = glm::perspective(45.0f, 4.0f / 3.0f, 1.0f, 100.0f); glMatrixMode(GL_PROJECTION); glLoadMatrixf(glm::value_ptr(m)); while(glfwWindowShouldClose(window) == GL_FALSE) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glColor4f(1, 0, 0, 1); glBegin(GL_TRIANGLES); glVertex3f( 0, 0.5, -5); glVertex3f( 0.5, -0.5, -5); glVertex3f(-0.5, -0.5, -5); glEnd(); glfwSwapBuffers(window); glfwPollEvents(); } glfwDestroyWindow(window); glfwTerminate(); return 0; }

Важно! Имейте в виду, что все эти glBegin, glEnd и glVertex3f – это устаревший OpenGL. В новых проектах вы не должны их использовать. Здесь эти функции используются только для того, чтобы не уводить повествование в сторону VAO, VBO и шейдеров. Заинтересованные читатели могут ознакомиться с почти таким же проектом на новом OpenGL.

Код предположительно очень прост и на данном этапе не нуждается в особых пояснениях. Здесь вы видите пример создания окна при помощи GLFW, простой пример использования GLM, а также рисование красного треугольника на черном фоне при помощи устаревших функций OpenGL. Заинтересованные читатели могут найти больше теории в предыдущих постах этого блога, посвященных OpenGL, или дождаться будущих. Результат же будет выглядеть как-то так:

Рисуем красный треугольник на OpenGL

А сборка проекта «вручную», без использования CLion, осуществляется так:

mkdir build cd build cmake .. make

Примечательно, что это же проект успешно компилируется и под Windows. Там есть свои сложности, связанные с необходимостью устанавливать MinGW и Git для Windows, но в целом все работает точно так же. Еще может потребоваться немного подправить идущий с MinGW файл math.h, так как в нем есть баг. Кроме того, код успешно собирается и под MacOS.

Итак, теперь у нас есть полноценная среда для изучения OpenGL. Полную версию исходного кода к заметке вы найдете в этом репозитории.

Дополнение: Также вас могут заинтересовать статьи Продолжаем изучение OpenGL: VBO, VAO и шейдеры и Пишем простую программу с использованием DirectX.