Текстурные форматы в OpenGL 3.3 и выше

Текстуры в OpenGL довольно быстро эволюционировали от простых одно- и двухмерных массивов из нескольких (от 1 до 4) однобайтовых компонент. Сейчас текстуры могут хранить в себе данных самых разных типов и различными способами. Соответственно у текстуры есть как формат, сообщающих об извлекаемых из нее данных, так и внутренний формат, задающий хранение пикселов в памяти GPU и внутреннее представление пикселов.

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

texture formats

Рис 1. Различные типы текстур в современном OpenGL.

В качестве допустимых (внешних) форматов текстур в современном OprnGL возможны следующие значения:

Внутренний формат уточняет что именно и как именно хранится для данной текстуры. Ниже рассматриваются различные типы текстур и допустимые Внутренний формат уточняет что именно и как именно хранится для данной текстуры. Ниже рассматриваются различные типы текстур и допустимые внутренние форматы для них.

Цветовые (color) форматы

Цветовые форматы обычно делятся на нормализованные, floating-point и целочисленные форматы. Также есть группа специальных форматов, не попадающих в эту классификацию.

Классические текстуры (например с внутренним форматом GL_RGBA8) являются пример так называемых нормализованных текстур - в памяти каждая компонента тексела хранится как целое число из заданного числа бит. При чтении значений из такой текстуры каждое такое целое число автоматически переводится в float из отрезка [0,1] или [-1,1] (для нормализованных текстур со знаком - signed normalized).

Нормализованные текстуры со знаком трактуют каждую компоненту как целое число со знаком (в отличии от обычных нормализованных, считающих целочисленные значения беззнаковыми) и переводят их в отрезок [-1,1]. Общая схема преобразования для любых нормализованных текстур - это линейная функция, переводящая минимальное целое число данного типа в 0 или -1, а максимальное целое число - в +1. OpenGL поддерживает нормализованные текстуры с 8 и 16 битами на компоненту.

Ненормализованные текстуры при чтении возвращают значения того типа, значения которого хранятся для каждого тексела без преобразования типов. При этом значение может быть как целочисленным (знаковым или беззнаковым, 8/16/32 битовым) или же floating-point (16 или 32 бита).

Для обозначения внутренних форматов, соответствующих подобным структурам используют довольно простые соглашение. Константа формата имеет вид GL_[components][bits][type]. Здесь через components обозначено число компонент в текстуре в виде R, RG, RGB или RGBA, через bits число бит на компоненту, а через type тип компоненты в виде I (знаковая целочисленная), UI (беззнаковая целочисленная) и F (floating-point). Если явно не задан тип, то текстура считается беззнаковой нормализованной (например GL_RGBA8), для знаковых нормализованных в конце добавляется _SNORM.

Таким образом, формат для целочисленной (со знаком) текстуры с четырьмя 16-битовыми компонентами имеет вид GL_RGBA16I, двух-компонентной floating-point текстуры с 32-битовыми значениями имеет вид GL_RG32F, однокомпонентной 8-битовой нормализованной текстуры со знаком имеет вид GL_R8_SNORM и т.д.

Кроме этих внутренних форматов текстур также существует еще несколько внутренних форматов, явно не вписывающихся в вышеприведенную схему. Прежде всего к ним относятся так называемые "пакованные" текстуры, когда число бит, отводимых на одну компоненту не кратно 8. Это GL_R3_G3_B2, GL_RGB5_A1 и GL_RGBA10_A2. В каждом из них тексел занимает целое число байт (один байт в первых двух случаях и четыре в последнем), однако сами компоненты являются наборами бит внутри этих байт (так для GL_R3_G3_B2 байт разбит на три группы - 3, 3 и 2 бита). Все эти текстуры являются нормализованными беззнаковыми.

Рис 2. Строение тексела для формата GL_RGBA10_A2.

Рис 3. Строение тексела для формата GL_R3_G3_B2.

Также есть упакованный floating-point-формат - GL_R11F_G11F_B10F. Для этого формата 32 бита, отводимые на тексел разбиты на три группы (11, 11 и 10 бит). Каждая группа представляет собой специальный вид floating-point числа - знаковый бит отсутствует вообще, под мантиссу отводится 6 или 5 бит, еще 5 бит отводится под экспоненту.

Рис 4. Строение компонент для формата GL_R11F_G11F_B10F.

Еще одним экзотическим floating-point форматом является GL_RGB9_E5. Это вариация floating-point формата, только здесь на три отдельно лежащих 9-битовых беззнаковых мантиссы приходится одна общая 5-битовая экспонента. При этом для перевода компонент используются следующие формулы (через r, g, b и e обозначены значения соответствующих полей из тексела как беззнаковые целые числа).

Еще два специальных формата (GL_SRGB8 GL_SRGB8_ALPHA8) отличаются тем, что работают не в обычном линейном цветом пространстве, а в пространстве sRGB, учитывающем нелинейность цветопередачи реальными устройствами. При чтении из такого формата автоматически происходит перевод цвета из пространства sRGB в линейное цветовое пространство по следующим формулам:

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

Последней категорией нестандартных цветовых форматов являются сжатые форматы. Для этих текстур непосредственно в памяти GPU хранятся сжатые данные и на ходу распаковываются. За счет сжатия происходит заметный выигрыш в объеме (и скорости обращения к) памяти. Однако для того, чтобы можно было по адресу тексела быстро получить его распакованное значения приходится использовать специальные блочные форматы. Обычно для таких текстур изображение разбивается на блоки 4x4 тексела и каждый такой блок сжимается в заранее определенное форматом число бит. За счет этого получается обеспечить быстрый доступ к любому текселу текстуры.

Стандартными внутренними форматами для сжатых текстур являются форматы, основанные на сжатии RGTC1/RGCT2 и S3TC (DXT1/DXT3/DXT5). Обратите внимание, что все они являются нормализованными и что в эти текстуры нельзя осуществлять рендеринг. Ниже приводится полный список этих форматов - GL_COMPRESSED_RED_RGTC1, GL_COMPRESSED_SIGNED_RED_RGTC1, GL_COMPRESSED_RG_RGTC2, GL_COMPRESSED_SIGNED_RG_RGTC2, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_RGB_S3TC_DXT3_EXT и GL_COMPRESSED_RGB_S3TC_DXT5_EXT.

Форматы для depth-текстур

Здесь существует всего два варианта - нормализованный (значения хранятся в памяти как беззнаковые целые числа с заданным числом бит и при чтении и записи приводятся к отрезку [0,1]) и 32-битовые floating-point.

Соответственно нормализованные внутренние форматы могут быть GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24 и GL_DEPTH_COMPONENT32. Для ненормализованных текстур есть всего один формат - GL_DEPTH_COMPONENT32F.

Комбинированные depth-stencil форматы

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

Поддерживаются всего два таких формата - нормализованный GL_DEPTH24_STENCIL8 и floating-point GL_DEPTH32F_STENCIL8.

Класс TexFormat

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

class   TexFormat
{
    GLenum  format;
    GLenum  internalFormat;
    int     numComponents;
    int     bitsPerChannel;
    
    TexFormat () {}
    
public:
    TexFormat ( GLenum theFormat, GLenum theIntFormat );
    
    GLenum  getFormat () const
    {
        return format;
    }
    
    GLenum  getInternalFormat () const
    {
        return internalFormat;
    }
    
    int getNumComponents () const
    {
        return numComponents;
    }
    
    bool    isOk () const
    {
        return numComponents > 0 && numComponents < 5;
    }
    
    bool    isPacked     () const;  // whether it is a bit-packet format
    bool    isCompressed () const;  // whether it is a compressed (DXTC or RGTC) format
    bool    isColor      () const;  // whether it is a color texture format
    bool    isColorFloat () const;  // whether it is a color texture floating-point format
    bool    isColorInt   () const;  // whether it is a color texture signed integer format
    bool    isColorUint  () const;  // whether it is a color texture unsigned integer format
	
    bool    isColorNormalized       () const;
    bool    isColorSignedNormalized () const;
        
    bool    isDepth () const
    {
        return format == GL_DEPTH_COMPONENT;
    }
        
    bool    isDepthStencil () const
    {
        return format == GL_DEPTH_STENCIL;
    }
        
    string  description () const;   // get text description of the format
    
                                    // create specific color formats
    static TexFormat intFormat   ( int theNumChannels, int theNumBits = 8 );
    static TexFormat uintFormat  ( int theNumChannels, int theNumBits = 8 );
    static TexFormat floatFormat ( int theNumChannels, int theNumBits = 16 );
    static TexFormat normFormat  ( int theNumChannels, int theNumBits = 8 );
    static TexFormat snormFormat ( int theNumChannels, int theNumBits = 8 );
    static TexFormat srgbFormat  ();
    static TexFormat srgbaFormat ();
    
                                    // create speficic depth formats
    static TexFormat depthNormalied ( int theNumBits );
    static TexFormat depthFloat     ();
    
                                    // create specific packed depth-stencil formats
    static TexFormat depthStenil24_8 ();
    static TexFormat depthStencilFloat ();
};

По этой ссылке можно скачать исходный код класса TexFormat к этой статье.