Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая |
Довольно часто возникает необходимость использовать текстуру просто как большой массив с данными. В этом случае ряд возможностей, характерных для доступа к текстурам (различные способы фильтрации и т.п.) не просто не нужны - они даже будут мешать.
Кроме того, стандартные ограничения на максимальный размер 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 представляет собой простой и удобный способ создания, работы и модификации больших массивов однородных данных и для их использования в шейдерах.
Часто возникает необходимость вывода большого количества почти одинаковых объектов - отличия между индивидуальными объектами могут заключаться всего лишь в положении, ориентации или похожих характеристиках. Подобная необходимость часто возникает в играх при выводе большого количества деревьев/кустов, группы людей и т.п.
Понятно что в 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; }
На следующем скриншоте приводится получаемое изображение.
Данное расширение предоставляет другой вариант осуществления инстансинга. В его основе лежит возможность задания для массива обобщенных вершинных атрибутов так называемого делителя (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.