Расширения EXT_draw_instanced/ARB_draw_instanced, EXT_texture_buffer_object и ARB_instanced_arrays.

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

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

Кроме того, стандартные ограничения на максимальный размер 1D- и 2D-текстур тоже мешают и вынуждают переходить к текстурам большей размерности и менять схему индексации элементов.

Именно для этих случаев и служит расширение EXT_texture_buffer_object. Оно вводит новый тип текстур - текстурные буфера (texture buffer objects).

Такая текстура - это фактически просто одномерный массив (компонент заданного формата) с практически не ограниченным размером, для индексации которого используются целые числа. Для этих текстур нет никаких режимов отсечения текстурных координат (texture clamping) и фильтрации.

Кроме того в качестве источника данных для такой текстуры используется вершинный буфер (VBO) специального типа GL_TEXTURE_BUFFER_EXT. Это позволяет использовать целый ряд функций для задания или изменения содержимого вершинного буфера (таких как glMapBuffer) для задания/изменения данной текстуры, что гораздо удобнее традиционных текстурных способов.

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

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

GLenum      textureId;          // bufferred texture id
GLenum      texBuffer;          // VBO with data for texture

void    createTbo ( const void * data, unsigned size )
{
    buildData       ();
    
    glGenBuffersARB ( 1, &texBuffer );
    glBindBufferARB ( GL_TEXTURE_BUFFER_EXT, texBuffer );
    glBufferDataARB ( GL_TEXTURE_BUFFER_EXT, size, data, GL_STREAM_DRAW_ARB );
    
    glGenTextures   ( 1, &textureId );
    glBindTexture   ( GL_TEXTURE_BUFFER_EXT, textureId );
    glPixelStorei   ( GL_UNPACK_ALIGNMENT, 1 );                         // set 1-byte alignment

    glTexBufferEXT  ( GL_TEXTURE_BUFFER_EXT, GL_RGBA32F_ARB, texBuffer );
    
    glBindBufferARB ( GL_TEXTURE_BUFFER_EXT, 0 );
    glBindTexture   ( GL_TEXTURE_BUFFER_EXT, 0 );
}

При этом для работы с такими текстурами в шейдерах введены новые типы сэмплеров - samplerBuffer, isamplerBuffer и usamplerBuffer. Для чтения из таких текстур служит функция textureFetchBuffer.

Т. о. текстура типа GL_TEXTURE_BUFFER_EXT представляет собой простой и удобный способ создания, работы и модификации больших массивов однородных данных и для их использования в шейдерах.

Расширение EXT_draw_instanced/ARB_draw_instanced.

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

Понятно что в OpenGL все эти объекты можно легко вывести по одному за раз. Однако с точки зрения эффективности было бы гораздо лучше, если бы их все можно было вывести всего одним вызовом, используя при этом какой-либо механизм для задания индивидуальных отличий между объектами.

Именно такая возможность и называется geometric instancing (instancing). При этом если для OpenGL такая возможность не является "больным" местом, то для M$ D3D поддержка instancing-а жизненно важна, поскольку там вызов DrawIndexedPrimitive очень дорог и количество ее вызов за кадр желательно сделать как можно ниже.

Собственно именно поэтому в D3D 9 и был введен instancing. С OpenGL ситуация получилась иначе - с одной стороны такой острой необходимости в instancing'е нет, но с другой иметь подобную возможность было бы довольно удобно.

Сначала на сайте developer.nvidia.com появилась статья GLSL Pseudo-Instancing, показывающая как можно реализовать упрощенный instancing средствами OpenGL.

Несколько позже появилось экспериментальное расширение NVX_instanced_arrays. Это расширение было полным аналогом соответствующего вызова в D3D9, однако дальше экспериментов дело не пошло - оказалось, что прироста производительности оно почти не дает, а GPU серии GeForce 8xxx предоставляли гораздо большие возможности для geometric instancing'а.

Поэтому расширение NVX_instanced_arrays больше не поддерживается, а вместо него появилось новое, гораздо более гибкое и мощное расширение EXT_draw_instanced.

Данное расширение вводит следующие две функции:

void glDrawArraysInstancedEXT   ( GLenum mode, int first, GLsizei count, GLsizei primCount );
void glDrawElementsInstancedEXT ( GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei primCount );

Каждая из этих функций эквивалентна primCount вызовам функций glDrawArrays/glDrawElements. Для того, чтобы можно было отличать отдельные объекты, в вершинном шейдере вводится новая целочисленная (32-битовая) переменная gl_InstanceID, содержащая номер выводимого объекта (от 0 до primCount-1). Для обычных (не instanced вызовов значение gl_InstanceID равно нулю).

Таким образом вся конфигурация отдельных объектов осуществляется в вершинном шейдере на основе переменной gl_InstanceID.

Ниже приводится вершинный шейдер для простейшего варианта - по номеру объекта их текстурного буфера (текстуры типа texture buffer object) извлекается смещение данного объекта и прибавляется к значению gl_Vertex.

uniform samplerBuffer texBuf;

void main(void)
{
    vec4 instData = texelFetchBuffer ( texBuf, gl_InstanceID );
    vec4 pos      = gl_Vertex + instData;

    gl_Position     = gl_ModelViewProjectionMatrix * pos;
    gl_TexCoord [0] = gl_MultiTexCoord0;
}

За счет применения такого шейдера можно легко за один вызов вывести сразу массив одинаковых объектов, отличающихся только смещением. Ниже приводится исходный кода на С++ программы, использующей данный шейдер для вывода массива 8*8 торов (полностью весь исходный код можно скачать по ссылке в конце статьи).

#include    "libExt.h"

#include    <glut.h>
#include    <stdio.h>
#include    <stdlib.h>
#include    <assert.h>

#include    "libTexture.h"
#include    "Vector3D.h"
#include    "Vector4D.h"
#include    "GlslProgram.h"
#include    "Data.h"
#include    "utils.h"
#include    "Torus.h"

#define NUM_INSTANCES   64

unsigned    decalMap;
GLenum      textureId;          // bufferred texture id
GLenum      texBuffer;          // VBO with data for texture

GlslProgram program;
float     * data = NULL;
int         size = sizeof ( float ) * 4 * NUM_INSTANCES;

Torus       torus ( 1, 3, 30, 30 );
Vector3D    eye   ( 7, 5, 7 );          // camera position
Vector3D    rot   ( 0, 0, 0 );
int         mouseOldX = 0;
int         mouseOldY = 0;


void    buildData ()
{
    data = new float [size / sizeof ( float )];
    
    for ( int i = 0; i < NUM_INSTANCES; i++ )
    {
        data [4*i]   = ((i % 8) - 4) * 8;
        data [4*i+1] = ((i / 8) - 4) * 8;
        data [4*i+2] = 0;
        data [4*i+3] = 0;
    }
}

void    createTbo ()
{
    buildData       ();
    
    glGenBuffersARB ( 1, &texBuffer );
    glBindBufferARB ( GL_TEXTURE_BUFFER_EXT, texBuffer );
    glBufferDataARB ( GL_TEXTURE_BUFFER_EXT, size, data, GL_STREAM_DRAW_ARB );
    
    glGenTextures   ( 1, &textureId );
    glBindTexture   ( GL_TEXTURE_BUFFER_EXT, textureId );
    glPixelStorei   ( GL_UNPACK_ALIGNMENT, 1 );                         // set 1-byte alignment

    glTexBufferEXT  ( GL_TEXTURE_BUFFER_EXT, GL_RGBA32F_ARB, texBuffer );
    
    glBindBufferARB ( GL_TEXTURE_BUFFER_EXT, 0 );
    glBindTexture   ( GL_TEXTURE_BUFFER_EXT, 0 );
}

void display ()
{
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode ( GL_MODELVIEW );
    glPushMatrix ();
    glScalef     ( 0.3, 0.3, 0.3 );
    glRotatef    ( rot.x, 1, 0, 0 );
    glRotatef    ( rot.y, 0, 1, 0 );
    glRotatef    ( rot.z, 0, 0, 1 );

    glActiveTextureARB ( GL_TEXTURE0_ARB );
    glBindTexture      ( GL_TEXTURE_2D, decalMap );
    glActiveTextureARB ( GL_TEXTURE1_ARB );
    glBindTexture      ( GL_TEXTURE_BUFFER_EXT, textureId );
    
    program.bind   ();
    
    torus.preDraw  ();

    glDrawElementsInstancedEXT  ( GL_TRIANGLES, 3*torus.getNumFaces (), GL_UNSIGNED_INT, 0, NUM_INSTANCES );
    
    torus.postDraw ();
    program.unbind ();

    glPopMatrix ();

    glutSwapBuffers ();
}

void reshape ( int w, int h )
{
   glViewport     ( 0, 0, (GLsizei)w, (GLsizei)h );
   glMatrixMode   ( GL_PROJECTION );
   glLoadIdentity ();
   gluPerspective ( 60.0, (GLfloat)w/(GLfloat)h, 1.0, 60.0 );
   glMatrixMode   ( GL_MODELVIEW );
   glLoadIdentity ();
   gluLookAt      ( eye.x, eye.y, eye.z,    // eye
                    0, 0, 0,                // center
                    0.0, 0.0, 1.0 );        // up
}

void key ( unsigned char key, int x, int y )
{
    if ( key == 27 || key == 'q' || key == 'Q' )        // quit requested
        exit ( 0 );
}

void motion ( int x, int y )
{
    rot.y -= ((mouseOldY - y) * 180.0f) / 400.0f;
    rot.x -= ((mouseOldX - x) * 180.0f) / 400.0f;
    rot.z  = 0;

    if ( rot.z > 360 )
        rot.z -= 360;

    if ( rot.z < -360 )
        rot.z += 360;

    if ( rot.y > 360 )
        rot.y -= 360;

    if ( rot.y < -360 )
        rot.y += 360;

    mouseOldX = x;
    mouseOldY = y;

    glutPostRedisplay ();
}

void mouse ( int button, int state, int x, int y )
{
    if ( state == GLUT_DOWN )
    {
        mouseOldX = x;
        mouseOldY = y;
    }
}

int main ( int argc, char * argv [] )
{
                                // initialize glut
    glutInit            ( &argc, argv );
    glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL );
    glutInitWindowSize  ( 512, 512 );


                                // create window
    glutCreateWindow ( "OpenGL textures buffer object & draw instanced demo" );

                                // register handlers
    glutDisplayFunc  ( display );
    glutReshapeFunc  ( reshape );
    glutKeyboardFunc ( key     );
    glutMouseFunc    ( mouse   );
    glutMotionFunc   ( motion  );

    init           ();
    initExtensions ();

    assertExtensionsSupported ( "GL_EXT_texture_buffer_object GL_EXT_draw_instanced" );

    createTbo ();
    
    decalMap = createTexture2D ( true, "../../Textures/oak.bmp" );
    
    if ( !program.loadShaders ( "program-1.vsh", "program-1.fsh" ) )
    {
        printf ( "Error loading shaders:\n%s\n", program.getLog ().c_str () );

        return 3;
    }
    
    program.bind       ();
    program.setTexture ( "texBuf",   1 );
    program.setTexture ( "decalMap", 0 );
    program.unbind     ();

    torus.setupBuffers ();                  // create VBO's
    
    glutMainLoop ();
    
    return 0;
}

Можно слегка изменить вершинный шейдер - теперь текстура будет содержать в себе не только смещение (по x и y), но и два угла поворота, а в сам шейдер добавим в качестве uniform-переменной время, что позволит нам получить вращение каждого отдельного объекта вокруг своего центра.

uniform samplerBuffer texBuf;
uniform float         time;

void main(void)
{
    vec4    instData = texelFetchBuffer ( texBuf, gl_InstanceID );
    vec4    disp = vec4 ( instData.xy, 0.0, 0.0 );
    float   cphi = cos ( instData.z + time );
    float   sphi = sin ( instData.z + time );
    float   cpsi = cos ( instData.w + time );
    float   spsi = sin ( instData.w + time );
    mat4    r1   = mat4 ( cphi, -sphi, 0, 0,
                          sphi, cphi,  0, 0,
                          0,    0,     1, 0,
                          0,    0,     0, 1 );
                          
    mat4    r2   = mat4 ( 1,    0,     0, 0,
                          0, cpsi, -spsi, 0,
                          0, spsi, cpsi,  0,
                          0,    0,     0, 1 );
    vec4    pos  = r1*r2*gl_Vertex + disp;
    
    gl_Position     = gl_ModelViewProjectionMatrix * pos;
    gl_TexCoord [0] = gl_MultiTexCoord0;
}

Обратите внимание, что смещение прибавляется только после применение матриц поворота, так как поворот осуществляется вокруг центра объекта.

Также geometric instancing можно с успехом использовать и для вывода более сложным моделей, например md3. Если мы хотим для этой цели использовать библиотеку libMesh, но нам потребуется внести в нее небольшое изменение - вынести всю настройку для вывода и после вывода в отдельные методы, как бы "обрамляющие" вызов glDrawElements.

void Mesh :: render ()
{
    preRender ();
                                                // request draw
    glDrawElements  ( GL_TRIANGLES, 3*numFaces, GL_UNSIGNED_INT, 0 );

    postRender ();
}

void Mesh :: preRender ()
{
                                                // save state
    glPushClientAttrib  ( GL_CLIENT_VERTEX_ARRAY_BIT );

                                                // setup vertex buffer
    glBindBufferARB     ( GL_ARRAY_BUFFER_ARB, vertexBuffer );

                                                // go through the list of mappings
    for ( list <pair <int, int> > :: iterator it = mapping.begin (); it != mapping.end (); ++it )
    {
        int from = it -> first;
        int to   = it -> second;

        if ( to == tagVertex )
        {
            glEnableClientState ( GL_VERTEX_ARRAY );
            glVertexPointer     ( 4, GL_FLOAT, vertexStride, (void *) offsets [from] );
        }
        else
        if ( to == tagNormal )
        {
            glEnableClientState ( GL_NORMAL_ARRAY );
            glNormalPointer     ( GL_FLOAT, vertexStride, (void *) offsets [from] );
        }
        else
        if ( to == tagColor )
        {
            glEnableClientState ( GL_COLOR_ARRAY );
            glColorPointer      ( 4, GL_FLOAT, vertexStride, (void *) offsets [from] );
        }
        else
        if ( to >= tagTex0 && to <= tagTex7 )
        {
            glClientActiveTextureARB ( GL_TEXTURE0_ARB + to - tagTex0 );
            glEnableClientState      ( GL_TEXTURE_COORD_ARRAY );

            if ( from == tagTexCoord )
                glTexCoordPointer ( 2, GL_FLOAT, vertexStride, (void *) offsets [tagTexCoord] );
            else
                glTexCoordPointer ( 3, GL_FLOAT, vertexStride, (void *) offsets [from] );
        }
    }
                                                // setup index buffer
    glBindBufferARB ( GL_ELEMENT_ARRAY_BUFFER_ARB, indexBuffer );

    material.bind ();
}

void Mesh :: postRender ()
{
                                                // unbind array buffer
    glBindBufferARB ( GL_ARRAY_BUFFER_ARB,         0 );
    glBindBufferARB ( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );

    glPopClientAttrib ();
}

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

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

Поэтому если мы хотим использовать geometric instancing, то нам необходимо отделить информацию о положении всей группы объектов (т.е. модельновидовую матрицу) от информации о связи частей одного объекта между собой. Можно поместить эту информацию (матрицу) в матрицу преобразования одного из текстурных блоков, например блока номер 7.

Вместо переписывания MeshNode::render для использования geometric instancing, используем для этого паттерн Visitor, поддержка которого уже заложена в класс MeshNode. Для этого введем новый класс DIVisitor, реализация которого приводится ниже.

#ifdef	_WIN32
    #include <windows.h>
#endif

#include "libExt.h"
#include "di-visitor.h"
#include "Mesh.h"
#include "MeshNode.h"

bool DIVisitor :: visit ( MeshNode * node )
{
    Mesh * mesh = node -> getMesh ();

    if ( mesh != NULL )
    {
        mesh -> preRender  ();
		
        glDrawElementsInstancedEXT  ( GL_TRIANGLES, 3 * mesh -> getNumFaces (), GL_UNSIGNED_INT, 0, numInstances );
		
        mesh -> postRender ();
    }

    return true;
}

bool DIVisitor :: visitLink ( MeshNode :: MeshLink * link )
{
    float m [16];
                                          // create appropriate transform matrix
    link -> matr.getHomMatrix ( m, link -> offset );

    glActiveTextureARB ( GL_TEXTURE7_ARB );
    glMatrixMode       ( GL_TEXTURE );
    glPushMatrix       ();
    glMultMatrixf      ( m );

    if ( link -> node != NULL )
        link -> node -> visit ( *this );

    glActiveTextureARB ( GL_TEXTURE7_ARB );
    glMatrixMode       ( GL_TEXTURE );
    glPopMatrix        ();
    glMatrixMode       ( GL_MODELVIEW );
	
    return true;
}

Ниже приводится соответствующий вершинный шейдер, использующий матрицу gl_Texture [7] для преобразования отдельных частей модели.

uniform samplerBuffer texBuf;

void main(void)
{
    vec4 instData = texelFetchBuffer ( texBuf, gl_InstanceID );
    vec4 pos      = gl_TextureMatrix [7] * gl_Vertex;

    pos.yz = pos.zy;        // swap coordinates -> Quake III Arena uses different coordinate system
    
    gl_Position     = gl_ModelViewProjectionMatrix * (pos + instData);
    gl_TexCoord [0] = gl_MultiTexCoord0;
}

На следующем листинге приведен код программ на С++, осуществляющий вывод матрицы md3-моделей описанным выше способом.

#include    "libExt.h"

#include    <glut.h>
#include    <stdio.h>
#include    <stdlib.h>
#include    <assert.h>

#include    "libTexture.h"
#include    "Vector3D.h"
#include    "Vector4D.h"
#include    "GlslProgram.h"
#include    "Mesh.h"
#include    "MeshUtils.h"
#include    "MeshNode.h"
#include    "Md3Loader.h"
#include    "Data.h"
#include    "utils.h"
#include    "di-visitor.h"

#define NUM_INSTANCES   64

unsigned    decalMap;
GLenum      textureId;          // bufferred texture id
GLenum      texBuffer;          // VBO with data for texture

GlslProgram program;
MeshNode  * root;
Mesh      * mesh;
float     * data = NULL;
int         size = sizeof ( float ) * 4 * NUM_INSTANCES;

Vector3D    eye   ( 7, 5, 7 );          // camera position
Vector3D    rot   ( 0, 0, 0 );
int         mouseOldX = 0;
int         mouseOldY = 0;

void    buildData ()
{
    data = new float [size / sizeof ( float )];
    
    for ( int i = 0; i < NUM_INSTANCES; i++ )
    {
        data [4*i]   = ((i % 8) - 4) * 70;
        data [4*i+1] = ((i / 8) - 4) * 70;
        data [4*i+2] = 0;
        data [4*i+3] = 0;
    }
}

void    createTbo ()
{
    buildData       ();
    
    glGenBuffersARB ( 1, &texBuffer );
    glBindBufferARB ( GL_TEXTURE_BUFFER_EXT, texBuffer );
    glBufferDataARB ( GL_TEXTURE_BUFFER_EXT, size, data, GL_STREAM_DRAW_ARB );
    
    glGenTextures   ( 1, &textureId );
    glBindTexture   ( GL_TEXTURE_BUFFER_EXT, textureId );
    glPixelStorei   ( GL_UNPACK_ALIGNMENT, 1 );                         // set 1-byte alignment

    glTexBufferEXT  ( GL_TEXTURE_BUFFER_EXT, GL_RGBA32F_ARB, texBuffer );
    
    glBindBufferARB ( GL_TEXTURE_BUFFER_EXT, 0 );
    glBindTexture   ( GL_TEXTURE_BUFFER_EXT, 0 );
}

void    prepareNode ( MeshNode * node )
{
    for ( MeshNode :: Links :: const_iterator it = node -> begin (); it != node -> end (); ++it )
    {
        MeshNode * n = (*it) -> node;

        if ( n == NULL )
            continue;

        prepareNode ( n );

        Mesh * mesh = n -> getMesh ();

        if ( mesh == NULL )
            continue;

        mesh -> createBuffers ();
        mesh -> getMaterial   ().diffuse.bindToUnit ( 0 );
        mesh -> getMaterial   ().diffuse.load       ();

        mesh -> addCoordAssignment ( Mesh :: tagVertex,   Mesh :: tagVertex );
        mesh -> addCoordAssignment ( Mesh :: tagTexCoord, Mesh :: tagTex0   );
    }
}

void display ()
{
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode ( GL_TEXTURE );
    glLoadIdentity ();
    glMatrixMode ( GL_MODELVIEW );
    glPushMatrix ();
    glScalef     ( 0.06, 0.06, 0.06 );
    glRotatef    ( rot.x, 1, 0, 0 );
    glRotatef    ( rot.y, 0, 1, 0 );
    glRotatef    ( rot.z, 0, 0, 1 );
    
    glActiveTextureARB ( GL_TEXTURE1_ARB );
    glBindTexture      ( GL_TEXTURE_BUFFER_EXT, textureId );
    
    program.bind   ();
    
    DIVisitor   visitor ( NUM_INSTANCES );
    
    root -> visit ( visitor );
    
    program.unbind ();

    glMatrixMode ( GL_MODELVIEW );
    glPopMatrix  ();

    glutSwapBuffers ();
}

void reshape ( int w, int h )
{
   glViewport     ( 0, 0, (GLsizei)w, (GLsizei)h );
   glMatrixMode   ( GL_PROJECTION );
   glLoadIdentity ();
   gluPerspective ( 60.0, (GLfloat)w/(GLfloat)h, 1.0, 60.0 );
   glMatrixMode   ( GL_MODELVIEW );
   glLoadIdentity ();
   gluLookAt      ( eye.x, eye.y, eye.z,    // eye
                    0, 0, 0,                // center
                    0.0, 0.0, 1.0 );        // up
}

void key ( unsigned char key, int x, int y )
{
    if ( key == 27 || key == 'q' || key == 'Q' )        // quit requested
        exit ( 0 );
}

void motion ( int x, int y )
{
    rot.y -= ((mouseOldY - y) * 180.0f) / 200.0f;
    rot.x -= ((mouseOldX - x) * 180.0f) / 200.0f;
    rot.z  = 0;

    if ( rot.z > 360 )
        rot.z -= 360;

    if ( rot.z < -360 )
        rot.z += 360;

    if ( rot.y > 360 )
        rot.y -= 360;

    if ( rot.y < -360 )
        rot.y += 360;

    mouseOldX = x;
    mouseOldY = y;

    glutPostRedisplay ();
}

void mouse ( int button, int state, int x, int y )
{
    if ( state == GLUT_DOWN )
    {
        mouseOldX = x;
        mouseOldY = y;
    }
}

int main ( int argc, char * argv [] )
{
                                // initialize glut
    glutInit            ( &argc, argv );
    glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL );
    glutInitWindowSize  ( 512, 512 );

                                // create window
    glutCreateWindow ( "OpenGL textures buffer object & draw instanced demo" );

                                // register handlers
    glutDisplayFunc  ( display );
    glutReshapeFunc  ( reshape );
    glutKeyboardFunc ( key     );
    glutMouseFunc    ( mouse   );
    glutMotionFunc   ( motion  );

    init           ();
    initExtensions ();

    assertExtensionsSupported ( "GL_EXT_texture_buffer_object GL_EXT_draw_instanced" );

    createTbo ();
    
    Data * headData  = getFile ( "../../models/players/laracroft/lara_head.md3"  );
    Data * lowerData = getFile ( "../../models/players/laracroft/lara_lower.md3" );
    Data * upperData = getFile ( "../../models/players/laracroft/lara_upper.md3" );
    Data * headSkin  = getFile ( "../../models/players/laracroft/lara_head.skin"  );
    Data * lowerSkin = getFile ( "../../models/players/laracroft/lara_lower.skin" );
    Data * upperSkin = getFile ( "../../models/players/laracroft/lara_upper.skin" );

    addSearchPath ( "../.." );

    MeshNode * head  = Md3Loader ().load ( headData,  headSkin  );
    MeshNode * lower = Md3Loader ().load ( lowerData, lowerSkin );
    MeshNode * upper = Md3Loader ().load ( upperData, upperSkin );

    delete headData;
    delete lowerData;
    delete upperData;

    lower -> nodeWithName ( "tag_torso" ) -> node = upper;
    upper -> nodeWithName ( "tag_head"  ) -> node = head;

    root = lower;

    if ( root == NULL )
    {
        printf ( "Error loading md3 file\n" );

        exit ( 1 );
    }

    prepareNode ( root );
    
    if ( !program.loadShaders ( "program-3.vsh", "program-1.fsh" ) )
    {
        printf ( "Error loading shaders:\n%s\n", program.getLog ().c_str () );

        return 3;
    }
    
    program.bind       ();
    program.setTexture ( "texBuf",   1 );
    program.setTexture ( "decalMap", 0 );
    program.unbind     ();

    glutMainLoop ();
    
    return 0;
}

На следующем скриншоте приводится получаемое изображение.

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

Данное расширение предоставляет другой вариант осуществления инстансинга. В его основе лежит возможность задания для массива обобщенных вершинных атрибутов так называемого делителя (divisor). По умолчанию для каждого массива атрибутов делитель равен нулю. Если для массива явно задано значение делителя, большее нуля, то это значит этот атрибут используется при инстансинге.

При выводе instanced-объектов все атрибуты для вершин берутся из вершинных массивов. Как именно осуществляется выбор элемента из массива для каждого выводимого примитива, определяется делителем. Для каждого вершинного массива можно задать свой делитель.

Если у массива атрибутов установлено значение делителя равное нулю, то каждый выводимый примитив получает свое значение из этого массива:

Рис 1. Передача значений атрибутов для массива с делителем равным нулю.

В случае, когда для какого-либо массива атрибутов задано ненулевое значение делителя divisor, то каждому значению этого атрибута соответствует уже не один инстанс (копия) объекта, а divisor копий. Так в следующем примере используется два вершинных массива. Для одного из них делитель равен нулю, а для второго - двум (выбор значений условен).

Рис 2. Передача значений атрибутов при использовании двух массивов, для одного из которых значение делителя отлично от нуля.

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

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

При этом для вывода инстанциируемых данных используются те же самые вызовы, что и для расширения EXT_draw_instanced/ARB_draw_instanced, только появляется дополнительный способ передачи данных для инстансов. Обратите внимание, что данный способ работает только для обобщенных вершинных атрибутов, можно задавать ненулевое значение делителя даже для атрибута с индексом 0.

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

void glVertexAttribDivisorARB ( GLuint attrIndex, GLuint divisor );

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

Обратите внимание, что для компиляции примера вам понадобится новые версии библиотек libExt и libMesh , а также класса GlslProgram.

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