OpenGL - VAO と VBO の関係を理解する方法

シリーズ記事ディレクトリ



1 はじめに

前の章LearnOpenGL Notes - Getting Started 04 で、こんにちは、Triangle はVBO、VAO、EBO、Shader などの多くの概念を紹介しました。集中的な知識ポイントがあなたを攻撃し、この章の難易度を急激に上昇させます. 正直なところ、この章はかなりがっかりしました。VBOやVAOなどの解説は書いてありますが、初心者の私には理解できません。読者がかなりイライラするほどです。

今日は、この章の概念を「幼稚園」に変えて、初心者の観点から、VAO や VBO などの概念を疑似コードの形で理解できるようにします。

2. レンダリング パイプラインの入り口 - 頂点シェーダー

OpenGL を使用して三角形をレンダリングする場合でも、複雑なモデルをレンダリングする場合でも、頂点データを入力して画像を取得するだけです。
ここに画像の説明を挿入
レンダリング パイプラインは、頂点シェーダー、ジオメトリ シェーダー、フラグメント シェーダーなどを含む複数のステージ (このパートの前の章で詳しく説明) で構成されます。

2.1 頂点シェーダー処理

その中で、頂点シェーダーはパイプライン全体の最初の段階に位置し、すべての頂点データは最初に頂点シェーダーに送られます。頂点座標、色、テクスチャ座標などのデータを受け取り、回転、スケーリング、平行移動などのこれらのデータを変換し、最終的に処理された頂点データを後続のレンダリング ステップに渡します。

三角形のレンダリングを例にとると、その頂点シェーダー コードは非常に単純です。

const char *vertexShaderSource = R"(
    #version 330
    layout (location = 0) in vec3 aPos;
    void main()
    {
        gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    }
)";

float vertices[] = {
    
    
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

このvertices[]うち3 つの頂点の位置が に格納されていますが、頂点シェーダーのコードを見ると、1 つの頂点しか処理していないことがわかります。これは私の最初の混乱です: OpenGL はどのように複数の頂点をレンダリングしますか?

実際、頂点シェーダーはグラフィックス プロセッシング ユニット (GPU) 上で並列に実行できます。つまり、複数の頂点データを同時に処理できます。GPU には、頂点データを同時に処理できる単純な処理ユニットが多数あります。

例えば頂点データが100個、GPU上に処理ユニットが10個あるとすると、バーテックスシェーダーの処理プロセスは約

  1. データ分散: 100 個の頂点データが GPU 上の 10 個の処理ユニットに分散されます。各処理ユニットに割り当てられる頂点データの量は異なる場合があります。
  2. データ処理: 各処理ユニットは、割り当てられた頂点データを個別に処理します。頂点シェーダーで定義された変換 (回転、スケーリング、移動など) は、各頂点データに適用されます。
  3. 結果のマージ: 各処理ユニットで処理された結果がマージされます。最終結果は100頂点データの処理結果です。
  4. 結果を渡す: 処理された頂点データが後続のレンダリング ステップに渡され、3D グラフィックスのレンダリングが完了します。

これはプロセスの簡略化された説明であり、実際のプロセスはより複雑になる場合があります。しかし、上記の処理により、100 個の頂点データを効率的に処理できるため、効率的な 3D グラフィックスのレンダリングが可能になります。

上記のプロセスを説明するために疑似コードを使用します。

#define NUM_VERTICES 100
#define NUM_UNITS 10

vector<vec3> vertex_data(NUM_VERTICES); // 有 100 个顶点数据

// 1. 数据分配
vector<vec3> processing_unit_data[NUM_UNITS]; // 有 10 个处理单元,每个单元处理 10 个顶点
const int num_vertices_per_unit = NUM_VERTICES / NUM_UNITS;
for (int i = 0; i < NUM_UNITS; i++) {
    
    
    processing_unit_data[i].assign(vertex_data.begin() + i * num_vertices_per_unit,
                                    vertex_data.begin() + (i + 1) * num_vertices_per_unit);
}

// 2. 数据处理
for (int i = 0; i < NUM_UNITS; i++) {
    
    
    for (int j = 0; j < processing_unit_data[i].size(); j++) {
    
    
        processing_unit_data[i][j] = vertex_shader(processing_unit_data[i][j]);
    }
}

// 3. 结果合并
vector<vec3> result; // 最终得到 100 个处理后的数据
for (int i = 0; i < NUM_UNITS; i++) {
    
    
    result.insert(result.end(), processing_unit_data[i].begin(), processing_unit_data[i].end());
}

// 4. 传递结果
render(result);

2. 疑似コードのデータ処理部分では、for ループを使用して、各 GPU 処理ユニットでシェーダーを順次実行します。ただし、この部分は実際の G​​PU 操作では並列であり、GPU は非常に多くのデータを並列処理できることに注意してください。以下に示すように
ここに画像の説明を挿入

2.2 さらにデータを入力する

先ほど三角形を描いたときに、三角形の頂点位置データを入力しました。より精巧で複雑なモデルを描画するために入力する必要があるのは、頂点の位置だけでなく、色、テクスチャ座標、法線ベクトル座標などのデータです。これらをまとめて頂点属性と呼びます。名前が示すように、実際には頂点の特定のプロパティを記述します。

頂点シェーダーを関数と見なす場合、入力が頂点位置情報のみを持っている場合、関数パラメーターは 1 つしかないと理解できます; 頂点シェーダーが入力頂点の色など、他の頂点属性をさらに入力する場合、次に 2 つの関数入力パラメーターがあります。

void vertex_shader(vec3 pos);	// 输入顶点位置数据
void vertex_shader(vec3 pos, vec3 color); // 输入顶点位置数据、顶点颜色数据

複数の入力がシェーダー ソース コードに反映され、複数のin変数。たとえば、

const char *vertexShaderSource_one_input = R"(
    #version 330
    layout (location = 0) in vec3 aPos; // 顶点位置数据
    void main()
    {
    
    
        // ...
    }
)";

const char *vertexShaderSource_two_input = R"(
    #version 330
    layout (location = 0) in vec3 aPos; // 顶点位置数据
    layout (location = 1) in vec3 aColor; // 顶点颜色数据
    void main()
    {
    
    
        // ...
    }
)";

OpenGL では、少なくとも 16 個の 4 コンポーネントの頂点属性を使用できることが保証されています。つまり、vertex_shader関数は少なくとも 16 個のパラメーター入力を処理できます。このとき、GPU がシェーダーを実行すると、下図に示すように、複数のデータが入力されます。
ここに画像の説明を挿入

3. VBO 頂点バッファ オブジェクト

頂点シェーダーの入力は頂点属性データですが、データはどこに格納されているのでしょうか? 答えはビデオメモリに保存されます

「いいえ、前の vertices[] 変数を見てください。これはコードに格納されており、コード内のデータはメモリに格納する必要があります。」

そうです、vertices確かにメモリに格納されていますが、OpenGL API を使用して、メモリに格納されたデータをビデオ メモリにコピーする必要があります。ビデオ メモリでは、このビデオ メモリを表すために同様のverticesオブジェクトが、そのようなオブジェクトが VBO です。
ここに画像の説明を挿入

3.1 頂点属性データの格納方法

三角形を描画する際に、頂点の位置データに加えて、各頂点の色情報もあるとすると、この2種類の情報をどのように並べればよいでしょうか。
位置と色は異なる属性です. プログラミングの直感のために, 私は 2 つの配列を使用してそれらを別々に格納することを好みます.

// xyz
float positions[] = {
    
    
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

// rgb
float colors[] = {
    
    
	1.0f, 0.0f, 0.0f,
	0.0f, 1.0f, 0.0f,
	0.0f, 0.0f, 1.0f,
}

これに対応して、OpenGL API を使用して 2 つの vbo を作成しpositionscolorsメモリのデータとメモリからビデオ メモリにそれぞれデータをコピーします。コードは大まかに次のようになります。

GLuint vbos[2] = {
    
    
    0,0
};
glGenBuffers(2, vbos);

// copy positions to first vbo
glBindBuffer(GL_ARRAY_BUFFER, vbos[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

// copy colors to second vbo
glBindBuffer(GL_ARRAY_BUFFER, vbos[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);

もちろん、すべての頂点属性データを配列と vbo に入れることもできます。

float vertices[] = {
    
    
        // 位置              // 颜色
        0.5f,  -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,  // 右下
        -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
        0.0f,  0.5f,  0.0f, 0.0f, 0.0f, 1.0f  // 顶部
};
GLuint vbo{
    
    0}
glGenBuffers(1, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

2 つのアプローチの長所と短所は何ですか?

単一の VBO にデータを保存します。

  • アドバンテージ:
    • 使いやすさ: すべてのデータを保存する VBO を作成するだけです。
    • 効率的: すべてのデータを一緒に使用すると、CPU/GPU 間のデータ転送の回数を減らすことができます。
  • 欠点:
    • 柔軟性がない: 一部のデータを変更したい場合は、VBO 全体を更新する必要があります。
    • 更新に時間がかかる: データ量が多いため、VBO の更新に時間がかかる場合があります。
    • 大量のメモリを占有する: データ量が多いため、多くのメモリを占有する場合があります。

複数の VBO にデータを保存します。

  • アドバンテージ:
    • 柔軟性: 各 VBO のデータは個別に変更できます。
    • 短い更新時間: 各 VBO のデータ量が少ないため、VBO の更新時間が短い場合があります。
    • 小さいメモリ フットプリント: 各 VBO のデータ量が少ないため、メモリ フットプリントが小さい場合があります。
  • 欠点:
    • 少し複雑: すべてのデータが正しくレンダリングされるようにするには、複数の VBO を管理する必要があります。
    • 低効率: すべてのデータを一緒に使用すると、CPU/GPU 間のデータ転送数が増加し、レンダリング効率が低下する可能性があります。

一般に、すべてのデータを一緒に使用する場合は、単一の VBO を使用する方が効率的です。ただし、データを変更する柔軟性が必要な場合は、複数の VBO を使用する方が適切な場合があります。したがって、単一の VBO を使用するか複数の VBO を使用するかの選択は、特定のアプリケーションのニーズによって異なります。

3.2 VBO からデータを取得する

VBO は、大量のデータを格納するビデオ メモリの一部を表します。前述のように、頂点シェーダーの入力はビデオ メモリから取得されますが、これは実際には VBO からのものです。

ここで問題について考えてみましょう。1 つの VBO が位置、色などを含む大量のデータを格納し、これらのデータをそれぞれ格納するビデオ メモリに複数の VBO が存在する場合があります。では、OpenGL はどのようにしてこれらのデータを正しく検出し、レンダリング時にシェーダーにフィードするのでしょうか?

ここに画像の説明を挿入
この質問に対する答えは実際には VAO ですが、この質問を説明する前に、GPU がデータを正しく取得するためにどのような情報を知る必要があるかを見てみましょう。

引き続き三角形を描画し、頂点シェーダーは頂点情報と色情報を入力します。そのソース コードは大まかに次のようになります。

const char *kVertexShaderSource = R"(
    #version 330
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aColor;

    out vec3 ourColor;

    void main()
    {
    
    
        gl_Position = vec4(aPos, 1.0);
        ourColor = aColor;
    }
)";

頂点データに位置と色が含まれており、それらすべてが VBO に配置されていると仮定すると、このように配置できます。最初にすべて xyz を保存し、次にすべて rgb を保存し、覚えやすい名前を付けます。タイプ:

x0 y0 z0 x1 y1 z1 x2 y2 z2 r0 g0 b0 r1 g1 b1 r2 g2 b2

また、最初の点の xyz と RGB を保存し、次に 2 番目の点、というように保存することもできます. この種の名前はインターリービングとも呼ばれます:

x0 y0 z0 r0 g0 b0 x1 y1 z1 r1 g1 b1 x2 y2 z2  r2 g2 b2

これらはどちらもデータを保存するのに妥当な場所であり、両方のレイアウトをサポートできるほど柔軟なインターフェイスを提供する必要があります。

GPU の場合、 VBO から頂点属性を取得する擬似コードは、おおよそ次のようになります。

void* vbo = some_address;
const int num_vertex = 3;
const int vertex_pos_index = 0;
const int vertex_rgb_index = 1;

for(int i = 0; i < num_vertex; ++i)
{
    
    
	vec3_float xyz = getDataFromVBO(vbo, i, ...);
	vec3_float rgb = getDataFromVBO(vbo, i, ...);
	auto result = vertex_shader(xyz, rgb);
}
// ...

の:

  • vboビデオ メモリのアドレスを指します。おなじみの C ポインターと考えてください。
  • vertex_pos_index = 0およびvertex_rgb_index = 1、シェーダー ソース コードのlayout (location = 0) in vec3 aPosおよびlayout (location = 1) in vec3 aColor必要な頂点属性を示します
  • vboから頂点の位置情報と色情報を取得するgetDataFromVBOことでi
  • vertex_shader頂点位置情報と色情報の2つのパラメータを入力

Now, you have to think about how to implement getDataFromVBOthe function . 簡単にするために、vbo はすべての float 型データを格納し、vec3_float データを (サイズ 3 の std::vector として) 返すと仮定します。上記の 2 つのデータ レイアウトと互換性を持たせるために、ストライドとオフセットのパラメーターを導入します。getDataFromVBO実装はおおよそ次のようになります。

vec3_float getDataFromVBO(VBO vbo, int vertex_index, int stride, int offset)
{
    
    
	const int num_float_in_vec3 = 3;
	float* begin = (float*)(vbo) + offset;	// 起始位置偏移
	const int vertex_offset = vertex_index * stride; // 第 i 个顶点属性的获取位置
	vec3_float result = vec3_float{
    
    begin + vertex_offset, begin + vertex_offset + num_float_in_vec3}
	return result;
}

offsetパラメータ、つまりオフセットはよく理解されています。次の表に、必要なさまざまな種類の頂点位置情報 (xyz) と色情報 (rgb) を示します。offset

位置データ カラーデータ
フラットタイプ 0 9
インターレース 0 3
  • フラット タイプの場合、最初の頂点位置のオフセット (x0) は 0、最初の頂点カラーのオフセット (r0) は 9 です。
  • インターレース モードでは、最初の頂点位置のオフセット (x0) は 0、最初の頂点カラーのオフセット (r0) は 3 です。

strideパラメータは「ステップ サイズ」を意味します。これは、次のデータを取得するためにドメインを横断する必要があるユニットの数を指します。次の表に、必要なさまざまな種類の頂点位置情報 (xyz) と色情報 (rgb) を示します。stride

位置データ カラーデータ
フラットタイプ 3 3
インターレース 6 6
  • プレーン タイプでは、現在の頂点位置から次の頂点位置までが x0 から x1 のように 3 つの単位にまたがる必要があり、その間に 3 つのデータが含まれている必要があります (カラー データについてstrideも同様です
  • インターレース モードでは、現在の頂点位置から次の頂点位置までが 6 ユニット (x0 から x1 など) にまたがる必要があり、その間に 6 つのデータが含まれます。strideこれはカラー データにも当てはまります

strideoffsetパラメータを使用すると、2 つの異なる順列を適切に処理できます。これで、頂点の位置を取得するか、色を取得するかに応じて、さまざまなパラメーターを設定することで vbo からスムーズにデータを取得できます。擬似コードは次のように更新されます。

void* vbo = some_address;
const int num_vertex = 3;
const int vertex_pos_index = 0;
const int vertex_index_0_offset = 0; // 平面型为 0,交织型为 0
const int vertex_index_0_stride = 3; // 平面型为 3,交织型为 6

const int vertex_rgb_index = 1;
const int vertex_index_1_offset = 9	 // 平面型为 9,交织型为 3
const int vertex_index_1_stride = 3; // 平面型为 3,交织型为 6

for(int i = 0; i < num_vertex; ++i)
{
    
    
	vec3_float xyz = getDataFromVBO(vbo, i, 
			vertex_index_0_stride,
			vertex_index_0_offset);
	vec3_float rgb = getDataFromVBO(vbo, i, 
			vertex_index_1_stride,
			vertex_index_1_offset);
	auto result = vertex_shader(xyz, rgb);
}

3.3 さらに先へ

先ほど説明したのは、実際にはglVertexAttribPointer関数。改良に移り、疑似コードを近づけましょうglVertexAttribPointer

まず、前の疑似コードでは、デフォルトで vec3 を取得します。実際の使用シナリオでは、すべての頂点属性が必ずしも vec3 であるとは限りません。場合によっては vec4 または vec2、さらには単一の float でさえあります。したがって、属性の数をsizeこの、以下を取得します。

vecn_float getDataFromVBO(VBO vbo, int vertex_index, int size, int stride, int offset)
{
    
    
	float* begin = (float*)(vbo) + offset;	// 起始位置偏移
	const int vertex_offset = vertex_index * stride; // 第 i 个顶点属性的获取位置
	vecn_float result = vec3_float{
    
    begin + vertex_offset, begin + vertex_offset + size}
	return result;
}

次に、頂点属性は必ずしもfloattype、 typeintである可能性がありますbool型を新しいパラメータとして抽象化し、type

enum DataType
{
    
    
	GL_BYTE, 
	GL_SHORT, 
	GL_INT,
	GL_FLOAT,
}
vecn getDataFromVBO(VBO vbo, int vertex_index, int size, DataType type, int stride, int offset)
{
    
    
	type* begin = (type*)(vbo) + offset;	// 起始位置偏移
	const int vertex_offset = vertex_index * stride; // 第 i 个顶点属性的获取位置
	vecn result = vecn{
    
    begin + vertex_offset, begin + vertex_offset + size}
	return result;
}

最後に、より一般的に言うと、strideと の両方をoffset

vecn getDataFromVBO(VBO vbo, int vertex_index, int size, DataType type, int stride, int offset)
{
    
    
	void* begin = vbo + offset;	// 起始位置偏移
	const int vertex_offset = vertex_index * stride; // 第 i 个顶点属性的获取位置
	const int vertex_size = sizeof(tpye) * size;
	vecn result = vecn{
    
    begin + vertex_offset, begin + vertex_offset + vertex_size}
	return result;
}

上記の調整の後、vbo から頂点データを取得するための擬似コードは次のように更新されます。

void* vbo = some_address;
const int num_vertex = 3;
const int vertex_pos_index = 0;
const int vertex_index_0_size = 3;
const int vertex_index_0_type = GL_FLOAT;
const int vertex_index_0_offset = 0; 
const int vertex_index_0_stride = 3 * sizeof(float);

const int vertex_rgb_index = 1;
const int vertex_index_1_size = 3;
const int vertex_index_1_type = GL_FLOAT;
const int vertex_index_1_offset = 9 * sizeof(float)
const int vertex_index_1_stride = 3 * sizeof(float);

for(int i = 0; i < num_vertex; ++i)
{
    
    
	vec3_float xyz = getDataFromVBO(vbo, i, 
				vertex_index_0_size,
				vertex_index_0_type,
				vertex_index_0_stride,
				vertex_index_0_offset
				);
	vec3_float rgb = getDataFromVBO(vbo, i, 
				vertex_index_1_size,
				vertex_index_1_type,
				vertex_index_1_stride,
				vertex_index_1_offset 
				);
	auto result = vertex_shader(xyz, rgb);
}

4. VAOとVBOの関係

前の 3 つの章では、vbo から頂点属性データを取得し、それをシェーダーに送信してレンダリングするプロセスを整理しました.ビデオ メモリからデータを正常に取得したい場合は、系列を与える必要があることがわかりました。などを含むパラメーターの、sizeまた、strideどの vbo から取得するかを指定します。

レンダリングするモデルがたくさんある場合、モデルを使用する前にパラメーターを設定すると、プロセスが非常に面倒になります。これらを格納するためにオブジェクトを使用することは可能かどうか疑問に思ったので、VAO (Vertex Array Object) が登場しました。

OpenGL では、頂点属性配列の属性と位置を設定するglVertexAttribPointerために。レンダリングで使用するために、現在バインドされている VAO に頂点属性配列のデータ形式と位置を格納します。

疑似コードを使用して何glVertexAttribPointerをした、次のようになります。

// 定义一个glVertexAttribPointer函数
function glVertexAttribPointer(index, size, type, normalized, stride, offset) {
    
    
  // 获取当前绑定的VAO和VBO
  vao = glGetVertexArray();
  vbo = glGetBuffer();

  // 检查参数的有效性
  if (index < 0 or index >= MAX_VERTEX_ATTRIBS) {
    
    
    return GL_INVALID_VALUE;
  }
  if (size < 1 or size > 4) {
    
    
    return GL_INVALID_VALUE;
  }
  if (type not in [GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT]) {
    
    
    return GL_INVALID_ENUM;
  }
  if (stride < 0) {
    
    
    return GL_INVALID_VALUE;
  }
  
  // 将顶点属性数组的数据格式和位置存储在VAO中
  vao.vertexAttribs[index].enable = true;
  vao.vertexAttribs[index].size = size;
  vao.vertexAttribs[index].type = type;
  vao.vertexAttribs[index].normalized = normalized;
  vao.vertexAttribs[index].stride = stride;
  vao.vertexAttribs[index].offset = offset;
  vao.vertexAttribs[index].buffer = vbo;
}
  • まず、現在バインドされている vao と vbo を OpenGL コンテキストから取得します。
  • vao にvertexAttribs配列、index現在のプロパティをこの配列に設定します

はい、vao と vbo の関係は単純です。vao は、vbo からデータを取得する方法のパラメーターを記録します。

5. コードを理解する

コード レベルに戻って、最初に混乱させたコード スニペット、vao と vbo の使用を見てみましょう。

	GLuint VBO{
    
    0};
    GLuint VAO{
    
    0};
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)(9 * sizeof(float)));
    glEnableVertexAttribArray(1);

このコードの関数はすべて知っていますが、関数間の関係は不明です。glVertexAttribPointerたとえば、以前にバインドされた vao と vbo に適用されますが、関数パラメーターに反映がないため、このコードの理解に「誤り」が生じます。主な理由は、OpenGL コンテキスト プロパティへの変更とアクセスが OpenGL API の背後に隠されているためです。この部分がどのように実装されているかは不明です。

ここで、このコードをよりよく理解するために、疑似コードを使用して各関数が何をするかを説明してみてください。

class OpenGLContext
{
    
    
public:
	const int max_num_vao = 256;
	const int max_num_buffers = 256;
	std::vector<Buffer> buffers(256);
	std::vector<VAO> vaos(256);

	VAO* current_vao;
	VBO* current_vbo;
}

// 全局的 OpenGL Context 对象
OpenGLContext context;
void glGenBuffers(GLsizei n, GLuint * buffers)
{
    
    
	static int count = 0;
	GLuint* index = new GLuint[n];
	for(int i = 0; i < n; ++i){
    
    
		index[i] = ++count;
	}
	
	for(int i = 0; i < n; ++i){
    
    
		// create_new_vao 创建一个新的 vao 对象
		context.buffers[index[i]] = create_new_buffer_ojbect();
	}
	buffers = index;
}

void glGenVertexArrays(	GLsizei n, GLuint * arrays)
{
    
    
	static int count = 0;
	
	GLuint* index = new GLuint[n];
	for(int i = 0; i < n; ++i){
    
    
		index[i] = ++count;
	}
	
	for(int i = 0; i < n; ++i){
    
    
		// create_new_vao 创建一个新的 vao 对象
		context.vaos[index[i]] = create_new_vao();
	}
	arrays = index;
} 

void glBindBuffer(GLenum target,GLuint buffer)
{
    
    
	if(target == GL_ARRAY_BUFFER){
    
    
		context.current_vbo = &context.buffers[buffer];
	}
	//....
}
void glBufferData(GLenum target,GLsizeiptr size, const void * data, GLenum usage)
{
    
    
	if(target == GL_ARRAY_BUFFER){
    
    
		copy_data_to_vbo(size, data, context.current_vbo);
	}
}

void glVertexAttribPointer(GLuint index,
 	GLint size,
 	GLenum type,
 	GLboolean normalized,
 	GLsizei stride,
 	const void * pointer)
{
    
    
  VBO* vbo = context.current_vbo;
  VAO* vao = context.current_vao;
  
	// 将顶点属性数组的数据格式和位置存储在VAO中
  vao.vertexAttribs[index].enable = true;
  vao.vertexAttribs[index].size = size;
  vao.vertexAttribs[index].type = type;
  vao.vertexAttribs[index].normalized = normalized;
  vao.vertexAttribs[index].stride = stride;
  vao.vertexAttribs[index].offset = offset;
  vao.vertexAttribs[index].buffer = vbo;
}

上記の疑似コードを通じて、OpenGL API の機能とそれらの間の接続についての一般的な理解を得る必要があります。ここで書くのは疲れたので、これ以上の説明や説明は書きませんが、頭のいい人なら理解できるはずです。

6. まとめ

この記事では、OpenGL を始めたばかりの初心者向けに、VAO と VBO の関係を説明します. 頂点シェーダーから始めて、レンダリング プロセスで頂点がどのように GPU に送られるかを説明し、次に vbo の概念を紹介します.実際にはビデオ メモリへのポインタです。メモリからビデオ メモリにデータをコピーするには、多くのパラメータを指定する必要があります。モデルをレンダリングするたびにパラメータを再指定する必要がある場合、プロセス全体が非常に面倒になります。 . vao オブジェクトはこれらのパラメーターを格納するために導入されているため、パラメーターのみを設定する必要があります.一度すべてを再利用できます.最後に、疑似コードを使用して、最初は混乱する OpenGL 関数を説明します.

おすすめ

転載: blog.csdn.net/weiwei9363/article/details/128989702