2 апреля 2012 г.

OpenGL ES 1. Применение индексов при обходе вершин

Применение команды glDrawArrays обязывает нас упорядочивать массивы координат вершин, нормалей и текстур в строго определённом порядке в соответствии с выбранным правилом обхода точек GL_TRIANGLES, GL_TRIANGLE_STRIP или GL_TRIANGLE_FAN. Это не всегда бывает удобно. Поэтому в OpenGL существует возможность разделить данные вершин и правила обхода по разным массивам. Специально подготовленный массив, в котором хранится порядок обхода вершин, называют массивом индексов. Рассмотрим применение индексов на простом примере.

Допустим, что у нас имеется два ряда горизонтальных точек. Верхний ряд состоит из точек 0,1,2,3, нижний ряд из точек 4,5,6,7. Обозначим координаты вершин точки 0 как x0,y0,z0, точки 1 - x1,y1,z1, точки 2 - x2,y2,z2 и так далее до точки 7. Предположим, что нам удобно хранить координаты вершин в следующем порядке - 0,1,2,3,4,5,6,7. Сформируем массив координат и передадим его в буфер:
float [] vertexArray={x0,y0,z0, x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4, x5,y5,z5, x6,y6,z6, x7,y7,z7};
ByteBuffer b1 = ByteBuffer.allocateDirect(12*8);
b1.order(ByteOrder.nativeOrder());
vertexBuffer = b1.asFloatBuffer();
vertexBuffer.put(vertexArray);
vertexBuffer.position(0);

Для примера выберем правило GL_TRIANGLE_STRIP, в соответствии которым лента треугольников формируется в следующем порядке: сначала рисуется треугольник из точек 0-4-1, далее 1-4-5, 1-5-2, 2-5-6,  2-6-3, 3-6-7:

Массив индексов представляет из себя последовательность номеров точек, понятную для выбранного правила обхода. Поскольку мы используем правило GL_TRIANGLE_STRIP, которое само добавляет промежуточные точки, из массива индексов нужно выбросить лишние вершины, т.е. вместо порядка 0,4,1, 1,4,5, 1,5,2, 2,5,6, 2,6,3, 3,6,7  необходимо указать номера точек в следующей последовательности: 0,4,1,5,2,6,3,7
Формат номеров точек в индексном массиве может быть только byte или short, целые числа типа int для индексов в OpenGL ES не используются. Определимся с размером буфера индексов. Каждый индекс как число типа short занимает в памяти два байта. Всего у нас 8 точек. Поэтому буфер должен быть размером 16 байт. Сформируем массив индексов и передадим его в буфер: 
short [] indexArray={0,4,1,5,2,6,3,7};
ByteBuffer b2 = ByteBuffer.allocateDirect(16);
b2.order(ByteOrder.nativeOrder());

ShortBuffer indexBuffer = b2.asShortBuffer();
indexBuffer.position(0);
indexBuffer.put(indexArray);
indexBuffer.position(0);
Далее мы должны разрешить использование массивов координат вершин и передать их в OpenGL:
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// указываем программе, что буфер с именем vertexBuffer является буфером координат вершин
gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);
Для рисования с использованием массивов индексов команда  glDrawArrays не используется, вместо неё применяется другая команда glDrawElements:
gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 8, 
           GL10.GL_UNSIGNED_SHORT,indexBuffer);
В качестве первого аргумента задается выбранное правило, в нашем примере это GL_TRIANGLE_STRIP. Второй аргумент - это количество элементов в индексном массиве, т.е. восемь. Третий аргумент - формат чисел индексов, в нашем случае short. Последний аргумент буфер, в который записан индексный массив.
Если вместо правила GL_TRIANGLE_STRIP для рисования ленты использовать GL_TRIANGLES, которое рисует каждую тройку вершин как отдельный треугольник, то мы должны указывать в массиве индексов все промежуточные точки. В этом случае количество индексов возрастет до 18 значений:
short [] indexArray={ 0,4,1, 1,4,5, 1,5,2, 2,5,6, 2,6,3, 3,6,7 };
ByteBuffer b2 = ByteBuffer.allocateDirect(2*18);
b2.order(ByteOrder.nativeOrder());
ShortBuffer indexBuffer = b2.asShortBuffer();
indexBuffer.position(0);
indexBuffer.put(indexArray);
indexBuffer.position(0);
и соответственно изменится формат команды glDrawElements:
gl.glDrawElements(GL10.GL_TRIANGLES, 18, 
           GL10.GL_UNSIGNED_SHORT,indexBuffer);   
Практика показывает, что использование индексов заметно увеличивает  скорость отрисовки кадров при большом количестве вершин. Поэтому, по возможности, нужно заменять команду  glDrawArrays на glDrawElements.

8 комментариев:

  1. вот это классно объяснено

    ОтветитьУдалить
  2. Целый день изучаю всякие ресурсы по OpenGL для iOS. Попадаются статьи, написанные для людей с опытом. Хорошо, что мне повстречался этот блог, здесь мне новичку понятна суть :)

    ОтветитьУдалить
  3. Спасибо!! Очень помогло быстро вспомнить обход меша.

    ОтветитьУдалить
  4. Увеличивает скорость отрисовки кадров при большом количестве вершин. -
    А если и у меня количество индексов больше, чем вмещает Short? Получается буффер индексов не построить.

    ОтветитьУдалить
    Ответы
    1. может тогда применить несколько массивов индексов?

      Удалить
  5. Спасибо! Всё очень доступно объяснили.

    ОтветитьУдалить
  6. Подскажите, пожалуйста. Можно ли комбинировать команды glDrawArrays и glDrawElements, если конечные буферы вершин и нормалей общие, для разных 3d-фигур?

    ОтветитьУдалить