OpenGLで頂点バッファのレイアウトを定義する場合、複数の属性を扱うとコードが冗長になりがちです:
glBindBuffer(GL_ARRAY_BUFFER, vboID);
// 位置情報
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 色情報
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 法線情報
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
// UV座標
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(9 * sizeof(float)));
glEnableVertexAttribArray(3);
// 接線ベクトル
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(11 * sizeof(float)));
glEnableVertexAttribArray(4);
より簡潔な実装方法として、以下のようなインターフェースが考えられます:
vertexArray->ApplyLayout({
{ DataType::Float3, "position" },
{ DataType::Float4, "color" },
{ DataType::Float2, "uv" },
{ DataType::Float, "texIndex" },
{ DataType::Float, "tilingFactor" },
{ DataType::Int, "entityID" }
});
実装方法
頂点バッファクラス
class GLVertexArray : public VertexArrayBase {
public:
virtual const VertexLayout& GetLayout() const override { return layout; }
virtual void SetLayout(const VertexLayout& config) override {
layout = config;
SetupAttributes();
}
private:
VertexLayout layout;
};
レイアウト管理クラス
class VertexLayout {
public:
VertexLayout(std::initializer_list<VertexElement> elements) : attributes(elements) {
ComputeLayout();
}
auto begin() { return attributes.begin(); }
auto end() { return attributes.end(); }
private:
void ComputeLayout() {
uint32_t currentOffset = 0;
stride = 0;
for (auto& attr : attributes) {
attr.offset = currentOffset;
currentOffset += attr.size;
stride += attr.size;
}
}
std::vector<VertexElement> attributes;
uint32_t stride = 0;
};
頂点属性構造体
enum class DataType {
Float, Vec2, Vec3, Vec4,
Int, IVec2, IVec3, IVec4,
Matrix3, Matrix4, Bool
};
uint32_t GetDataTypeSize(DataType type) {
// データ型ごとのサイズ計算
}
struct VertexElement {
std::string identifier;
DataType dataType;
uint32_t size;
uint32_t offset;
bool normalized;
VertexElement(DataType type, const std::string& name, bool norm = false)
: identifier(name), dataType(type), size(GetDataTypeSize(type)), offset(0), normalized(norm) {}
};
属性設定関数
GLenum ConvertToGLType(DataType type) {
switch(type) {
case DataType::Float: case DataType::Vec2: case DataType::Vec3: case DataType::Vec4:
case DataType::Matrix3: case DataType::Matrix4: return GL_FLOAT;
case DataType::Int: case DataType::IVec2: case DataType::IVec3: case DataType::IVec4: return GL_INT;
case DataType::Bool: return GL_BOOL;
default: /* エラー処理 */;
}
}
void SetupAttributes() {
auto& config = vertexArray->GetLayout();
vertexArray->Bind();
uint32_t index = 0;
for (const auto& elem : config) {
switch(elem.dataType) {
case DataType::Float: case DataType::Vec2: case DataType::Vec3: case DataType::Vec4:
glEnableVertexAttribArray(index);
glVertexAttribPointer(index, GetComponentCount(elem.dataType),
ConvertToGLType(elem.dataType), elem.normalized,
config.GetStride(), (void*)elem.offset);
index++;
break;
case DataType::Int: case DataType::IVec2: case DataType::IVec3: case DataType::IVec4:
glEnableVertexAttribArray(index);
glVertexAttribIPointer(index, GetComponentCount(elem.dataType),
ConvertToGLType(elem.dataType), config.GetStride(),
(void*)elem.offset);
index++;
break;
// マトリックス型の特殊処理
}
}
}