Простой кроссплатформенный OpenGL-проект на C++
24 августа 2015
В свободное время я потихоньку раскуриваю 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/Groovounet/glm glm
Если вы склонируете репозиторий на другую машину, подтянуть зависимости можно так:
git submodule update
Интересный факт о сабмодулях — они смотрят на конкретный коммит. Изменить его можно, перейдя в каталог сабмодуля и сделав checkout интересующей ветки или тэга. После этого git diff
вашего собственного репозитория покажет, что в нем есть изменения, который можно закоммитить. Также в какой-то момент вы можете обнаружить, что удаляются сабмодули Git очень непросто. Делается это так (почти рабочий рецепт был найден на gist, приведенная ниже инструкция тестировалась на Git 1.9.1):
- Скажите
git rm --cached имя_сабмодуля
; - Удалите соответствующие строчки из файла .gitmodules;
- Также грохните соответствующую секцию в .git/config;
- Сделайте коммит;
- Удалите файлы сабмодуля;
- Удалите каталог .git/modules/имя_сабмодуля;
Теперь правим файл CMakeList.txt нашего проекта:
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 <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, или дождаться будущих. Результат же будет выглядеть как-то так:
А сборка проекта «вручную», без использования CLion, осуществляется так:
cd build
cmake ..
make
Примечательно, что это же проект успешно компилируется и под Windows. Там есть свои сложности, связанные с необходимостью устанавливать MinGW и Git для Windows, но в целом все работает точно так же. Еще может потребоваться немного подправить идущий с MinGW файл math.h, так как в нем есть баг. Кроме того, код успешно собирается и под MacOS.
Итак, теперь у нас есть полноценная среда для изучения OpenGL. Полную версию исходного кода к заметке вы найдете в этом репозитории.
Дополнение: Также вас могут заинтересовать статьи Продолжаем изучение OpenGL: VBO, VAO и шейдеры и Пишем простую программу с использованием DirectX.
Метки: C/C++, OpenGL, Кроссплатформенность.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.