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.

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

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

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

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

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

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

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

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