
Это часть серии руководств по продвинутому современному 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. Хороший!