Это часть серии руководств по продвинутому современному OpenGL. Чтобы использовать все функции в полной мере, вам понадобится OpenGL 4.6. В этих статьях предполагается, что вы знакомы с OpenGL.
При старом способе использования OpenGL требовалось, чтобы вы отправляли такие данные, как вершины, uv и данные нормалей в формате, который драйвер мог понять и использовать. Это означало использование объектов буфера вершин (VBO) и объектов массива вершин (VAO). Благодаря программируемому извлечению вершин мы полностью избавимся от VBO и VAO и будем использовать собственный формат данных с ручной распаковкой данных в шейдере. Это дает нам большую гибкость в том, как мы структурируем наши данные, и будет очень полезно в будущих статьях, в которых будут обсуждаться другие передовые методы.
Если вы еще этого не сделали, обязательно ознакомьтесь с учебным пособием Shader Storage Buffer Objects (SSBOs), так как мы будем использовать их здесь.
Старый метод
Давайте начнем с примера того, как мы будем упаковывать вершины для отправки в GPU в потоке со старым способом.
main.cpp
// Data is laid out as 3 positions, 2 uvs, 3 normals, repeat std::vector<float> vertices; ... code to fill vertices with vertex data ... GLuint vbo; glGenBuffers(1, &vbo); // Bind and upload data glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData( GL_ARRAY_BUFFER, vertices.size(), (const void *)vertices.data(), GL_STATIC_DRAW ); // Tell OpenGL how to interpret the data and where it should be linked to // Positions glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0 ); glEnableVertexAttribArray(0); // UVs glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)) ); glEnableVertexAttribArray(1); // Normals glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float)) ); glEnableVertexAttribArray(2); // Bind program and draw glUseProgram(program); glDrawArrays(GL_TRIANGLES, 0, vertices.size()); // Unbind glUseProgram(0); glBindBuffer(GL_ARRAY_BUFFER, 0);
Тогда шейдер будет выглядеть примерно так:
shader.vs
#version 330 core layout (location = 0) in vec3 position; layout (location = 1) in vec2 uv; layout (location = 2) in vec3 normal; uniform mat4 projection; uniform mat4 view; out vec2 fsUv; out vec3 fsNormal; void main() { gl_Position = projection * view * vec4(position, 1.0); fsUv = uv; fsNormal = normal; }
Новый метод — программируемое извлечение вершин с помощью SSBO
Это будет работать как для индексированного, так и для неиндексированного рисования и может быть расширено для дальнейшей поддержки косвенных команд множественного рисования. В приведенном выше примере мы поместили все данные в вектор с плавающей запятой, затем отправили их в буфер массива OpenGL и сообщили OpenGL, как он должен интерпретировать данные и в какие места он должен их отправлять. С программируемым извлечением вершин мы покончили с этим и вместо этого вручную распаковываем наши данные в шейдере.
Преимущество этого заключается в том, что мы избавляемся от OpenGL, когда дело доходит до интерпретации наших данных, и вместо этого пишем код для прямой работы с нашими данными, во многом так же, как мы делали бы с C++, но теперь на GLSL. Это дает нам большую гибкость как с данными вершин, так и с другими данными, которые будут обсуждаться в будущих руководствах.
main.cpp
// We use arrays of floats since they will be tightly packed with the // layout std430 qualifier struct VertexData { float position[3]; float uv[2]; float normal[3]; }; // (inside either main or a different function) std::vector<VertexData> vertices; ... populate vertices with either hardcoded data or data you load from a file ... // Create and fill the mesh data buffer GLuint vertexDataBuffer; glCreateBuffers(1, &vertexDataBuffer); glNamedBufferStorage(vertexDataBuffer, sizeof(VertexData) * vertices.size(), (const void *)vertices.data(), GL_DYNAMIC_STORAGE_BIT); // Bind the buffer to location 0 - matches (binding = 0) for ssbo1 in the // vertex shader listed below glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, vertexDataBuffer); // Bind program and draw glUseProgram(program); glDrawArrays(GL_TRIANGLES, 0, vertices.size()); glUseProgram(0); // Uncomment if you want to explicitly unbind the resouce //glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
Внутри вершинного шейдера мы будем использовать встроенный ввод под названием gl_VertexID
. При использовании неиндексированного рисунка, такого как glDrawArrays
, gl_VertexID будет равен текущему индексу вершины. При использовании индексированного рисунка, такого как glDrawElements
, gl_VertexID будет равен текущему индексу из буфера массива элементов.
shader.vs
#version 460 core // This matches the C++ definition struct VertexData { float position[3]; float uv[2]; float normal[3]; }; // readonly SSBO containing the data layout(binding = 0, std430) readonly buffer ssbo1 { VertexData data[]; }; uniform mat4 projection; uniform mat4 view; // Helper functions to manually unpack the data into vectors given an index vec3 getPosition(int index) { return vec3( data[index].position[0], data[index].position[1], data[index].position[2] ); } vec2 getUV(int index) { return vec2( data[index].uv[0], data[index].uv[1] ); } vec3 getNormal(int index) { return vec3( data[index].normal[0], data[index].normal[1], data[index].normal[2] ); } out vec2 fsUv; out vec3 fsNormal; void main() { gl_Position = projection * view * vec4(getPosition(gl_VertexID), 1.0); fsUv = getUV(gl_VertexID); fsNormal = getNormal(gl_VertexID); }
Больше никаких VBO или VAO. Хороший!