В свободное время я потихоньку раскуриваю 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 и шейдеры