Hi everyone,
I'm trying to make a 3D rendering engine just for learning purposes (it would be nice if it evolves into a portfolio project for landing a rendering engineer job) but I just can't figure out how to properly abstract vertex attributes. To begin with, I've seen that vertex attributes have the following characteristics:
- There are many types of vertex attributes (e.g. positions, normals, texture_coords, etc.).
- A mesh may not have all of the attributes (e.g. it may not have a color attribute for instance).
- There are many ways of representing them (e.g. array of float, array of struct, etc).
- They can be static, stream, and dynamic attributes.
- They map to an attribute in a shader.
- Been able to retrieve position data is desirable for making AABB.
So far I've come up with the following designs:
Block-Based Vertex Attribute Storage
In this design there's a class that manages all the possible attributes that a mesh can have. Also, it produces a list of AttributeDescriptor
s that can be used to load the attribute data into OpenGL easily (i.e. allocate a buffer big enough to hold all the data and then load it using buffer sub data functions).
struct AttributeDescriptor
{
// E.g. 3 if its a vec3.
uint_8 num_cmp;
// how many attributes of this type are there.
size_t size;
// E.g. AttributeType::FLOAT
AttributeType type;
// E.g. AttributeName::POSITION
AttributeName name;
// E.g. AttributeUsage::STATIC;
AttributeUsage usage;
void* data;
};
class VertexBuffer
{
public:
...
void addPosition(const glm::vec3& position);
void addTextureCoord(const glm::vec2& coord);
...
size_t getBufferSize() const noexcept;
std::vector<AttributeDescriptors> getAttributeDescriptors() const noexcept;
private:
std::vector<glm::vec3> m_positions;
std::vector<glm::vec2> m_texture_coords;
...
};
Pros:
- If a mesh doesn't have some attributes its underlying vector on the class will be empty.
- Building attribute descriptors are easy to build and mostly static.
- Fetching position data is easy.
- Building a VertexBufferObject in OpenGL is straight forward.
- Attributes can be updated quickly when they are dynamic (e.g. setPosition(…)).
- Can be stored directly in a Mesh instance (i.e. no need to use a pointer).
Cons:
- Attributes are static (?), this is, if the class only supports positions, normals, and texture coords then adding a new attribute (e.g. bone weights) is painful/needs refactoring (violation of Open-Closed principle (?)).
- Forces the
AttributeDescriptor
to contain a pointer to the data. - Everything is coupled to a single implementation.
Interface-Based Implementation
The idea of this design is that we abstract common buffer operations into an interface, each implementer manages one possible buffer representation (e.g. an array of floats), AttributeDescriptor
allows OpenGL to properly setup the VBO.
struct AttributeDescriptor
{
uint_8 num_cmp;
// Like the OpenGL offset
size_t offset;
AttributeType type;
std::string name;
};
struct VertexBuffer
{
virtual ~VertexBuffer() noexcept = default;
virtual size_t getBufferSize() const noexcept = 0;
virtual size_t getStrideSize() const noexcept = 0;
virtual void* getDataPtr() noexcept = 0;
virtual std::vector<glm::vec3> getPositions() const noexcept = 0;
virutal std::vector<AttributeDescriptor> getAttributeDescriptors() const noexcept = 0;
}
/*
Concrete implementation of a vertex buffer type based on a std::vector<float>.
In this case an user can supply std::vector<float> with the attribute information:
std::vector<float> attributes = {
// Positions // Texture Coords
1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
...
};
*/
class SimpleVertexBuffer: public VertexBuffer
{
...
}
Pros:
- Doesn't constrain what attributes can be supplied.
- No space wasted when a mesh doesn't have a set of attributes.
- More flexible implementation (E.g. use structs, single float vector, etc.).
Cons:
- Depending on the implementer of the interface getting position attributes can be painful or easy.
- The buffer has to be stored in a Mesh using a pointer thus breaking cache usage on a cache-friendly architecture (e.g. ECS).
- Users now have to carefully define Attribute Descriptors and supply them (painful to validate them on implementers).
- Forces a single pointer for loading the data (everything is packed into a single vector).
Does anybody can give me feedback on this topic? Design suggestions are welcome too.
Thanks in advance.