sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English
Українська
Show/Hide search bar
black cat logo variable logo
[17 Січ 2013]

Використання Vertex Array Objects, Vertex Buffer Objects та Element Buffer Objects в OpenGL

Призначенням Vertex Buffer Object (VBO) є збереження інформації про вершини, які будуть використовуватися як джерело данних для атрибутів вершинного шейдера. Щоб візуалізувати примітив потрібно створити вершинні буфера, заповнити їх данними вершин (позиція, колір, нормаль і тд), вказати який буфер відповідає якому атрибуту в вершинному шейдері, вказати як повинні інтерпретуватися данні з буфері і після цього зробити виклик малювання.
Vertex Array Object (VAO) використовується для того, щоб зберегти порядок приєдняння вершинних буферів (VBO) до атрибутів шейдера та інформацію про те, як інтерпретувати данні в кожному вершинному буфері (VBO). Для правильного рендерінгу в OpenGL 3+ завжди має бути активний VAO.
Element Buffer Object (EBO) використовується як джерело для індексів при малюванні об'єктів в яких наявні індекси.
В наступному прикладі показано ініціалізацію VAO, двох VBO та EBO:
const int floatsPerPosition = 3;
const int floatsPerColor = 4;
const int numVertices = 3;
const int sizeOfPositions = sizeof(float) * numVertices * floatsPerPosition;
const int sizeOfColors = sizeof(float) * numVertices * floatsPerColor;
const int numIndices = 3;

// Позиції збережені на CPU
float positions[numVertices][floatsPerPosition] =
{
   {0, 0, 0},
   {1, 1, 1},
   {1, 0, 1}
};

// Кольори збережені на CPU
float colors[numVertices][floatsPerColor] =
{
   {0, 0, 0, 1},
   {1, 1, 1, 1},
   {1, 0, 1, 1}
};

// Індекси збережені на CPU
int indices[numVertices] =
{
   0, 1, 2
};

////////////////////////////////////////////////////////////////////////

GLuint vao, vbo1, vbo2, ebo; // Порядкові номера OpenGL об'єктів

glGenVertexArrays(1, &vao); // Створити новий VAO
// Встановити поточний VAO, який буде записувати привязку буферів до атрибутів
glBindVertexArray(vao);

glGenBuffers(1, &vbo1); // Створити новий VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo1); // Зробити vbo1 поточним вершинним буфером
// ініціалізувати вершинний буфер, виділити пам'ять, заповнити буфер данними
glBufferData(GL_ARRAY_BUFFER, sizeOfPositions, positions, GL_STATIC_DRAW);
// вказати VAO, що поточний VBO має використовуватися для атрибута з індексом 0
glEnableVertexAttribArray(0);
// вказати VAO, як інтерпретувати данні з буфера для атрибуту з індексом 0
glVertexAttribPointer(0, floatsPerPosition, GL_FLOAT, GL_FALSE, 0, 0);

glGenBuffers(1, &vbo2); // Створити новий VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo2); // Зробити vbo2 поточним вершинним буфером
// ініціалізувати вершинний буфер, виділити пам'ять, заповнити буфер данними
glBufferData(GL_ARRAY_BUFFER, sizeOfColors, colors, GL_STATIC_DRAW);
// вказати VAO, що поточний VBO має використовуватися для атрибута з індексом 1
glEnableVertexAttribArray(1);
// вказати VAO, як інтерпретувати данні з буфера для атрибуту з індексом 1
glVertexAttribPointer(1, floatsPerColor, GL_FLOAT, GL_FALSE, 0, 0);

// Створити новий буфер, який буде використовуватися як індексний буфер
glGenBuffers(1, &ebo);
// Привязати буфер до відповідної цілі
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
// ініціалізувати індексний буфер, виділити пам'ять, заповнити буфер данними
glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices, indices, GL_STATIC_DRAW);

// збиваємо привязку об'єктів
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
Функція glBufferData виділяє нову ділянку пам'яті для буфера. Тому ця функція не застосовується для оновлення буфера, а тільки для виділення ділянки пам'яті при ініціалізації. Для оновлення буфера можна використати функцію glBufferSubData або glMapBufferRange. Використання glMapBufferRange може бути швидшим у випадку, коли OpenGL надає прямий вказіник до пам'яті, яка використовується контекстом. У певних операційних системах glMapBufferRange може дати збій під час втрати фокусу програмою і в інших подібних ситуаціях.
Щоб відрендерити об'єкти необхідно приєднати VAO та викликати малюваня об'єкту:
// Після ініціалізації VAO зберіг необхідну інформацію для приєднання вершинних буферів до атрибутів шейдера, а також про те як інтерпретувати данні в буферах. Тож просто приєднюємо VAO і об'єкт автоматично проведе всю ініціалізацію, яка необхідна для рендерінгу
glBindVertexArray(vao);
// Провести просте малювання трикутників з приєднаних вершинних буферів
glDrawArrays(GL_TRIANGLES, 0, numVertices);

/////////////////////////////////////////////////////////////////

// якщо необхідний індексний рендерінг, то також треба приєднати буфер елементів
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
// провести індексне малювання з приєднаних вершинних буферів
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, NULL);
В попередньому прикладі використано окремі вершинні буфери для кожного атрибута шейдера. Данні про вершини можна об'єднати в один буфер. Дані в буфері можуть бути погруповані по вершинах (атрибути вершини йдуть один за одним, далі атрибути для наступної вершини і тд.) чи по атрибутах (спочатку в буфері розміщаються данні для першого атрибуту, потім для другого і тд.).
Далі показано приклад групування атрибутів по вершинах. Для glVertexAttribPointer() вказується зсув атрибутів відносно інших атрибутів. Параметер stride рівний розміру вершини, тож для кожної вершини буде правильно розрахований додатковий зсув.
const int floatsPerPosition = 3;
const int floatsPerColor = 4;
const int numVertices = 2;
const int positionsOffset = 0 * sizeof(float);
const int colorsOffset = floatsPerPosition * sizeof(float);
const int sizeOfVertex = (floatsPerPosition + floatsPerColor) * sizeof(float);

// Данні про вершини збережені на CPU. Позиції збережені в перемішку з кольорами.
float data[numVertices * (floatsPerPosition * floatsPerColor)] =
{
   1, 1, 1, // Позиція першої вершини
   1, 1, 1, 1// Колір першої вершини
   0, 0, 0, // Позиція другої вершини
   0, 0, 0, 1 // Колір другої вершини
};

// stride рівний sizeOfVertex, початковий зсув для позицій рівний positionsOffset
glVertexAttribPointer(0, floatsPerPosition, GL_FLOAT, GL_FALSE, sizeOfVertex,
                       (void*)(positionsOffset));
// stride рівний sizeOfVertex, початковий зсув для кольорів рівний colorsOffset
glVertexAttribPointer(1, floatsPerColor, GL_FLOAT, GL_FALSE, sizeOfVertex,
                       (void*)(colorsOffset));
Наступний приклад показує як треба ініціалізувати VAO при роботі з даними, де спочатку збережений перший атрибут для всіх вершин, а потім другий атрибут для всіх вершин:
const int floatsPerPosition = 3;
const int floatsPerColor = 4;
const int numVertices = 2;
const int positionsStart = 0 * sizeof(float);
const int colorsStart = numVertices * floatsPerPosition * sizeof(float);

// Данні про вершини збережені на CPU. Спочатку збережені позиції, потім кольори.
float data[numVertices * (floatsPerPosition * floatsPerColor)] = {
   0, 0, 0, // Позиція першої вершини, початок даних з позиціями
   1, 1, 1, // Позиції другої вершини
   0, 0, 0, 1, // Колір першої вершини, початок даних з кольорами
   1, 1, 1, 1 // Колір другої вершини
};

// stride ріний 0, початковий зсув для позицій рівний positionsStart
glVertexAttribPointer(0, floatsPerPosition, GL_FLOAT,
                       GL_FALSE, 0, (void*)(positionsStart));
// stride ріний 0, початковий зсув для кольорів рівний colorsStart
glVertexAttribPointer(1, floatsPerColor, GL_FLOAT,
                       GL_FALSE, 0, (void*)(colorsStart));



Sun and Black Cat- Ігор Дихта (igor dykhta email) © 2007-2014