Использование библиотеки DevIL для загрузки и сохранения текстур в OpenGL

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

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

Библиотека DevIL (изначально она называлась OpenIL) как раз и предназначена для этого. DevIL является простой и кроссплатформенной библиотекой для чтения и записи изображений (текстур) из/в большое число различных графических форматов.

При этом сама библиотека организована по образу и подобию OpenGL (так что первоначальное название OpenIL отнюдь не случайно) - она представляет из себя набор функций и констант, разбитых на три модуля - IL, ILU и ILUT.

Собственно за чтение и запись текстур отвечает именно библиотека IL, библиотека ILU предоставляет ряд возможностей по обработке изображений, а библиотека ILUT служит для упрощения работы с IL для сторонних библиотек (таких как OpenGL).

Библиотека DevIL предоставляет сразу несколько способов загрузки текстур в OpenGL и сохранения скриншотов. В простейшем случае для этого достаточно вызова всего одной функции из ILUT.

Однако за счет непосредственной работы с модулем IL можно получить ряд дополнительных возможностей, поэтому мы сперва рассмотрим работу с изображениям в модуле IL, а уже потом перейдем к упрощенным вариантам из ILUT. В самом конце будет рассмотрена небольшая "обертка" для DevIL'а.

Подключение и инициализация DevIL

Для подключения DevIL Вам следует подключить заголовочные файлы DevIL (и указать путь к ним в настройках проекта), а также подключить библиотеки DevIL.lib, ILU.lib и ILUT.lib (для форточек).

Для Linux и Mac OS X проще всего установить библиотеку из исходников. Для этого скачайте последнюю версию исходников, разархивируйте их и выполните следующие команды:

./configure
make
sudo make install

При сборке проекта задайте подключение библиотек IL, ILU и ILUT.

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

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

if ( ilGetInteger ( IL_VERSION_NUM ) < IL_VERSION )
{
    fprintf ( stderr, "Incorrect devil.dll version\n" );

    return 0;
}

if ( iluGetInteger ( ILU_VERSION_NUM ) < ILU_VERSION )
{
    fprintf ( stderr, "Incorrect ilu.dll version\n" );

    return 0;
}

if ( ilutGetInteger ( ILUT_VERSION_NUM ) < ILUT_VERSION )
{
    fprintf ( stderr, "Incorrect ilut.dll version\n" );

    return 0;
}

После проверки версии следует произвести инициализацию каждого из модулей библиотеки DevIL:

ilInit   ();
iluInit  ();
ilutInit ();

Для нормальной работы с OpenGL (и, в том числе, нормальной поддержки сжатых текстур) следует произвести дополнительную настройку DevIL при помощи следующих команд:

ilutRenderer ( ILUT_OPENGL );
ilSetInteger ( IL_KEEP_DXTC_DATA, IL_TRUE );
ilutEnable   ( ILUT_GL_AUTODETECT_TEXTURE_TARGET );
ilutEnable   ( ILUT_OPENGL_CONV );
ilutEnable   ( ILUT_GL_USE_S3TC );

Удобно все это завернуть в небольшой модуль, предоставив к нему простой интерфейс для загрузки 2-3-мерных текстур и кубических текстурных карт с автоматической инициализацией библиотеки.

Работа с изображениями

Работа с изображениями в DevIL'е очень напоминает работу с различными объектами (текстурами, вершинными массивами, шейдерами и т.п.) в OpenGL.

Каждое изображение идентифицируется беззнаковым целым числом (типа ILuint), называемым его именем. Для работы с изображением необходимо сначала создать изображение, запросив для него идентификатор при помощи функции ilGenImages.

ILvoid ilGenImages ( ILsizei n, ILuint * ids );

Как и в аналогичных функциях OpenGL, первый параметр (n) задает сколько идентификаторов (изображений) нужно создать, а второй (ids) - куда их поместить.

Для уничтожения изображений служит функция ilDeleteImages:

ILvoid ilDeleteImages ( ILsizei n, const ILuint * ids );

После получения идентификатора изображения его можно сделать текущим при помощи команды ilBindImage:

ILvoid ilBindImage ( ILuint id );

После того как изображение (его идентификатор) создано и выбрано текущим его можно загрузить из файла при помощи одной из следующих команд:

ILboolean ilLoadImage ( char * fileName );
ILboolean ilLoad      ( ILenum type, char * fileName );
ILboolean ilLoadF     ( ILenum type, ILHANDLE file );
ILboolean ilLoadL     ( ILenum type, ILvoid * data, ILuint size );

Функция ilLoadImage загружает текстуру из файла с заданным именем (fileName) в текущее (bound) изображение и возвращает IL_TRUE при успехе и IL_FALSE при ошибке.

Функция ilLoad отличается от функции ilLoadImage тем, что в ней явно передается тип загружаемого файла (например IL_BMP, IL_TGA, IL_JPG). В случае, когда тип заранее не известен, то можно в качестве типа передать IL_TYPE_UNKNOWN и тогда данная функция ведет себя полностью аналогично ilLoadImage, т.е. сперва пытается определить тип по расширению файла, если это не получается - то по началу файла.

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

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

ILuint	id;

ilGenImages ( 1, &id );
ilBindImage ( id );
ilLoadImage ( "mypic.tga" );

Для проверки, не возникла ли какая-либо ошибка служит функция ilGetError, возвращающая код ошибки или IL_NO_ERROR, если все в порядке.

Обратите внимание, что DevIL поддерживает стек ошибок, так что новые ошибки не затирают старые, а просто добавляются в стек ошибок. Команда ilGetError снимает очередное значение с вершины стека и возвращает его.

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

char * iluErrorString ( ILenum error );

Для получения доступа к пиксельным данным изображения служит функция ilGetData:

ILubyte * ilGetData ();

Сохранение выбранного изображения также осуществляется крайне просто, для этого служат функции:

ILboolean ilSave      ( ILenum type, char * fileName );
ILboolean ilSaveImage ( char * fileName );

Если, при сохранении изображения в файл, DevIL обнаруживает, что файл с таким именем уже существует, то сохранение заканчивается неудачей - по умолчанию DevIL не перезаписывает файлы.

Для изменения этого поведения служит следующая команда:

ilEnable ( IL_FILE_OVERWRITE );

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

ILboolean ilutOglScreen   ();
ILboolean ilutOglScreniie ();

Первая из них сохраняет содержимое буфера кадра OpenGL в текущее изображение. После этого Вы сами можете его сохранить в файл с нужным именем и в необходимом формате.

Функция ilutOglScreenie не изменяя текущего изображения просто сохраняет содержимое буфера кадра в файле вида "screen%d.tga".

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

int  width  = ilGetInteger ( IL_IMAGE_WIDTH      );
int  height = ilGetInteger ( IL_IMAGE_HEIGHT     );
int  depth  = ilGetInteger ( IL_IMAGE_DEPTH      );
int  type   = ilGetInteger ( IL_IMAGE_TYPE       );
int  fmt    = ilGetInteger ( IL_IMAGE_FORMAT     );
int  dxtc   = ilGetInteger ( IL_DXTC_DATA_FORMAT );
bool comp   = (dxtc == IL_DXT1) || (dxtc == IL_DXT2) || (dxtc == IL_DXT3) || (dxtc == IL_DXT4) || (dxtc == IL_DXT5);

Первые три запроса возвращают высотку, ширину и глубину (глубина имеет смысл только для 3D-текстур, в остальных случаях возвращается единица) текущего изображения в текселах.

Следующие два запроса возвращают тип компонент изображения в котором данные выдаются командой ilGetData (GL_UNSIGNED_BYTE, GL_FLOAT и т.п.) и формат изображения (GL_RGB, GL_RGBA).

Следующий запрос возвращает используемый тип сжатия (для dds-текстур). Последний запрос определяет является ли данная текстура сжатой (т.е. нужно ли для ее загрузки использовать команды вида glCompressedTexImage*).

Простейшим способом загрузки текстуры в OpenGL является использование функции ilutOglLoadImage:

GLuint ilutOglLoadImage ( char * fileName );

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

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

Однако данная функция (пока) не умеет загружает 3D-текстуры в OpenGL, хотя сам DevIL такие изображения успешно загружает. Поэтому загрузка 3D-текстур по изображению DevIL'а должна осуществляться вручную.

Ниже приводится код из wrapper'а, осуществляющий загрузку 3D-текстуры.

unsigned	loadTexture3D ( const char * fileName, bool mipmap )
{
    ILuint texId;
    GLuint id;
 
    initDevIL   ();
    ilGenImages ( 1, &texId );
    ilBindImage ( texId );
 
    if ( !ilLoadImage ( fileName ) )
        return 0;
 
    int  width  = ilGetInteger ( IL_IMAGE_WIDTH  );
    int  height = ilGetInteger ( IL_IMAGE_HEIGHT );
    int  depth  = ilGetInteger ( IL_IMAGE_DEPTH  );
    int  type   = ilGetInteger ( IL_IMAGE_TYPE   );
    int  fmt    = ilGetInteger ( IL_IMAGE_FORMAT );
    int  dxtc   = ilGetInteger ( IL_DXTC_DATA_FORMAT );
    bool comp  = (dxtc == IL_DXT1) || (dxtc == IL_DXT2) || (dxtc == IL_DXT3) || (dxtc == IL_DXT4) || (dxtc == IL_DXT5);
 
    glGenTextures ( 1, &id );
    glBindTexture ( GL_TEXTURE_3D_EXT, id );

    if ( mipmap )
    {
        glTexParameteri ( GL_TEXTURE_3D_EXT, GL_GENERATE_MIPMAP_SGIS, GL_TRUE );
        glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_MIN_FILTER,   GL_LINEAR_MIPMAP_LINEAR );
    }
 
    glTexImage3DEXT ( GL_TEXTURE_3D_EXT, 0, fmt, width, height, depth, 0, fmt, type, ilGetData () );  
    glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_S,     GL_REPEAT );
    glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_T,     GL_REPEAT );
    glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_R,     GL_REPEAT );
    glBindTexture   ( GL_TEXTURE_3D_EXT, 0 );
 
    ilDeleteImages ( 1, &texId );

    return id;
}

Довольно удобно завернуть доступ к DevIL в небольшой набор функций, отвечающий за создание текстур заданных типов - 2D, 3D и кубические текстурные карты.

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

unsigned loadTexture2D   ( const char * fileName, bool mipmap );
unsigned loadTexture3D   ( const char * fileName, bool mipmap ) ;
unsigned loadTextureCube ( const char * fileName, bool mipmap );		
unsigned loadTextureCube ( const char ** files, const char * path, bool mipmap );

Обратите внимание, что загрузка кубической текстурной карты может производиться как из одного файла (dds), так и из 6 отельный файлов, при этом общий путь к ним передается отдельным параметром.

Расширение DevIL

Библиотека DevIL позволяет пользователям добавлять поддержку новых форматов текстур.

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

Для регистрации таких функций (для загрузки и сохранения в данном формате) служат следующие функции из модуля IL:

ILboolean ilRegisterLoad ( char * ext, IL_LOADPROC loadFunc );
ILboolean ilRegisterSave ( char * ext, IL_SAVEPROC saveFunc );

Каждая из них получает на вход расширение файла для данного формата и адрес функции для загрузки/сохранения изображения.

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

ILboolean loadFunction ( char * fileName )
{
    return IL_FALSE;
}

ILboolean saveFunction ( char * fileName )
{
    // Open file here

    fwrite ( ilGetData (), 1, ilGetInteger ( IL_IMAGE_SIZE_OF_DATA ), file );

    // Close file here

    return IL_TRUE;
}

Каждая из этих функций получает на вход имя файла и возвращает признак успешности операции. При этом операция загрузки/сохранения относится к текущему (bound) изображению.

Для загрузки уже готового изображения (как массива байт) в текущее изображение можно использовать следующую функцию.

ILboolean ilTexImage ( ILuint width, ILuint height, ILuint depth, ILubyte bpp, ILenum format, ILvoid * data );

По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows, Linux и Mac OS X.

Valid HTML 4.01 Transitional

Напиши мне
Используются технологии uCoz