28 сентября 2010

OpenGL: Текстуры

Привет. Это продолжения серии уроков про OpenGL(1,2). В этом небольшом уроку мы научимся накладывать текстуры. Это не сложно. Все что нам понадобиться, это воспользоваться несколькими функциями OpenGL.Мы же это уже умеем, правда?

Если быть абсолютно честным, то нам еще понадобится загружать заранее подготовленные текстуры. Вот так все страшно и сложно. Но вы же не боитесь?

Для начало нужно подготовить текстуры. Давайте возьмем две текстуры, одну с прозрачностью, другую нет. Зачем нам прозрачность? Для спрайтов. В 2d играх не все объекты квадратные. Нарисуем наши текстуры? Нет, их нарисую я.

Теперь поговорим о том как загрузить текстуру. Важно помнить что размер текстуры был кратный двум, а ширина и высота, должны быть равны. Зачем это надо? Это особенности OpenGL, просто запомните это.

Прежде чем работать с текстурами в OpenGL, их нужно включить.

glEnable(GL_TEXTURE_2D);

Непосредственно OpenGL не умеет загружать текстуры из файлов, даже самых простых. Это должен сделать кто-то другой, например SDL_image. Вот специальный метод из нашего класса:

void load_texture(std::string file, std::string name)
{
  using std::stringstream;
  using std::cerr;
  using std::endl;
  using std::runtime_error;

  GLuint texture; // Каждой текстуре должен быть присвоен номер.
  int COLOR_FLAG; // Это формат пикселей.
  SDL_Surface* img = IMG_Load(file.c_str()); // Это функция SDL_image
  if(!img)
    {
      stringstream err;
      err << "IMG_Load: " << IMG_GetError() << endl;
      throw runtime_error(err.str().c_str());
    }
  glGenTextures(1,&texture); // Создаем уникальный номер для текстуры.
  glBindTexture(GL_TEXTURE_2D, texture); // Выбираем текстуру
  /* Задаем параметры для данной текстуры. */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  /* В зависимости от типа изображения (для простоты мы пока
  используем только 24 и 32 битные текстуры). */
  if (img->format->BitsPerPixel == 24)
    COLOR_FLAG = GL_RGB; // Здесь пиксель состоит из трех байт.
  else
    COLOR_FLAG = GL_RGBA; // Тут из четырех.
  glTexImage2D(GL_TEXTURE_2D, // Это тип текстуры.
               0, // Нам пока сгодиться 0 (а вообще это уровень детализации).
               COLOR_FLAG, // Внутренний формат пикселей.
               img->w, img->h, // Ширина и высота.
               0, // Граница.
               COLOR_FLAG, // Формат пикселей используемой текстуры.
               GL_UNSIGNED_BYTE, // Тип формата данных загруженной текстуры.
               img->pixels); // указатель на область памяти с текстурой.
  /* Сохраняем номер текстуры для дальнейшего использования. */
  this->textures[name] = texture;
  SDL_FreeSurface(img); // Выделенную память можно(читай "нужно") отчистить.
}

Если какие-то функции вам не понятны, не отчаивайтесь, и не поленитесь заглянуть на сайт OpenGL и, возможно, на сайт SDL_image. Там есть документация, которую я вам очень советую почитать. Заглянув на сайт SDL тоже будет полезно.

Вернемся к текстурам.

Вот еще два метода нашего класса:

void use_texture(std::string name)
{
  if (this->textures.count(name))
    glBindTexture(GL_TEXTURE_2D, this->textures[name]); // Это мы уже видели.
}

void destroy_texture(std::string name)
{
  if (this->textures.count(name))
    {
      // Это функция удаляет текстуру.
      glDeleteTextures(GL_TEXTURE_2D, &this->textures[name]);
      this->textures.erase(name);
    }
}

Теперь давайте посмотрим как можно наложить текстуру на полигон (если вы помните, то мы уже использовали текстуры, когда делали экспорт из blender)

void draw_sprite(std::string name)
{
  this->use_texture(name);
  glBegin(GL_QUADS);
  {
    glTexCoord2f(0, -1); glVertex2d(  0.0,  256.0);
    glTexCoord2f(0,  0); glVertex2d(  0.0,    0.0);
    glTexCoord2f(1,  0); glVertex2d( 256.0,   0.0);
    glTexCoord2f(1, -1); glVertex2d( 256.0, 256.0);
  }
  glEnd();
}

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

На этом пожалуй все. Осталось только представить весь код. Собственно вот он:

#if 0
echo "compile $0 to sdlgl..."

g++ -o sdlgl $0 `pkg-config --libs gl glu sdl` \
`pkg-config --cflags sdl` -Wall -Os -s -lSDL_image

exit
#endif

#include <iostream>
#include <string>
#include <map>
#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <cmath>
#include "Game.hpp"

class MyGame : public Game
{
public:
  MyGame(int width=640, int height=480, int bpp=32) :
    Game(width, height, bpp)
  {
    this->deg = 0;
  }

  virtual void setup_opengl(int width, int height)
  {
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    gluOrtho2D(0, width, 0, height);
    glMatrixMode(GL_MODELVIEW);
    glClearColor(0.5, 0.2, 0.8, 1.0);

    // Включаем текстуры.
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
    // Включаем прозрачность.
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    this->load_texture("1.png", "one");
    this->load_texture("2.png", "two");
  }

  // Загрузка текстуры
  void load_texture(std::string file, std::string name)
  {
    using std::stringstream;
    using std::cerr;
    using std::endl;
    using std::runtime_error;

    GLuint texture;
    int COLOR_FLAG;
    SDL_Surface* img = IMG_Load(file.c_str());
    if(!img)
      {
        stringstream err;
        err << "IMG_Load: " << IMG_GetError() << endl;
        throw runtime_error(err.str().c_str());
      }
    glGenTextures(1,&texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    if (img->format->BitsPerPixel == 24)
      COLOR_FLAG = GL_RGB;
    else
      COLOR_FLAG = GL_RGBA;
    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 COLOR_FLAG,
                 img->w, img->h,
                 0,
                 COLOR_FLAG,
                 GL_UNSIGNED_BYTE,
                 img->pixels);
    this->textures[name] = texture;
    SDL_FreeSurface(img);
  }

  // Выбор текстуры.
  void use_texture(std::string name)
  {
    if (this->textures.count(name))
      glBindTexture(GL_TEXTURE_2D, this->textures[name]);
  }

  // Уничтожение текстуры.
  void destroy_texture(std::string name)
  {
    if (this->textures.count(name))
      {
        glDeleteTextures(GL_TEXTURE_2D, &this->textures[name]);
        this->textures.erase(name);
      }
  }

  virtual void draw(int dt)
  {
    this->deg = fmod((this->deg + (float)dt/25.0f), 360.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    this->draw_sprite("one");
    glLoadIdentity();
    glTranslatef(0, 0, 0.3);
    this->rotate(this->deg);
    this->draw_sprite("two");
    glFlush();
  }

  virtual void on_key_down(SDL_KeyboardEvent key)
  {
    if (key.keysym.sym == SDLK_ESCAPE)
      {
        this->quit();
      }
  }

  virtual ~MyGame()
  {
    std::map<std::string, GLuint>::iterator it;
    for (it=this->textures.begin(); it != this->textures.end(); it++)
      glDeleteTextures(GL_TEXTURE_2D, &(*it).second);
    IMG_Quit();
  }

protected:
  void draw_sprite(std::string name)
  {
    this->use_texture(name);
    glBegin(GL_QUADS);
    {
      glTexCoord2f(0, -1); glVertex2d(  0.0,  256.0);
      glTexCoord2f(0,  0); glVertex2d(  0.0,    0.0);
      glTexCoord2f(1,  0); glVertex2d( 256.0,   0.0);
      glTexCoord2f(1, -1); glVertex2d( 256.0, 256.0);
    }
    glEnd();
  }

  void rotate(float deg)
  {
    glTranslatef(128, 128, 0.0);
    glRotatef(-deg, 0, 0, 1);
    glTranslatef(-128, -128, 0.0);
  }

private:
  float deg;
  std::map<std::string, GLuint> textures;
};

int main(int argc, char *argv[])
{
  MyGame game(256, 256);
  game.set_caption("(Урок 3)OpenGL: Текстуры");
  game.run();
  return 0;
}

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

Засим все.