Часть №3: Рендеринг спрайтов в игре «Breakout» на С++/OpenGL

  Дмитрий Бушуев  | 

  Обновл. 20 Дек 2020  | 

 2301

Чтобы привнести немного жизни в черную бездну нашего игрового мира, мы добавим в нее несколько спрайтов. У термина спрайт существует много различных определений, но фактически это — 2D-изображение, используемое вместе с некоторой информацией (например, информацией о его положении, повороте и размере), задающей его позиционирование в окружающем пространстве. Если говорить совсем по-простому, то спрайты — это визуализируемые объекты изображений/текстур, которые мы будем использовать в нашей 2D-игре.

Как и на предыдущих уроках, мы можем создавать 2D-фигуру из набора вершинных данных, затем передавать их в графический процессор и после этого вручную произвести все необходимые преобразования. Однако в более крупном приложении, как наше, предпочтительнее иметь некоторые абстракции для рендеринга 2D-фигур. Иначе, если бы мы продолжили для каждого объекта самостоятельно задавать его форму и преобразования, то это быстро бы привело к беспорядку в исходных кодах программы.

На этом уроке мы определим класс рендеринга, который позволит нам при минимальном объеме кода визуализировать большое количество уникальных спрайтов. Таким образом, мы абстрагируем код геймплея от остального кода рендеринга, как это обычно делается в более крупных проектах. А начнем с того, что подготовим необходимую матрицу проекции.

Матрица 2D-проекции

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

Так как игра создается полностью в 2D-пространстве (а это значит, что про третье измерение (перспективу) можно забыть), для рендеринга мы задействуем матрицу ортографической проекции. Матрица ортографической проекции напрямую преобразует координаты вершин в нормализованные координаты устройства. Определяя проекционную матрицу следующим образом, у нас появляется возможность применять координаты мирового пространства в качестве координат экрана:

Первые четыре аргумента определяют (по порядку) левую, правую, нижнюю и верхнюю части усеченной пирамиды проекции. Описанная проекционная матрица преобразует все x-координаты из диапазона [0; 800] в диапазон [-1; 1], а все y-координаты — из диапазона [0; 600] в диапазон [-1; 1]. Вышеописанной строкой кода мы указываем, что верхняя часть усеченной пирамиды имеет y-координату, равную 0, а нижняя часть — y-координату, равную 600. В результате верхний левый угол сцены будет иметь координаты (0; 0), а нижний правый — координаты (800; 600), точно так же, как и координаты экрана; координаты мирового пространства напрямую соответствуют координатам результирующих пикселей.

Благодаря этому, у нас появляется возможность задать прямое (и что самое главное — интуитивно понятное) соответствие между координатами вершин и пикселями на экране, в которые попадут вершины.

Рендеринг спрайтов


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

Примечание: Поскольку «Breakout» — это игра с одной сценой, то нам не потребуются матрицы вида/камеры. При помощи матрицы проекции можно напрямую преобразовать координаты мирового пространства в нормализованные координаты устройства.

Для преобразования спрайта мы будем использовать следующий вершинный шейдер:

Обратите внимание, что данные, как координат позиции, так и текстурных координат, сохранены в одной переменной типа vec4. Поскольку координаты позиции и текстурные координаты содержат два значения типа float, то можно объединить их в один атрибут вершины.

Фрагментный шейдер также относительно прост. Мы берем текстуру и цветовой вектор, которые определяют конечный цвет фрагмента. А имея uniform-переменную цветового вектора, мы можем легко управлять цветом спрайтов непосредственно из игрового кода:

Чтобы сделать рендеринг спрайтов более организованным, напишем класс SpriteRenderer, который способен визуализировать спрайт при помощи всего лишь одной функции. Его определение выглядит следующим образом:

Класс SpriteRenderer содержит шейдер и один VAO, а также функцию визуализации и инициализации. Конструктор класса в качестве параметра принимает шейдер, который будет использоваться для всего последующего рендеринга.

Инициализация

Сначала давайте подробнее рассмотрим функцию initRenderData(), которая настраивает переменную quadVAO:

В данном фрагменте кода мы сначала определяем набор вершин. При этом, стоит заметить, что точка (0; 0) является верхним левым углом прямоугольника. Это означает, что когда мы применим к прямоугольнику перенос или масштабирование, то оно будет выполняться относительного этого угла. Обычно, данный подход можно встретить в 2D-графических системах, где координаты позиции элемента привязаны к верхнему левому углу элемента.

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

Рендеринг

Теперь давайте разберем вопрос рендеринга спрайтов; мы задействуем шейдер рендеринга спрайтов, настраиваем матрицу модели и устанавливаем соответствующую uniform-переменную. Здесь важную роль играет порядок преобразований:

Когда вы будете размещать внутри сцены свои объекты, то рекомендуется первой применять операцию масштабирования, затем поворота и, наконец, переноса объекта. Поскольку умножение матриц происходит справа налево, то мы преобразуем матрицу в обратном порядке: переносим, поворачиваем, а затем масштабируем.

Преобразование поворота все еще может показаться вам немного пугающим. Из урока о трансформациях в OpenGL известно, что поворот всегда выполняется вокруг точки с координатами (0; 0). Поскольку в нашем случае координаты (0; 0) соответствуют верхнему левому углу прямоугольника, то все повороты будут происходить относительно этого угла. Такое расположение точки начала поворота может привести к нежелательным результатам. Поэтому необходимо переместить точку начала поворота в центр прямоугольника, чтобы он поворачивался вокруг центральной точки, а не вокруг своего верхнего левого угла. Мы решаем эту проблему, перемещая прямоугольник на половину своего размера таким образом, чтобы перед операцией поворота его центр имел координаты (0; 0).

Поскольку первым действием мы масштабируем прямоугольник, то должны учитывать размер спрайта при перемещении точки начала поворота в центр спрайта, поэтому мы производим умножение на вектор size спрайта. Как только преобразование поворота будет применено, мы перевернем предыдущее преобразование.

Комбинируя все эти преобразования, мы можем позиционировать, масштабировать и поворачивать каждый спрайт так, как нам нравится.

#Класс SpriteRenderer

Заголовочный файл — sprite_renderer.h:

Файл реализации — sprite_renderer.cpp:

Hello, спрайт

С созданием класса SpriteRenderer мы наконец-то получили возможность рендерить реальные изображения на экране! Давайте попробуем инициализировать какое-нибудь изображение, загрузив нашу любимую текстуру:

Код:

Затем, с помощью функции рендеринга, мы можем визуализировать наш любимый талисман, чтобы увидеть, всё ли работает так, как должно:

Здесь мы помещаем спрайт несколько ближе к центру экрана, причем его высота немного больше ширины спрайта. Мы также поворачиваем спрайт на 45 градусов и придаем ему зеленый цвет.

Если вы всё сделали правильно, то должны получить следующий результат:

  GitHub / Часть №3: Рендеринг спрайтов в игре «Breakout» на С++/OpenGL — Исходный код

Теперь, когда мы реализовали работающую систему рендеринга, можно использовать её с пользой на следующем уроке, в котором мы поработаем над созданием игровых уровней.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (4 оценок, среднее: 5,00 из 5)
Загрузка...

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *