Отправляет email-рассылки с помощью сервиса Sendsay

Программирование игр в Linux

  Все выпуски  

Программирование игр в Linux


Информационный Канал Subscribe.Ru

Программирование игр в Linux: OpenGL для графики 2D-игры

Часть 2

Здравствуйте, уважаемые читатели. В прошлой части нашего исследования, мы с вами выяснили, что OpenGL довольно неплохо справляется с рисованием двухмерной графики. Правда, есть несколько мелких, но очень важных особенностей работы с этой библиотекой. За раз, конечно же, не получится охватить все из них. Но есть несколько критичных моментов, которые мы и рассмотрим. Наиболее важное ограничение - все текстуры в OpenGL должны иметь размеры степени двойки (POT: power of two). В принципе, это не так важно для 3D-графики, но в двухмерной игре очень сложно использовать спрайты только с pot-размерами. Существует несколько приемлимых решений, каждое со своими достоинствами и недостатками. Также попробуем "поиграть" с нашим спрайтом: зеркальное отражение картинки при помощи OpenGL делается очень просто и быстро. Не очень много для рассылки, но это только начало. Кто знает, что получится в итоге...

Давайте вспомним спрайт из прошлой части. Уверен, что многие из вас загрузили исходный код. Особо внимательные заметили, что размеры картинки 128x128 пикселей (что соответствует степени двойки). Но для этого пришлось увеличить прозрачную часть изображения. Для обработки этой ненужной части тратятся ресурсы - alpha blending очень дорогое удовольствие. И так, проблема: необходимо иметь возможность загружать npot-текстуры спрайтов (с произвольными размерами). Ниже я выделил несколько вариантов решения. Скорее всего этот список не полный и я очень надеюсь, что кто-то из вас поделится еще одним вариантом решения с небольшим примером кода.

  • Добавить к изображению дополнительную прозрачную часть, как в прошлом выпуске. В результате тратим вычислительные ресурсы. Размеры спрайта бывают очень важны и придется хранить их отдельно.
  • Масштабировать изображение зараннее до pot-размеров. При отрисовке мы ничего не теряем, но размеры спрайта надо хранить отдельно.
  • Создавать новый SDL_Surface размерами pot и копировать на него npot-картинку
  • Генерировать pot-текстуру для набора спрайтов (на одной текстуре картинки нескольких спрайтов)
  • Динамически масштабировать npot-изображение при загрузке.
Последний способ наиболее интересен. Он позволяет хранить тектуры любых размеров (в том числе и нужные размеры спрайтов). Для масштабирования OpenGL текстур существует функция gluScaleImage. Работать с ней вполне несложно:
  • Выделяем память нужного размера под новое pot-изображение
  • Делаем масштабирование загруженной npot-картинки и сохраняем результат в выделенной памяти
  • Создаем OpenGL текстуру из масштабированного изображения
Как вы помните, мы используем библиотеку SDL_Image для загрузки графических файлов. В результате получается экземпляр структуры SDL_Surface. Указатель на данные изображения хранится в переменной void* pixels. Это и есть исходное изображение для масштабирования. Чтобы выделить память под масштабированное изображение, нам нужно знать сколько памяти в итоге оно будет занимать. Узнать очень просто: произведение ширины, высоты и глубины цвета (в байтах) нового изображение. Важно всегда помнить, что при масштабировании всегда нужно увеличивать изображение. Если делать наоборот, сильно пострадает качество. Для получения pot-размеров масштабированного изображения воспользуемся функцией
int power_of_two(int input);
Нужно всего лишь передать ей в качестве параметра размер стороны картинки и она вернет нам ближайшую степень двойки, которую мы и будем считать размером стороны нового изображения. Функция имеет очень простую реализацию, которую вы посмотрите, загрузив исходный код для этого выпуска. И так, все готово для загрузчика npot-картинок:
        /* загружаем картинку */
        SDL_Surface* surface = IMG_Load(filename);

        /* получаем глубину цвета в байтах */
        int bpp = surface->format->BytesPerPixel;

        /* вычисляем стороны нового изображения */
        int pot_w = power_of_two(surface->w);
        int pot_h = power_of_two(surface->h);

        /* вычисляем размеры и выделяем память для нового изображения */
        int size = bpp * pot_w * pot_h;
        void* pixels = malloc(size);

        /* при обращении напрямую к pixels нужно поставить блокировку */
        SDL_LockSurface(surface);
        
        /* делаем масштабированную копию */
        gluScaleImage(GL_RGBA,
                      surface->w,       /* ширина исходной картинки (npot) */
                      surface->h,       /* высота исходной картинки (npot) */
                      GL_UNSIGNED_BYTE,
                      surface->pixels,  /* исходная картинка (npot) */
                      pot_w,            /* ширина новой картинки (pot) */
                      pot_h,            /* высота новой картинки (pot) */
                      GL_UNSIGNED_BYTE,
                      pixels);          /* новая pot-картинка */

        SDL_UnlockSurface(surface);

        /* далее код создания текстуры из первой части... */
Конечно, код приведенный здесь немного упрощен, т.к. нет смысла дублировать исходный код к уроку. В программе производится проверка - имеет ли текстура npot размеры или нет: if( pot_w == surface->w && pot_h == surface->h ). Масштабирование делается в том случае, если текстура npot, что позволяет сэкономить время загрузки.

Теперь, как я обещал, пару слов о преобразовании спрайта, а конкретнее - его зеркальное отражение. Прежде всего необходимо понять как работают текстурные координаты в OpenGL. Рассмотрим схему нашего спрайта:

 (0,0)     (1,0)
   A---------B
   |         |
   |         |
   |         |
   D---------C
 (0,1)     (1,1)
Мы рисуем его при помощи GL_QUAD - прямоугольник, на который мы накладываем текстуру. Текстурные координаты начинают отсчитывать с точки А, тоесть, A(0, 0). Текстурные координаты в OpenGL нормированы: тоесть приводятся к длине от 0 до 1. Оставшиеся точки имеют координаты: B(1, 0), C(1, 1), D(0, 1). Спрайт рисуется внутри блока glBegin(GL_QUADS) и glEnd() заданием точек в порядке: A, B, C, D. Если для текстурных координат нам необходимо подставлять нормированные значения ширины и высоты, то для координат вершин нужно использовать обычные ненормированные значения (т.е. если спрайт размерами 100x200 то мы и должны использовать эти значения в качестве ширины и высоты). Рассмотрим код рисования подробней:
        /* нарисовать спрайт в позиции (X,Y) */
        glBegin ( GL_QUADS );
                /* точка A */
                glTexCoord2f(0, 0);
                glVertex2f(X, Y);

                /* точка B */
                glTexCoord2f(1, 0);
                glVertex2f(X+W, Y);

                /* точка C */
                glTexCoord2f(1, 1);
                glVertex2f(X+W, Y+H);
                
                /* точка D */
                glTexCoord2f(0, 1);
                glVertex2f(X, Y+H);
        glEnd();
Из приведенного кода видно как используются текстурные координаты для соответствующих вершин. Переменные X и Y задают место положения спрайта на экране, а в переменных W и H хранятся размеры спрайта (соответственно ширина и высота). Теперь собственно наш трюк с отражением текстуры. Если поменять текстурные координаты для точек A<->D и B<->C, то наша текстура будет отражена сверху вниз. Для горизонтального отражения нужно всего лишь поменять местами текстурные координаты для точек A<->B и D<->C. Таким образом получается:
   (0,1)     (1,1)             (1,0)     (0,0)
     A---------B                 A---------B
     |         |                 |         |
     |         |                 |         |
     |         |                 |         |
     D---------C                 D---------C
   (0,0)     (1,0)             (1,1)     (0,1)
1. Отражение сверху-вниз    2. Отражение слева-направо
При желании, можете поэкспериментировать с назначением текстурных координат разным вершинам. Таким нехитрым способом можно также вращать спрайт с шагом 90 градусов по часовой или против часовой стрелки. Нужно заметить, что манипуляции с текстурными координатами практически никак не отражаются на скорости отрисовки! Для того, чтобы получить похожий эффект в SDL, требуется выполнять попиксельное преобразование изображения.

Обязательно загрузите исходный код для этого выпуска. Перед тем как пожелать вам удачи, я хотел поблагодарить людей, которые помогают мне выпускать рассылку: Aerton и Keltar, большое вам спасибо. Также хотел сообщить, что вот уже несколько месяцев у нас есть свой канал в IRC, где мы можем общаться с друг другом. Канал находится на серверах RusNet: irc.rinet.ru, irc.primorye.ru, irc.lucky.net и другие. Нужно выбрать порт 6669 и затем зайти на канал #plg. Те, кто не пробовал использовать IRC, могут задать вопросы на форуме.
Ну а теперь, желаю вам удачи!


http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.soft.prog.plg
Отписаться

В избранное