Расширение EXT_texture_array.

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

texture array example

Рис 1. Пример организации текстурного атласа.

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

Последнее обстоятельство не позволяет использовать 3D-текстуры в качестве средства группировки отдельных текстур - мипмэпы отдельных слоев будут наползать друг на друга также приводя к появлению артефактов.

Идеальным решением была бы возможность объединения группы 2D-текстур в массив, получая при этом что-то вроде 3D-текстуры, но с независимыми мипмэпами для каждого слоя.

Именно такую возможность и дает расширение EXT_texture_array. Данное расширение позволяет группировать одно- и двухмерные текстуры одинакого размера и формата в массивы. Каждый такой массив является фактически одной текстурой (т.е. занимает ровно один текстурный блок).

Во многом такие текстурные массивы похожи на 2D- и 3D-текстуры, однако есть и принципиальные отличия.

Во-первых мипмэппинг осуществляется независимо для каждого слоя, поэтому их влияние друг на друга полностью исключается.

Во-вторых, компонента текстурных координат (t или r соответственно), используемая для задания слоя задает именно номер слоя, т.е. принимает значения от 0 до n-1, где n - число слоев.

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

Создание текстурного массива

Создание текстурных массивов полностью аналогично созданию 2D- и 3D-текстур, только в качестве типа текстур (target) используются GL_TEXTURE_1D_ARRAY_EXT и GL_TEXTURE_2D_ARRAY_EXT соответственно. Для задание данных текстур используются функции glTexImage2D и glTexImage3DEXT.

GLuint tex;

glGenTextures   ( 1, &tex );
glBindTexture   ( GL_TEXTURE_2D_ARRAY_EXT, tex );
glTexImage3DEXT ( GL_TEXTURE_2D_ARRAY_EXT, ... );

Ниже приводится пример функции, получающей на вход массив имен текстур (завершенный NULL'ом) и строящей по нему texture-array (Полностью пример можно скачать по ссылке в конце статьи).

unsigned    buildTextureArray ( const char * names [] )
{
    if ( names [0] == NULL || names [0][0] == '\0' )
        return 0;
        
    int         i;
    unsigned    id;
    Texture   * tex  = getTexture ( names [0] );    // load 1st texture
    int         count = 0;
    
    if ( tex == NULL )
    {
        delete tex;
        
        return 0;
    }
    
    while ( names [count] )                         // count # of textures
        count++;
        
    int     w    = tex -> getWidth         ();
    int     h    = tex -> getHeight        ();
    int     comp = tex -> getNumComponents ();
    int     fmt  = tex -> getFormat        ();
    size_t  sz   = tex -> getBytesPerLine  () * h;
    byte  * buf  = (byte *) malloc (  sz * count );
    
    memcpy ( buf, tex -> getData (), sz );
    delete tex;
    
    for ( i = 1; i < count; i++ )
    {
        Texture * t = getTexture ( names [i] );
        
        if ( t == NULL )
            break;
            
                                                    // check that all textures have same foramt/size
        if ( t -> getWidth () != w || t -> getHeight () != h || t -> getNumComponents () != comp || t -> getFormat () != fmt )
            break;

        memcpy ( buf + i*sz, t-> getData (), sz );
    
        delete t;
    }
    
    if ( i < count )                             // some error occured
    {
        free ( buf );
        
        return 0;
    }
    
    glGenTextures   ( 1, &id );
    glBindTexture   ( GL_TEXTURE_2D_ARRAY_EXT, id );
    glTexImage3DEXT ( GL_TEXTURE_2D_ARRAY_EXT, 0, comp, w, h, count, 0, fmt, GL_UNSIGNED_BYTE, buf );

    glTexParameteri ( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri ( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri ( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_S,     GL_REPEAT );
    glTexParameteri ( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_T,     GL_REPEAT );
    
    free ( buf );
    
    return id;
}

Обратите внимание, что данная функция проверяет на совпадение размеров и форматов всех текстур из списка. Фактически функция просто загружает все текстуры (как объекты класса Texture, строит новый массив данных и по нему создает соответствующий текстурный массив.

Доступ к текстурным массивам из шейдеров

Для использования текстурных массивов введены новые типы сэмплеров - sampler1DArray, sampler2DArray, sampler1DArrayShadow, sampler2DArrayShadow, isampler1DArray, usampler1DArray, isampler2DArray, usampler2DArray.

В начало шейдера следует вставить следующую директиву, сообщающую компилятору, что требуется поддержка расширение EXT_texture_array:

#extension GL_EXT_texture_aray: enable

Для чтения из текстурных массивов введены новые функции - texture1DArray, texture1DArrayLod, texture2DArray, texture2DArrayLod, shadow1DArray, shadow1DArrayLod, shadow2DArray и shadow2DArrayLod.

Обратите внимание, что при чтении из 1D-текстурного массива первая текстурная координата (s) задает координату внутри слоя (принимая значения из отрезка [0,1]), а вторая координата (t) содержит номер слоя, из которого будет осуществляться чтение (0,1,2,...).

Аналогично при чтении из 2D-текстурного массива первые две текстурные координаты (s,t) задают координаты внутри слоя, а третья текстурная координата (r) задает номер слоя.

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

#extension GL_EXT_texture_aray: enable

varying vec3 lt;
varying vec3 ht;
uniform sampler2DArray map;

void main (void)
{
    vec4    clr   = texture2DArray ( map, vec3 ( gl_TexCoord [0].xy, 0.0 ) );       // get color
    vec4    n     = texture2DArray ( map, vec3 ( gl_TexCoord [0].xy, 1.0 ) );       // get normal
    vec4    gloss = texture2DArray ( map, vec3 ( gl_TexCoord [0].xy, 2.0 ) );       // get gloss color
    const   vec4    specColor = vec4 ( 1 );

    vec3    nt   = normalize ( vec3 ( 0.0, 0.0, 1.0 ) + 2.0*n.xyz - 1.0 );
    vec3    l2   = normalize ( lt );
    vec3    h2   = normalize ( ht );
    float   diff = max ( dot ( nt, l2 ), 0.0 ) + 0.2;
    float   spec = pow ( max ( dot ( nt, h2 ), 0.0 ), 60.0 );

    gl_FragColor = diff * clr + spec * gloss;
}

Рендеринг в текстурный массив

Отдельный слоя двухмерного текстурного массива может выступать в качестве цели для рендеринга (render target) при использование расширения EXT_framebuffer_object.

Для поддержки этой возможности введена новая функция glFramebufferTextureLayerEXT.

void glFramebufferTextureLayerEXT ( GLenum target, GLenum attachment, GLuint textureId, int level, int layer );

Данная функция очень близка как по параметрам, так и по смыслу функции glFramebufferTexture3DEXT, только параметр layer, задает номер слоя (вместо параметра zOffset).

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

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