Создание 2d игры в opengl. Использование OpenGL в SFML. Один язык чтобы всё разрушить

Всем привет! Что-то последнее время много уроков по конструкторам игр, и не так много по различным языкам программирования. Что бы устранить эту не справедливость, я решил запилить урок по OpenGL C++, надеюсь вы оцените)

Сразу же перейдем к геймдеву и будем писать маленькую игрульку нашего детства.

Для начала подключаем библиотеку OpenGL к своей среде разработки, я лично программирую в Microsoft Visual Studio 2013.

200?"200px":""+(this.scrollHeight+5)+"px");">
#include "stdafx.h"
#include
#include
#include
#include // подключаем все необходимые инклюды.

Int N = 30, M = 20; // т.к. змейка будем ездить по квадратикам, создадим их, для нашего окна в идеале будет 30x20 квадратов
int scale = 25; // размер квадрата. Когда OpenGL будет расчерчивать поле для игры, расстояние между гранями квадрата будет 25 пикселей

Int w = scale*N; // ширина поля
int h = scale*M; // его высота

Int dir, num = 4; // 4 направления и начальный размер змеи.
struct { int x; int y; } s; // структура змеи, X и Y координаты, массив с длинной.

Class fruct // класс фруктов, тех самых, которые будет есть наша змея
{
public:
int x, y; //координаты фруктов, что и где будет находится

Void New() // паблик с новыми фруктами. Он будет вызываться в начале игры и в тот момент, когда змея съест один из фруктов
{
x = rand() % N; // вычисление X координаты через рандом
y = rand() % M; // вычисление Y координаты через рандом
}

Void DrawFruct() // паблик, отрисовывающий фрукты
{
glColor3f(0.0, 1.0, 1.0); // цвет фруктов. в openGL он задается от 0 до 1, а не от 0 до 256, как многие привыкли
glRectf(x*scale, y*scale, (x + 1)*scale, (y + 1)*scale); // "Закрашиваем" квадрат выбранным цветом, таким образом в нем "появляется" фрукт
}
} m; // масив с фруктами, таким образом, у нас появится одновременно 5 фруктов в разных местах, а не один, как мы привыкли

Void Draw() // функция, которая отрисовывает линии
{
glColor3f(1.0, 0.0, 0.0); // цвет наших линий, в данном слуае - красный
glBegin(GL_LINES); // начинаем рисовать и указываем, что это линии
for (int i = 0; i < w; i+= scale) // отрисовываем линии в ширину
{
glVertex2f(i, 0); glVertex2f(i, h); // рисуем прямую
}
for (int j = 0; j < h; j += scale) //отрисовываем линии в высоту
{
glVertex2f(0, j); glVertex2f(w, j); // рисуем ту же самую прямую, но в другом направлении
}

GlEnd(); // конец отрисовки
}

Void tick() // функция в которой будет все обновляться (двигаться змея и т.д.)
{
for (int i = num; i > 0; --i) // движение змеи. Система остроумна и проста: блок перемешается вперед, а остальные X блоков, на X+1(2 блок встанет на место 1, 3 на место 2 и т.д...)
{
s[i].x = s.x; // задаем Х координату i блока координатой i - 1
s[i].y = s.y; // то же самое делаем и с Y координатой
}
// далее у нас система направлений.
if (dir == 0) s.y += 1; // если направление равно 0, то первый фрагмент массива перемещается на один по Y
if (dir == 1) s.x -= 1; // если направление равно 1, то первый фрагмент массива перемещается на минус один по X
if (dir == 2) s.x += 1; // аналогиная система
if (dir == 3) s.y -= 1; // аналогичная система

For (int i = 0; i < 10; i++) //цикл, в котором наша змея будет расти
{
if ((s.x == m[i].x) && (s.y == m[i].y)) // Если голова нашей змеи находится в одном блоке с фруктом, то...
{
num++; //...увеличиваем размер нашей змеи на 1
m[i].New(); // ... запускаем функцию отрисовки нового фрукта.
}
}
// Следующее нужно, что бы змея не выходила за рамка поля. Действует это просто: если змея выходит за рамки поля, то задаем
if (s.x > N) dir = 1; // Ей обратное направление. Например, если она выйдет за экран по высоте, то задаем ей направление, при котором она ползет
if (s.y > M) dir = 3; // вниз
if (s.x < 0) dir = 2;
if (s.y < 0) dir = 0;

For (int i = 1; i < num; i++) // с помощью этого цикла мы "обрежем" змею, если она заползет сама на себя
if (s.x == s[i].x && s.y == s[i].y) // проверка координат частей змеи, если X и Y координата головной части равно координате любого
num = i; // другого блока змеи, то задаем ей длину, при которой "откушенная" часть отпадает.
}

Void Snake() // выводим змейку на экран
{
glColor3f(0.1, 1.0, 0.0); //цвет змеи
for (int i = 0; i < num; i++) // цикл отрисовки.
{
glRectf(s[i].x*scale, s[i].y*scale, (s[i].x + 0.9)*scale, (s[i].y + 0.9)*scale); //Рисуем квадраты, те самые "блоки" змеи
}
}

Void Key(int key, int a, int b) // функция нажатия клавиш
{
switch (key) // используем оператор switch
{
case 101: dir = 0; break; // при нажатии клавиш, задаем направление змеи(вверх, вниз, влево, вправо)
case 102: dir = 2; break;
case 100: dir = 1; break;
case 103: dir = 3; break;
}
}

Void Display() //функция общий отрисовки
{
glClear(GL_COLOR_BUFFER_BIT); // очищаем окно перед началом отрисовки

Draw(); // вызов функции Draw, отвечающей за отрисовку фруктов
Snake(); // вызов функции Snake, отвечающей за отрисовку змейки

For (int i = 0; i < 5; i++) // заполнение карты фруктами
m[i].DrawFruct();

GlFlush(); // выводим на экран все вышеописанное
glutSwapBuffers();
}

Void timer(int = 0) // Таймер игры(промежуток времени, в котором будет производится все процессы)
{
Display(); // Вызов функций
tick();
glutTimerFunc(100, timer, 0); // новый вызов таймера(100 - промежуток времени(в милисекундах), через который он будет вызыватся, timer - вызываемый паблик)
}

Int main(int argc, char **argv) // Главная функция
{
std::cout << "Snake by Alexey Ovchinnikov:P\n Loading..."; // крутой текст в консоле при загрузке
srand(time(0));
for (int i = 0; i<10; i++) // начальная, самая первая отрисовка фруктов
m[i].New();

S.x = 10; // начальное положение змейки по X
s.y = 10; // и Y координате
// следующие функции абсолютно идиентичных почти во всех программах на OpenGL, так то запоминать их не обязательно, кроме...
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(w, h); // ... этой, она создаем окно (w - ширина, h - высота)
glutCreateWindow("Game"); // ... этой, она задает название окна
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 640, 0, 480);
glutDisplayFunc (Display); // ... и этой, она вызывает начальную функцию, в нашем случае это главная функция отрисовки - Display
glutSpecialFunc(Key);
glutTimerFunc(50, timer, 0); // ... Ну и в начале программы задаем рекурсивный таймер.
glutMainLoop();

Return 0;
}

И так, на этом все, запускаем, играем. Постарался объяснить максимально понятно, если будут вопросы - задавайте.
P.S. И пожалуйста, не кидайтесь тапками если что-то не понятно или не так написал, первый урок в моей жизни.

Конечно, Go не задумывался как язык для написания игр. Но чем еще заняться холодными осенними вечерами? Давайте попробуем запустить несколько игровых движков и посмотреть, как в Go-мире обстоят дела с геймдевом.

GarageEngine

Наш первый экземпляр - это 2d игровой движок для Go, который работает на OpenGL. Он состоит из модулей/компонентов. Сейчас уже есть приличное количество таких модулей для шрифтов, спрайтов, текстур, физики, сцен и так далее. Очень сильно ощущается влияние Unity3d. Особенно в названиях компонентов: Scene, Coroutines, Components, Transform, GameObjects и т.д.

К сожалению это проект для обучения. Не стоит ожидать от него супер-килер-фич и обратной совместимости.

Установка

Нам нужно установить либы glfw и glew

Sudo apt-get update sudo apt-get install build-essential binutils-gold \ freeglut3 freeglut3-dev libalut-dev libglew-dev \ libglfw-dev libxrandr2 libxrandr-dev

Если вы используете windows вам нужно юзать mingw и собрать glew.

Go get github.com/vova616/GarageEngine

Примеры

SpaceCookies - Небольшой пример для тестирования движка. Есть несколько нюансов, чтобы нормально запустить этот пример.

Прежде всего заходим в папку src/github.com/vova616/GarageEngine и собираем все руками:

Go build main.go

Если появилась ошибка вида:

# github.com/vova616/GarageEngine/engine/audio .godeps/src/github.com/vova616/GarageEngine/engine/audio/AudioSource.go:129: undefined: openal.DistanceModel

Комментируем строки 128-130 в файле src/github.com/vova616/GarageEngine/engine/audio/AudioSource.go

If currentDistanceModel != this.distanceModel { openal.SetDistanceModel(openal.DistanceModel(this.distanceModel)) }

Пытаемся запустить и получаем ошибку listen tcp 0.0.0.0:123: bind: permission denied . Меняем номера портов в файле src/github.com/vova616/GarageEngine/spaceCookies/server/Server.go на строке 88

Addr, err:= net.ResolveTCPAddr("tcp", "0.0.0.0:12345")

и в файле src/github.com/vova616/GarageEngine/spaceCookies/game/Client.go строки 19 и 20

Const ServerIP = "localhost:12345" const ServerLocalIP = "localhost:12345"

Опять пробуем запустить и ничего не получается. Рядом с нашим бинарником лежит файл log.txt c примерно таким содержимым:

Enginge started! GLFW Initialized! Opengl Initialized! open ./data/spaceCookies/background.png: no such file or directory open ./data/spaceCookies/button.png: no such file or directory runtime error: invalid memory address or nil pointer dereference panic.c:482, os_linux.c:234, Sprite.go:42, LoginScene.go:83, Engine.go:96, main.go:78, main.go:44, proc.c:220, proc.c:1394

Видим, что программа пытается найти файлы./data/spaceCookies/background.png и./data/spaceCookies/button.png . А папка, на самом деле, называется./data/SpaceCookies . Переименовываем папку и снова запускаем. Ура! Теперь все работает.

☣ Azul3D

Azul3D это 3D движок написанный полностью с нуля на языке программирования Go. Azul3D подходит для создания 2D и 3D игр. Так же, его можно использовать для создания не игровых, а просто интерактивных приложений. В отличии от большинства современных движков(таких как Unity, JMonkey) у Azul3D нет дополнительных фич типа редакторов уровней, IDE. Это просто набор необходимых разработчику пакетов.

Это игровой движок от программистов для программистов. Он очень минималистичен, но легко расширяется в отличии от других движков.

Azul3D предоставляет самые необходимые инструменты, которые будут использоваться изо дня в день. И делает это действительно хорошо.

Установка

Начинаем с зависимостей для Ubuntu 14.04. Для других систем .

Sudo apt-get install build-essential git mesa-common-dev \ libx11-dev libx11-xcb-dev libxcb-icccm4-dev \ libxcb-image0-dev libxcb-randr0-dev libxcb-render-util0-dev \ libxcb-xkb-dev libfreetype6-dev libbz2-dev \ libxxf86vm-dev libgl1-mesa-dev

Примеры

Теперь пробуем установить примеры. Они вынесены в отдельный пакет.

Go get azul3d.org/examples.v1

нам понадобится куча других пакетов:

Go get azul3d.org/keyboard.v1 go get azul3d.org/lmath.v1 go get azul3d.org/gfx.v1 go get azul3d.org/gfx/window.v2 go get azul3d.org/mouse.v1 go get azul3d.org/tmx.dev

Теперь можем посмотреть примеры. Заходим в src/azul3d.org/examples.v1/azul3d_tmx и запускаем пример

Go run azul3d_tmx.go

Вы увидите что-то такое:

Были проблемы с версией Go ниже 1.3. Обновился до самой последней и все отлично заработало на моей ubuntu 14.04.

gosfml

Возможность использовать C-код в Go приложениях - это просто прекрасно. Мы можем использовать кучу готовых библиотек.

И конечно же, нельзя обойти стороной Go биндинги к популярной библиотеке SFML . Есть несколько вариантов, но я смог запустить только этот .

SFML - это кросплатформенная библиотека для мультимедиа. Она написана на C++, но есть куча привязок к разным языкам и к Go в том числе.

Кроме работы с мультимедиа в этой либе есть несколько пакетов для программирования простых игр.

Модули, которые нам доступны в этой библиотеке:

  • System - управление временем и потоками.
  • Window - управление окнами и взаимодействием с пользователем.
  • Graphics - делает простым отображение графических примитивов и изображений.
  • Audio - предоставляет интерфейс для управления звуком.
  • Network - для сетевых приложений.

Во время своего #shaderchallenge (заинтересовался как устроена красивая графика, какие визуальные эффекты можно делать шейдерами и тд,- и решил непременно изучить) я пришел к тому, что мне необходимо знать как устроены внутри игровые движки. И, конечно, нет способа лучше изучить устройство чем попробовать написать свой!

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

В 2016ом я решил прокачаться в программировании графики и начал изучать программирование шейдеров. И как оказалось в этом деле никуда без математики, поэтому я хорошую часть этого года вспоминал, а по-большей части заново знакомился с линейной алгеброй и кое-каким мат. анализом.

С какими-то простыми эффектами я познакомился в Unity3d, я по роду своей деятельности программирую именно на этом движке. Делал скринэффекты, простой аутлайн и тп. Кстати, знание того, как устроен пайплайн и какие эффекты можно получить, развязывают руки и в некоторых ситуациях просто думаешь: — Ага, так можно вот шейдерочек написать.

Но, как я говорил выше, необходимы более глубокие знания. Пишу свой движок. Конечно на C++ и OpenGL. Не DirectX, потому, что мне казалось, что на консолях именно OpenGL, но это не так.

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

Про графическую библиотеку пояснил. Теперь про язык. Я очень доволен выбором именно C++, какой он крутой, ребята! Сколько всего я о нем не знал! Сколько добавилось в новых стандартах и сколько еще предстоит изучить!

Итак, встречайте, первое более-менее презентабельное демо, в моей собственном игровом движке Rudy Engine.

Первое демо

Реализовано: cubemap/skybox, загрузка моделей через assimp, half-lambert для освещения, overlay-рендерер для прицела и маркера-указателя на космическую станцию.


(очень дрожит видео, не стал искать какую-то хорошую программу для записи, воспользовался monosnap, наверное он не очень для этого подходит)

Дальнейшие планы

В дальнейшем я хочу сделать туман, shadow-mapping, и какие-то эффекты для этой демки и, возможно, переключиться на какую-то следующую. В целом, стараюсь придерживаться взрослой и мучительной разработки: дорабатывать движок таким образом, чтобы старые демо оставались рабочими. Подрихтовываю и подлатываю его. Для чего? Нужно понять этот trade-off как у Unity, чего стоит сделать универсальный инструмент?

Update 2017

Летом в 2017ом году, у меня закончился испытательный срок в крупной игровой студии и я написал в блоге, вся вот эта практика с игровым движком, мне очень помогла!

Мне нужно сделать что-то вроде рейтресинга, но существенно проще - просто найти депт по лучу из каждой руки, т.е. определить расстояние до обьекта от контроллера (в руке).
Что бы не делать дурную работу, я для черновой отбраковки использовал BoundingBox::Intersects.
Все работало отлично, пока я не стал вращать предметы. Сейчас у меня данный метод работает только для оригинальных обьектов. Положив предмет "на бок" находится только точка строго по оси вращения, т.е все остальное отбрасывается, что совершенно неверно. Наверно надо как-то повернуть BoundingBox, но у него нет такого метода.
Кто-то может дать совет как с этим бороться?

4 апр. 2019 18:30 Suslik

Я методом тыка нашёл ответ, но ума не приложу, почему он такой. Хочу понять, что происходит.

Дан точечный источник света, излучающий по закону . Дана тонкая линза со всеми известными параметрами - фокусное расстояние, положение. Для любого положения источника можно рассчитать положение его образа, а именно линза изменяет волновой фронт от источника таким образом, что после преобразования он получается эквивалентным волновому фронту его изображения. Вопрос - по какому закону будет излучать образ источника в линзе?

4 апр. 2019 1:52 zombihello

Привет народ!

Прикручиваю в своем двигле инстансинг и столкнулся с несколькими траблами ответ на который я так вразумительного не нашел.

Во-первых , как правильно реализовать отсечение геометрии (банально, например, по пирамиде видимости) при отрисовке инстансингом? Первая мысль какая в голову приходить, так это при каждом рендере перезаполнять с помощью glBufferData VBO где хранится информация об экземплярах (копиях) модели которую будем рисовать. Но такой способ меня смущает тем, что при большом количестве данных все упрется в пропускную способность шины.

Во-вторых , как вообще посоветуете организовывать работу с инстансингом, как Вы данную вещь делали? Опишу как у меня сейчас. Для модели (без повторений) формирую один VBO для вершин, другой VBO для инстансиг информации (по началу выделяю на 10.000 байт) и EBO для индексов вершин. При создании копии модели я в буфер инстансинга записываю матрицу трансформации с проверкой на кол-во используемой памяти в буфере, если уже нужно больше чем выделено - копирую во временный буфер все содержимое, с помощью glBufferData(.., useSize + 10000, NULL, ...) выделяю для рабочего буфера на 10.000 байт больше (учитывая текущий размер + новый элемент), копирую с временного буфера всю информацию в рабочий и с помощью glBufferSubData дописываю новый элемент. Но мне что-то подсказывает что это все можно решить куда проще)

Буду благодарен вашим советам.

Имеется полигональная сетка, координаты текстур динамически вычисляются в зависимости от номера ячейки сетки. Номер ячейки передается из вершинного шейдера, на границах ячеек возникает артефакт в виде пунктирной линии (при смене угла зрения меняется и иногда может исчезнуть). Выглядит вот так:

Координаты текстуры меняются так (cell - передается из вершинного шейдера и представляет собой кординаты ячейки):

float PHI = 1.61803398874989484820459; // Golden Ratio vec2 uv = PHI*fragTexCoord + floor(cell)*PHI;

Хотелось бы в общих чертах понять почему эта линия на стыках вообще возникает и как от нее избавится.

Update: Отключение фильтрации в семплере не помогает.

Update2: Если ликвидировать функцию floor - линия на стыках исчезает, но мне нужна целая часть от числа и без floor я обойтись не могу.

Правка: 6:55

Эта статья не о самом OpenGL , а скорее о том, каким образом использовать совместно OpenGL и SFML .

Как вы знаете, одна из самых важных сторон OpenGL - переносимость на другие платформы. Но одного OpenGL недостаточно для создания завершенных приложений: вам потребуется окно, контекст рендеринга, пользовательский ввод, и так далее. У вас нету другого выбора, кроме как писать платформо-зависимый код для управления этими вещами. В этом месте модуль sfml-window выходит из тени. Давайте посмотрим, как он позволяет вам использовать OpenGL .

Включаем и линкуем OpenGL к вашему приложению

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

#include

Этот заголовочный файл включает функции OpenGL и GLU , ничего более. Люди иногда думаю, что SFML автоматически подключает GLEW (библиотеку, которая дает управление над расширениями OpenGL ), потому что SFML использует GLEW внутри, но это технические детали. Со стороны пользователя, GLEW должен быть подключен также, как остальные библиотеки расширений.

Затем нам потребуется прилинковать к вашему приложению библиотеку OpenGL . К несчастью, такого же простого пути,как с заголовочными файлами, нету, и SFML не может предоставить общий принцип подключения OpenGL . Таким образом, вам нужно знать, какая библиотека прилинкована, в зависимости от используемой операционной системы ("opengl32 " в Windows , "GL" в Linux , и так далее). То же самое касается GLU , если вы хотите его использовать ("glu32 " в Windows , "GLU " в Linux , и так далее).

Функции OpenGL начинаются с префикса "gl ", функции GLU начинаются с префикса "glu ", Помните об это, когда будете получать ошибки при линковке - это поможет вам определить, какую библиотеку вы забыли прилинковать.

Создаем окно OpenGL

Так как SFML основан на OpenGL , его окна готовы для работы с функциями OpenGL безо всяких дополнительных действий.

Sf::Window window(sf::VideoMode(800, 600), "OpenGL"); // это работает без каких-либо дополнительных действий glEnable(GL_TEXTURE_2D); ...

В случае, если вы думаете, что это слишком автоматизировано, конструктор sf::Window имеет дополнительный аргумент, который позволяет изменить настройки основного контекста OpenGL . Этот аргумент - это экземпляр структуры sf::ContextSettings , который обеспечивает доступ к следующим настройкам:
depthBits - это количество битов на пиксель для использования в буфере глубины (0 , чтобы отключить его)
stencilBits - это количество бит на пиксель, чтобы использовать для буфера трафарета (0 , чтобы отключить его)
antialiasingLevel - это уровень мультисэмплинга
MajorVersion и MinorVersion содержат запрашиваемую версию OpenGL

Sf::ContextSettings settings; settings.depthBits = 24; settings.stencilBits = 8; settings.antialiasingLevel = 4; settings.majorVersion = 3; settings.minorVersion = 0; sf::Window window(sf::VideoMode(800, 600), "OpenGL", sf::Style::Default, settings);

Если какие-либо из этих настроек не поддерживаются графической картой, SFML попытается найти ближайшее схожее значение. Например, если 4х-антиалиасинг не поддерживается, SFML попытается использовать 2х, а если и это не поможет, то такая функция будет попросту отключена.
В любом случае, вы можете проверить, какие настройки SFML использует в данный момент с помощью функции getSettings :

Sf::ContextSettings settings = window.getSettings(); std::cout << "depth bits:" << settings.depthBits << std::endl; std::cout << "stencil bits:" << settings.stencilBits << std::endl; std::cout << "antialiasing level:" << settings.antialiasingLevel << std::endl; std::cout << "version:" << settings.majorVersion << "." << settings.minorVersion << std::endl;

OpenGL версии 3.0 и выше поддерживается в SFML (если поддерживается в драйвере видеокарты), но вы не сможете установить флаги на данный момент. Это означает, что вы не можете создать отладочный или совместимый контекст. На самом деле SFML автоматически создает контекст с "совместимыми" флагами, потому что он использует внутренние устаревшие функции. Это будет исправлено в ближайшее время и флаги будут выставлены также и в общественном API .

На OS X SFML поддерживается создание контекста OpenGL 3.2 используя профиль ядра. Помните, что эти контексты не совместимы с графическим модулем SFML. Если вы хотите использовать графический модуль, вам нужно использовать стандартный контекст с версией 2.1.

Типичное OpenGL-SFML приложение

Тут полный код минимального SFML приложения, использующего OpenGL :

#include #include int main() { // создаем окно sf::Window window(sf::VideoMode(800, 600), "OpenGL", sf::Style::Default, sf::ContextSettings(32)); window.setVerticalSyncEnabled(true); // загружаем ресурсы, инициализируем состояния OpenGL // запускаем главный цикл bool running = true; while (running) { // обрабатываем события sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { // пора закрывать приложение running = false; } else if (event.type == sf::Event::Resized) { // применяем область просмотра, когда изменены размеры окна glViewport(0, 0, event.size.width, event.size.height); } } // очищаем буферы glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // рисуем // конец текущего кадра (меняем местами передний и задний буферы) window.display(); } // освобождаем ресурсы return 0; }

Здесь мы не используем window.isOpen() как условие в главном цикле, потому что нам нужно, чтобы окно оставалось открытым, пока приложение не закончит свою работу, поэтому у нас все еще будет к этому моменту валидный контекст OpenGL для последней итерации цикла и очистки ресурсов.

Не стесняйтесь посмотреть на примеры "OpenGL " и "Window " в SFML SDK , там вы увидите более развернутые объяснения, которые могут содержать ответы на ваши вопросы.

Управляем несколькими OpenGL окнами

Управление многими окнами OpenGL не намного сложнее, чем управление одним, здесь только стоит запомнить несколько важных вещей.

Вызовы OpenGL делаются в активном контексте (в активном окне). Таким образом, если вы хотите отрисовать два разных окна в одном приложении, вам нужно выбрать, какое окно будет активным, перед тем, как что-либо нарисовать. Для этого нужно использовать функцию setActive:

// активируем первое окно window1.setActive(true); // рисуем первое окно // активируем второе окно window2.setActive(true); // рисуем второе окно

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

Другая вещь, которую нужно знать о контекстах OpenGL , созданных с помощью SFML - это разделение их ресурсов. Это значит, что вам не нужно перезагружать все ваши ресурсы OpenGL , когда вы пересоздаете окно. Только общие ресурсы OpenGL могут быть разделены между контекстами. Пример неразделяемого ресурса - буфер вершин.

OpenGL без окна

Иногда требуется вызвать функции OpenGL без активного окна и, соответственно, без контекста. Например, когда вы загружаете текстуры из разных потоков или перед тем, как первое из окон будет создано. SFML позволяет создавать полу-контекстные окна с помощью класса sf::Context . Все, что вам нужно сделать, это создать его экземпляр, чтобы получить правильный контекст.

Int main() { sf::Context context; // загружаем ресурсы OpenGL sf::Window window(sf::VideoMode(800, 600), "OpenGL"); ... return 0; }

Рисование в потоках

Типичное использование многопоточных приложений - это управление окном и его событиями в одном потоке (главном), и рисование в другом. Если вы так делаете, вам нужно кое-что запомнить: вы не можете активировать контекст (окно), если оно активировано в другом потоке. Это значит, что вам нужно деактивировать ваше окно перед запуском потока отрисовки.

Void renderingThread(sf::Window* window) { // активируем контекст окна window->setActive(true); // цикл отрисовки while (window->isOpen()) { // рисуем // конец текущего кадра - здесь функция отрисовки(требует контекст) window->display(); } } int main() { // создаем окно sf::Window window(sf::VideoMode(800, 600), "OpenGL"); // деактивируем его контексты OpenGL window.setActive(false); // запускаем поток отрисовки sf::Thread thread(&renderingThread, &window); thread.Launch(); // цикл событий\логики\чего-либо еще while (window.isOpen()) { ... } return 0; }

Использование OpenGL вместе с графическим модулем SFML

В этой статье описывается совместное использование OpenGL с модулем sfml-window , это очень легко, так как это единственная цель этого модуля. Использование совместно с графическим модулем немного сложнее: sfml-graphics тоже использует OpenGL , поэтому требуется усиленное внимание, чтобы состояния, заданные пользователем и внутри SFML не конфликтовали между собой.

Если вы еще не изучили графический модуль, то вам следует знать, что класс sf::Window заменен классом sf::RenderWindow , который содержит все его функции и добавляет функционал для отрисовки специальных сущностей SFML .

Единственный путь избежать конфликтов между состояниями SFML и вашими состояниями OpenGL - это сохранение\восстановление их каждый раз, когда вы переключаетесь между SFML и OpenGL .

Draw with OpenGL - save OpenGL states - draw with SFML - restore OpenGL states - draw with OpenGL ...

Простое решение - это позволить SFML делать это за вас,с помощью функций pushGLStates/popGLStates :

GlDraw... window.pushGLStates(); window.draw(...); window.popGLStates(); glDraw...

Так как SFML не знает всё о вашем коде OpenGL , он не может оптимизировать все шаги и в результате сохраняет\загружает все доступные состояния и матрицы OpenGL . Это может быть приемлемо для небольших проектов, но может существенно замедлять работу больших приложений, которые требуют максимальной производительности. В этом случае, вы можете управлять сохранением и загрузкой состояний OpenGL вручную, с помощью glPushAttrib/glPopAttrib , glPushMatrix/glPopMatrix , и так далее.
Если вы будете это делать, вам не нужно восстанавливать состояния SFML перед отрисовкой. Это будет сделано с помощью функции resetGLStates .

GlDraw... glPush... window.resetGLStates(); window.draw(...); glPop... glDraw...

Сохраняя и загружая состояния OpenGL собственноручно, вы можете использовать только те, которые вам реально необходимы, для снижения лишних вызовов драйвера.