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

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

  Все выпуски  

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


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

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

Часть 1

Многие, увидев слово OpenGL, сразу начинают думать о трёхмерной графике. Оно и понятно, так как OpenGL как нельзя лучше подходит для этого. Для двухмерной графики существует несколько библиотек, которые более-менее хорошо справляются со своей задачей. Мы будем рассматривать библиотеку OpenGL в немного непривычной для нее роли - для отрисовки графики двухмерной игры. Для начала нужно рассмотреть плюсы и минусы этого подхода к библиотеке. Самым, наверное, важным плюсом является скорость. Люди, которые следят за листом рассылки SDL, должны были заметить, что почти в каждом ответе на вопрос из серии "как заставить SDL работать быстрее" содержатся рекомендации использовать OpenGL для быстрой графики. Соответственно, это и есть один из важнейших плюсов - скорость отрисовки. Помимо этого, средствами OpenGL можно быстро выполнять скалирование и повороты изображения (для SDL приходится использовать довольно медленный SDL_Rotozoom). Из минусов можно отметить довольно непривычный подход к рисованию и работе с изображением, но это легко решается правильной архитектурой приложения. Рисование при помощи OpenGL нельзя сочетать с обычными методами рисования в SDL.
Постепенно по шагам мы с вами будем пробовать создать хороший движок для 2D-игры. Но начнем, конечно, с самых основ - будем учиться рисовать двухмерный спрайт при помощи OpenGL.

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

  • Инициализация контекста OpenGL и установка нужного режима отображения
  • Подготовка текстуры
  • Собственно отрисовка спрайта
Существует несколько способов создания OpenGL контекста в Linux: расширение GLX, библиотека GLUT и библиотека SDL. Мы будем использовать последнюю, т.к. помимо очень простой инициализации, мы получаем много других адвансов. Уверен, что практически все из вас могут создать каркас для простого SDL приложения:
main()
{
        SDL_Init();
        SDL_SetVideoMode();

        while // главный цикл
        {
                while(event); // обработка событий
                draw();
        }
        SDL_Quit();
}
И так, что же мы должны добавить для создания OpenGL контекста. Прежде всего перед вызовом SDL_SetVideoMode() необходимо установить специальные атрибуты следующим образом:
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
Атрибуту SDL_GL_DOUBLEBUFFER мы присваеваем значение 1, что означает использовать двойной буфер, то есть, рисуем в невидимый буфер, а потом быстро переключаем буферы, рисуя его содержимое одним махом. Далее устанавливаем размер каждого цветового компонента минимум по 5 бит. Установки этих параметров необходимо выполнить до того, как будет вызвана SDL_SetVideoMode! Кстати, в качестве флагов для этой функции нужно всего лишь передать SDL_OPENGL. Остальные параметры должны быть выставлены при помощи SDL_GL_SetAttribute, что мы с вами и сделали. Получать указатель на экранный SDL_Surface нам совершенно необязательно, т.к. с ним просто нечего делать :).
Теперь необходимо установить состояние OpenGL для отображения двухмерной графики:
        glDisable (GL_DEPTH_TEST);
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity();
        gluOrtho2D(0, SCREEN_WIDTH, 0, SCREEN_HEIGHT);
SCREEN_HEIGHT и SCREEN_WIDTH - размеры экрана. Здесь мы сохраняем предыдущие значения (при помощи glPushMatrix заносим в стек) матрицы просмотра (проекционная матрица) - GL_PROJECTION. При помощи gluOrtho2D модифицируем ее для режима 2Д: двухмерная ортографическая проекция. Люди работающие с библиотекой SDL знают, что начало координат расположено в верхнем левом углу. Чтобы не нарушать эту традицию, нам нужно развернуть область просмотра на 180 градусов. Иначе, начало координат будут расположены в нижнем левом углу. Для этого мы делаем поворот на 180 градусов относительно нижнего левого угла и перемещаем экран вверх (после поворота наш экран "уполз" вниз за горизонт). Теперь нужно занести в стек матрицу преобразований объекта и обнулить ее (а вернее присвоить ей единичную матрицу):
        glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
        glTranslatef(0, -SCREEN_HEIGHT, 0);
        
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();
Теперь надо разобраться с альфа-блендингом. Обычная практика в 2D играх - использовать ColorKey (выбирается специальное значение цвета в качестве ключа и пиксели с таким цветом не рисуются, создавая эффект прозрачности). Мы будем использовать изображения в формате PNG с альфа-каналом. Это намного эффективней ColorKey: мы можем рисовать полупрозрачные области. Для этого в библиотеке OpenGL существует GL_BLEND. Этот режим нужно инициализировать с помощью glBlendFunc(). Первый параметр задает параметры блендинга для исходной текстуры, а второй - для экранного буфера. Если быть точнее, как будут смешаны пиксели текстуры с пикселями экранного буфера. В качестве первого параметра берем GL_SRC_ALPHA (использовать значение alpha из текстуры как есть), а в качестве второго - GL_ONE_MINUS_SRC_ALPHA (в OpenGL Alpha = 1 для полностью непрозрачного пикселя, поэтому от 1 отнимаем значение альфа пикселя). Будьте внимательны, использование GL_BLEND замедляет отрисовку, поэтому этот режим лучше отключать при рисовании заведомо непрозрачных текстур!
        glDisable(GL_ALPHA_TEST);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
На этом первый шаг (инициализация) можно считать законченым.
Далее нам предстоит создать текстуру для нашего спрайта. Начнем с неудобств... Самый главный недостаток в texture mapping - размеры обязательно должны быть степенью двойки (POT - power of two), а именно: 1, 2, 4, ..., 32, 64, 128, 256, 512, ... Выбирать размер стороны изображения больше чем 512 не рекомендуется, т.к. не все видеокарты поддерживают текстуры таких размеров. Размер изображения нашего спрайта соответствует степеням двойки за счет того, что содержит пустую область. Вторая особенность - текстурные координаты находятся в пределах от 0 до 1. Владельцы видеокарт от nVidia могут использовать расширение NV_texture_rectangle. В этом случае можно легко использовать NPOT (non power of two) текстуры, а текстурные координаты не нормируются (то есть от 0 до ширины/высоты текстуры). Но к сожалению, это расширение не всегда работает на других видеокартах, а также может работать медленнее обычных текстур. И так, наша функция load_gl_texture будет создавать текстуру из изображения на диске и возвращять ее идентификатор (номер). Порядок действий: загружаем с помощью SDL_image картинку, устанавливаем параметры текстуры, создаем и возвращаем.
        /* загружаем */
        SDL_Surface* surface = IMG_Load(filename);

        /* устанавливаем параметры и создаем */
        GLuint texture;
        glEnable(GL_TEXTURE_2D);
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);

        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_EDGE);
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_EDGE);

        /* в качестве указателя на массив пикселей как нельзя лучше
           подходит указатель на пиксели SDL_Surface */
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, surface->w, surface->h, 0,
                     GL_RGBA, GL_UNSIGNED_BYTE,  surface->pixels);
Уверен, что уважаемые читатели в состоянии посмотреть описание всех функций в этом примере кода. Это стандартный способ создания текстуры и смысла его подробно описывать просто нет. Не забываем удалять ненужный SDL_Surface, т.к. использовать стандартный SDL_Blit на экран невозможно и смысла хранить его в памяти просто нет.
Полученную текстуру теперь нужно рисовать на экране. Для этого нам необходимо хранить координаты спрайта (если мы его хотим двигать по экрану по нажатию на клавиатуру). Так как мы освободили SDL_Surface картинки, нужно хранить также и размеры спрайта:
        /* очищаем экран */
        glClear(GL_COLOR_BUFFER_BIT);

        /* указываем какую текстуру использовать */
        glBindTexture ( GL_TEXTURE_2D, texture_sprite );
        /* рисуем в заданной позиции с заданными размерами */
        glBegin ( GL_QUADS );
                glTexCoord2f(0, 0);
                glVertex2f(x, y);

                glTexCoord2f(1, 0);
                glVertex2f(x + sprite_width, y);

                glTexCoord2f(1, 1);
                glVertex2f(x + sprite_width, y + sprite_height);
                
                glTexCoord2f(0, 1);
                glVertex2f(x, y + sprite_height);
        glEnd();
Перед отрисовкой можно сделать небольшой трюк: размеры спрайта можно изменить, например удвоить. Заметим, что скалирование спрайта выполнится очень быстро! После отрисовки нужно выполнить команды glFlush и SDL_GL_SwapBuffers для переключения текущего буфера (место куда мы рисовали) на экран.

Не забудьте скачать исходный код для этой статьи. С ним вам будет гораздо легче разобраться. Предлагаем к этому уроку почитать кое-что из литературы:

Обсудить или задать вопрос по этой статье вы всегда можете на нашем форуме. До новых встреч!


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

В избранное