Принцип індексації

До цих пір, коли ми будували VBO, ми завжди дублювали наші вершини коли два трикутника мають спільну вершину.

В цьому туторіалі, ми введемо поняття індексації, що дозволить нам використовувати ті самі вершини повторно. Це буде досягнуто за допомогою індексного буфера (index buffer).

Індексний буфер містить числа, по три для кожного трикутника в меші, які посилаються на різноманітні буфери атрибутів (позиція, колір, UV координати, інші UV координати, нормалі …). Це схоже на формат файлу OBJ, тільки з однією великою різницею: є тільки один буфер індексу. Це значить що для вершин, що є спільними для двох трикутників, всі атрибути мають бути однаковими.

Shared vs Separate

Давайте розглянемо на прикладі нормалей. В цій фігурі, артист, який створив ці два трикутника, напевне хотів, що б вони представляли гладку поверхню. Ми можемо об’єднати ці нормалі двох трикутників в одну вершинну нормаль. Для візуалізації цього, я додав червону лінію, яка показую напрямок гладкої поверхні.

В другому випадку, артист мав на увазі “шов”, грубий край. Але якщо ми об’єднаємо ці нормалі, це буде значити, що шейдер зробить інтерполяцію і створить гладкий напрямок, десь такий:

І цьому випадку насправді краще мати два набори нормалей, по одній на кожну вершину. Єдиний спосіб зробити це в OpenGL - здублювати вершину повністю з всіма атрибутами.

Індексація VBO в OpenGL

Використання індексів є дуже простим. По перше, необхідно створити додатковий буфер, який наповнити правильними індексами. Код для цього потрібен такий самий як і раніше, але тепер потрібен ELEMENT_ARRAY_BUFFER, а не ARRAY_BUFFER.

std::vector<unsigned int> indices;

// заповнимо індекси

// Згенеруємо буфер для індексів
 GLuint elementbuffer;
 glGenBuffers(1, &elementbuffer);
 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

та намалюємо меш, просто замінивши glDrawArrays наступним :

// буфер індексів
 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);

 // малюємо трикутник !
 glDrawElements(
     GL_TRIANGLES,      // режим
     indices.size(),    // кількість
     GL_UNSIGNED_INT,   // тип
     (void*)0           // індекс початку даних в масиві
 );

(швидка примітка: краще використовувати “unsigned short” ніж “unsigned int”, тому що такий масив займає менше пам’яті і швидше працює)

Заповнення буферного індексу

Тепер у нас є проблема. Як було сказано раніше, OpenGL може використовувати тільки один буфер індексу, де OBJ (і деякі інші популярні 3D формати, наприклад Collada) використовують один буфер для атрибута. Це значить, що нам потрібно якимось чином перетворити з N буферів індексу в один.

Алгоритм для реалізації цього такий:

Для кожної вхідної вершини
    Спробувати знайти подібну ( = з такими же атрибутами) вершину в тих, що вже вивели
    Якщо знайшли:
        Подібна вершина вже в VBO, використовуємо її!
    Якщо не знайшли:
        Додамо її до VBO

Реальний с++ код можна знайти в common/vboindexer.cpp. Він містить достатньо багато коментарів, тому, якщо вам зрозумілий алгоритм вище, то і код буде зрозумілим.

Критерій подібності є те, що позиція вершин, UV і нормалей повинна буди однаковою. Ви можете адаптувати це якщо ви маєте більше атрибутів.

Пошук подібних вершин виконано простим лінійним алгоритмом для простоти. Вибір std::map може бути кращим для реального коду.

Додатково : лічильник кадрів (FPS counter)

Це не відноситься безпосередньо до індексація, але це гарний момент що б подивитись на лічильник FPS, тому що це допоможе побачити покращення від роботи індексації. Інші інструменті для налагодження «перформанса» Tools - Debuggers.