OpenGL頂点レイアウト設定のカプセル化

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;
      // マトリックス型の特殊処理
    }
  }
}

タグ: OpenGL 頂点バッファ 頂点属性 バッファレイアウト C++ラッパー

5月30日 08:39 投稿