Наконец голова и руки дошли до написания первой программы с использованием OpenGL. Сразу скажу, что тема довольно нудная и с непривычки сложная (хотя по факту всё не так ужасно). Нужно немного терпения и усидчивости.
Если вы не в курсе про принципы работы OpenGL, рекомендую сначала ознакомиться:
У меня всё работает на ноутбуке с системой Windows 10. Я поставил на него компилятор языка C g++ в составе пакета mingw64. Там же есть графическая библиотека SDL2.
Как всё это скачать, установить и настроить, подробно описано здесь:
Редактирую в редакторе FAR, а компилирую из командной строки. Если у вас стоит какая-то IDE, типа Visual Studio, то вам нужно будет самостоятельно подключить заголовочные файлы и библиотеки к проекту. Какие именно – будет рассказано по ходу пьесы.
1. Загрузчик OpenGL-функций
Наша программа должна вызывать различные OpenGL-функции. Но проблема в том, что мы не можем скомпилировать программу с этими функциями – указателей на них просто не существует.
Функции содержатся в драйвере видеокарты, а так как различных видеокарт и драйверов много, указатели на функции мы должны получать в живом виде уже после запуска нашей программы.
Это чудовищно нудная часть, но к счастью существуют загрузчики, которые берут всю эту канитель на себя.
Загрузчик это обычно два файла: заголовочный и библиотека. Нужно просто добавить их в проект. Сейчас модно использовать генерируемые загрузчики.
Суть генерируемого загрузчика в том, что буквально генерируется исходный код на C под какую-то конкретную версию OpenGL, и ничего лишнего. После добавления этого кода в проект он становится его частью (как будто вы написали его руками), и вам больше не нужны никакие внешние зависимости.
Генераторы работают или на Python, или на Lua, так что может потребоваться поставить на машину дополнительный язык программирования. Но проще всего воспользоваться онлайн-генератором, например GALOGEN:
Полный список библиотек и генераторов вы можете посмотреть здесь:
https://www.khronos.org/opengl/wiki/OpenGL_Loading_Library
Генератор выдаст вам два файла: gl.h и gl.c.
Эти файлы мы добавляем в папку своего проекта. И пора начинать писать программу:
- define SDL_MAIN_HANDLED – заморочка SDL для работы в Windows.
- include <SDL2/SDL.h> – подключаем заголовочный файл библиотеки SDL.
- include "gl.h" – подключаем заголовочный файл загрузчика OpenGL, сгенерированный с помощью GALOGEN. Этот файл находится в одном каталоге с нашей программой, поэтому используем кавычки.
- include <SDL2/SDL_opengl.h> – подключаем OpenGL-интерфейс для SDL.
2. Настройка контекста
Начинаем писать функцию main():
Инициализируем видео с помощью SDL_Init(SDL_INIT_VIDEO) и переходим к настройкам OpenGL.
Функция SDL_GL_SetAttribute() устанавливает одно из свойств (атрибутов) OpenGL в нужное значение. Как свойства, так и значения можно задать с помощью констант, уже объявленных в заголовочных файлах.
SDL_GL_DOUBLEBUFFER это константа-атрибут для включения режима двойной буферизации (техника, используемая для анимации, которую мы обсудим дальше).
SDL_GL_ACCELERATED_VISUAL – атрибут, который включает аппаратное ускорение (что нам и нужно)
SDL_GL_RED_SIZE – атрибут, который задаёт размер красного компонента цвета. В нашем случае это 8 бит, или 1 байт. Аналогичные атрибуты задают размер зелёного, синего и альфа-компонентов.
Наконец, мы задаём свойства контекста OpenGL. Это актуальная версия 4.3 (SDL_GL_CONTEXT_MAJOR_VERSION = 4 и SDL_GL_CONTEXT_MINOR_VERSION = 3). И профиль SDL_GL_CONTEXT_PROFILE_MASK, который мы устанавливаем в значение CORE. Это значит, что мы не будем поддерживать совместимость с предыдущими версиями OpenGL.
Обратите внимание, что при генерации загрузчика в GALOGEN мы тоже указали версию 4.3.
После того, как мы описали с помощью этих атрибутов OpenGL-контекст, мы можем создать графическое окно с помощью SDL.
Создание окна – стандартное, но здесь мы передаём дополнительный флаг SDL_WINDOW_OPENGL.
Затем мы получаем указатель на контекст OpenGL, связанный с этим окном:
SDL_GLContext context = SDL_GL_CreateContext(window);
Саму переменную context мы после этого не используем, так как контекст, который мы получили, становится текущим и все последующие операции будут выполняться в нём.
И вот что мы делаем:
glClearColor(0.5, 0.0, 0.0. 0.0);
Мы задали цвет, которым очищается экран. Это 4 числа, которые соответствуют компонентам R, G, B и альфа (прозрачность). В отличие от обычных графических библиотек, компоненты это не целые числа от 0 до 255, а вещественные числа от 0 до 1.
Таким образом, мы задали половину интенсивности красного цвета (0.5) и нулевые интенсивности всего остального. Следовательно, цвет должен получиться тёмно-красным.
Далее мы задаём область просмотра (viewport) OpenGL. Это прямоугольник, на котором происходит рендеринг. Его размеры совпадают с размерами нашего окна (что необязательно, но логично):
glViewport(0, 0, width, height);
Задавать размеры области просмотра необходимо, так как в OpenGL используются нормализованные координаты 0..1, и нужно преобразовывать их в экранные координаты. А для этого нужно знать размеры области просмотра.
3. Программа
Далее мы организуем цикл опроса событий. Он не имеет отношения к OpenGL, а является стандартной частью любой интерактивной программы. Мы с ним знакомились в материалах про Сапёра:
В цикле мы используем функции OpenGL:
glClear(GL_COLOR_BUFFER_BIT);
Очищаем экран заданным ранее цветом (тёмно-красным). Флаг GL_COLOR_BUFFER_BIT, передаваемый в функцию, указывает на то, что мы хотим очистить цветовой буфер. В OpenGL также есть буферы глубины (depth) и трафарета (stencil), но мы их пока не трогаем, и следовательно не очищаем.
После очистки буфера мы меняем два буфера местами:
SDL_GL_SwapWindow(window);
Как это работает? Ранее упоминался механизм двойной буферизации. Один буфер (то есть область памяти с нарисованным в ней изображением) мы видим на экране, а рисование происходит в другом буфере, который мы не видим. После того как рисование закончено, буфера меняются местами. Теперь на экране мы видим тот буфер, в котором рисовали, а рисуем в том, который видели.
Такая смена буферов позволяет добиться большей плавности анимации.
Но у нас пока нет никакой анимации. Мы просто закрашиваем буфер красным цветом, и повторяем это бессмысленное действие в цикле:
Но сам факт работы программы говорит о том, что всё в порядке.
После получения события SDL_QUIT мы выходим из цикла и освобождаем контекст OpenGL и прочие ресурсы:
Вот и вся программа, наиболее простой и короткий вариант. Конечно, на этом нельзя останавливаться. Наша цель – нарисовать два треугольника, имеющие разные цвета вершин. Выглядеть это будет так:
Предстоит задать вершины, цвета, и написать шейдеры. Это большой объём материалов, поэтому в одном выпуске я про это не расскажу. Но очень скоро, буквально на днях, будут новые выпуски.
Текущий код проекта лежит на github в ветке init.
Читайте дальше: Готовим данные и рисуем простой треугольник