Skip to content

Instantly share code, notes, and snippets.

@apples
Last active August 29, 2015 14:05
Show Gist options
  • Save apples/03125c06809c82dc46e2 to your computer and use it in GitHub Desktop.
Save apples/03125c06809c82dc46e2 to your computer and use it in GitHub Desktop.
// ULTIMATE VERTEX ARRAY OBJECT EXAMPLE
// No guarantee of correctness.
// Warranty void upon successful compile.
// No warranty.
// This software is released into the public domain.
#include <GL/glew.h> // or whatever loader you want.
#include <glm/glm.hpp> // or whatever math library you want.
#include <cstddef>
#include <vector>
// Resource handle for a VBO. No logic.
class VBO {
GLuint handle;
public:
VBO() {
glGenBuffers(1, &handle);
// We should probably throw an exception if glGenBuffers() fails.
}
// VBOs should not be copied.
VBO(VBO const&) = delete;
VBO& operator=(VBO const&) = delete;
// VBOs can easily be moved.
VBO(VBO&& other) : handle(other.handle) {
other.handle = 0;
}
VBO& operator=(VBO&& other) {
this->~VBO();
new (this) VBO(std::move(other));
return *this;
}
~VBO() {
glDeleteBuffers(1, &handle);
}
GLuint get() const {
return handle;
}
};
// Resource handle for a VAO. No logic. Very similar to VBO.
class VAO {
GLuint handle;
public:
VAO() {
glGenVertexArrays(1, &handle);
// This is another good place for an exception.
}
// VAOs should not be copied.
VAO(VAO const&) = delete;
VAO& operator=(VAO const&) = delete;
// VAOs can easily be moved.
VAO(VAO&& other) : handle(other.handle) {
other.handle = 0;
}
VAO& operator=(VAO&& other) {
this->~VAO();
new (this) VAO(std::move(other));
return *this;
}
~VAO() {
glDeleteVertexArrays(1, &handle);
}
GLuint get() const {
return handle;
}
};
// A simple 3D vertex.
struct Vertex {
glm::vec3 position;
glm::vec3 normal;
glm::vec2 texcoord;
};
// A triangle is a set of 3 indices into a Vertex array.
struct Triangle {
GLuint verts[3];
};
// A few basic assertions to ensure proper alignment.
static_assert(sizeof(Vertex) == sizeof(GLfloat)*8, "Vertex misaligned!");
static_assert(sizeof(Triangle) == sizeof(GLuint)*3, "Triangle misaligned!");
// A simple non-animated model.
struct Model {
std::vector<Vertex> vertices;
std::vector<Triangle> triangles;
};
// A container for a VAO with an element count.
struct TriangleVAO {
VAO vao;
GLsizei count = 0;
};
// Let's upload a model onto the GPU.
TriangleVAO upload_model(Model const& model) {
TriangleVAO rv; // This will be a handle to our Model after we send it to the GPU.
VBO vertex_vbo; // This VBO will hold the Vertex data.
VBO element_vbo; // This VBO will hold the Triangle data.
// Step 1: Upload the Vertex data.
// The Vertex data is not directly associated with any VAO,
// so we can upload it without having a VAO bound yet.
// We'll be using GL_STATIC_DRAW, since we won't need to modify the data
// after uploading.
glBindBuffer(GL_ARRAY_BUFFER, vertex_vbo.get());
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*model.vertices.size(), &model.vertices[0], GL_STATIC_DRAW);
// Step 2: Create the VAO.
// Once bound, the OpenGL state machine will be in a special mode.
// During this time, certain procedures will act upon the VAO itself,
// instead of affecting global state.
// Note: The ARRAY_BUFFER could have be bound and uploaded while the VAO
// is bound, but it would not be attached to the VAO. ARRAY_BUFFER is
// completely agnostic to VAOs.
glBindVertexArray(rv.vao.get());
// We must enable a Vertex Attribute for each datum in a Vertex.
// These are only enabled for the currently bound VAO.
// Note: These do not have to be enabled until we want to draw the VAO,
// but since they stick with the VAO, we might as well go ahead and do so.
glEnableVertexAttribArray(0); // Position
glEnableVertexAttribArray(1); // Normal
glEnableVertexAttribArray(2); // Texture Coordinate
// Now that we've enabled the Attributes, we have to tell the GPU where
// to find the data for each Attribute.
// In our case, we're using an interleaved format, so we can use the
// same VBO for all Attributes.
// If a VBO is currently bound to ARRAY_BUFFER, then these pointers will
// be treated as offsets into that buffer.
// We've bound vertex_vbo to that buffer, so it will be used as the data
// source when we draw the VAO.
// A small helper, for readability.
auto setAttribute = [](GLuint index, GLint size, std::size_t offset) {
GLvoid* offset_v = static_cast<char*>(nullptr) + offset;
return glVertexAttribPointer(index, size, GL_FLOAT, GL_FALSE, sizeof(Vertex), offset_v);
};
setAttribute(0, 3, offsetof(Vertex, position));
setAttribute(1, 3, offsetof(Vertex, normal));
setAttribute(2, 2, offsetof(Vertex, texcoord));
// Step 3: Upload the Triangle data.
// Now, the only thing to do is upload the polygon data as an Element Array.
// The ELEMENT_ARRAY_BUFFER works exactly like a normal ARRAY_BUFFER,
// except it will be attached to the VAO instead of global state.
// The Element Array will be used as the indices into the Vertex VBO
// while drawing.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_vbo.get());
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Triangle)*model.triangles.size(), &model.triangles[0], GL_STATIC_DRAW);
// Don't forget to set the element count!
rv.count = model.triangles.size();
// Done! Let's get out of here.
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
return rv;
}
// Alright, time to draw it. This is going to be complicated; pay attention.
void draw_vao(TriangleVAO const& mesh) {
glBindVertexArray(mesh.vao.get());
glDrawElements(GL_TRIANGLES, mesh.count, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
}
// Fin.
@xxAtrain223
Copy link

Yeah, this is much easier to understand than some of the stuff out there, and i'm assuming that this is more up to date than 90% of the crap I found. Just need to clean it up, #include proper headers, fix some bugs/typos, and it'll be ready to turn into a black box that does the magic I need.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment