Androidアプリ開発 OpenGL VBO(Vertex Buffer Object)で高速化 [Android OpenGL]
本日は、VBOの話をしようと思う。
VBOはVertex Buffer Object。頂点データをバッファオブジェクトでGPU側で管理する方法。Android端末には、CPUとGPUが搭載されており、どちらも計算を行うのだが、GPUはグラフィックス周りの計算に特化している。
OpenGLによる3Dグラフィックスは、GPUを使って計算処理されることが多い。
GPUはグラフィックスの計算に特化しているので、グラフィックス処理ならCPUより高速に実行できる。ハードウェア回路で必要な計算ができるようになっている。
CPUとGPUの違いは、グラフィックス処理に特化しているかどうかだけ。基本的なしくみは変わらない。CPU、GPUの内部には計算のためにレジスタが存在しているが、レジスタの数は多くないし、容量も少ない。大量のデータは、メモリに保存されていて、計算する際にレジスタに持ってくる。
CPUとメモリの関係は、「ポインタが理解できない理由」に詳しく書いてあるので、興味があればどうぞ。
多くのデスクトップPCでは、CPUが使うメモリとGPUが使うメモリが別になっている。特にゲームをするために、ビデオカードを拡張している場合は、ビデオカードにGPUとGPUが使うメモリが搭載されている。
ノートPCや廉価版のPCでは、CPUにGPUが統合されたオールインワンのCPUが使われている場合がある。このタイプのCPUでは、メモリは同じ。GPUが使うメモリはメインメモリの一部が割り当てられる感じになる。
GPUが使うメモリは「フレームバッファ」または単に「バッファ」と呼ばれる。ビデオメモリとかVRAMと呼ばれる場合もある。
Android端末にもGPUは搭載されている。OpenGLの3D機能を使えば、おのずとGPUが使われる。しかし、OpenGLを使わない普通のCanvasでの描画では、GPUは使用されていない模様。なんともったいない。
ちょっと調べてみる
http://techbooster.jpn.org/andriod/application/7054/
によると、Android 3.0以降なら、Cnavas描画でもGPUアクセラレーションが可能であるらしい。
AndroidManifest.xmlでandroid:hardwareAcceleratedの設定が必要なのか。
2.3だと無視されるのかなぁ...
AREarthroidで実験してみたが、あまり変化はない。3Dの部分はそのままだからなぁ...
2.3だと、Canvas#isHardwareAcceleratedメソッドが存在しないエラーになる。3.0以降じゃないとだめ。
VBO
やっと、本題のVBOである。
VBOを使わない場合、頂点データや法線データは、メインメモリ上にある。描画する際は、GPUを使うので、頂点データは、GPU側のメモリにコピーされる。コピーは描画の度に行われる。
VBOを使うと、メインメモリからGPU側のバッファに移動されて常駐するようになる。描画の度にコピーする必要がなくなるので、その分高速に描画できる。
バッファに頂点データというオブジェクトを作るので、Vertex Buffer Objectとなる。
「VBOを使うと描画を高速化できる」というわけ。
ただし、OpenGL 1.1の機能になるため、Android端末のバージョンによっては使用できない。
さて、実際のコードはどうなるのかというと、AREarthroidから一部を抜いてみよう。
まず、glGenBuffersでバッファオブジェクトを作成する。バッファオブジェクトは番号で識別されるので、これを受けるためのint配列を渡してやる。最初の引数は作成するオブジェクトの個数。第二引数が、番号の配列。第三引数はオフセット。
これで、4つのオブジェクトが作成される。オブジェクトの番号が配列vboIdsに入って戻ってくる。このまま使っても問題はないが、わかりやすくするために、フィールド変数で記憶する。
vertexBufferObjectIdが、頂点データの入ったオブジェクトの番号。
normalBufferObjectIdが、法線データの入ったオブジェクトの番号。
以下は省略。
次に、バッファオブジェクトにデータを突っ込んでいく。CPU側のメモリからGPU側のメモリにデータをコピーする。
glBindBufferで、どのバッファを使うのかを番号で指定する。
vertexByteBufferが頂点データの入ったCPU側メモリのバッファ。先頭からデータが入るように、position(0)でポインタの位置を先頭にしている。
glBufferDataで、vertexByteBufferのデータがGPU側のVBOに転送される。
GL_STATIC_DRAWは、バッファオブジェクトの用途。
同様な手法で、法線データ、テクスチャマッピングデータ、面データもVBOにしてGPU側にアップロードする。面データは、バッファの種類が異なるので注意。
VBOはこれで完成。CPU側メモリのバッファは不要になるので、nullをセット。
次に、VBOを使って描画する方法。
VBOを使わない場合は、glVertexPointerでCPU側メモリのバッファを指定すればよかったが、VBOの場合は、glBindBufferでVBOの番号を指定してから、glVertexPointerでポインタを設定する。
useVBOはVBOを使用しているかどうがのフラグ。if文のtrueブロックがVBOを使った場合の処理になる。
法線データ、テクスチャ、面データも同様に処理を行う。
glDrawArraysなどの描画関数呼び出しは変更しなくてOK。
glDrawElementsは、indexBufferの指定が、オブジェクトになるので、GL11にあるglDrawElementsを使う。
バッファオブジェクトは、使用されなくなったら、メモリから削除できる。削除は、glDeleteBuffersで行うことができる。
引数は、glGenBuffersと同じ。
GLThreadから呼び出さないと無効なので注意。
サイト内を検索
VBOはVertex Buffer Object。頂点データをバッファオブジェクトでGPU側で管理する方法。Android端末には、CPUとGPUが搭載されており、どちらも計算を行うのだが、GPUはグラフィックス周りの計算に特化している。
OpenGLによる3Dグラフィックスは、GPUを使って計算処理されることが多い。
GPUはグラフィックスの計算に特化しているので、グラフィックス処理ならCPUより高速に実行できる。ハードウェア回路で必要な計算ができるようになっている。
CPUとGPUの違いは、グラフィックス処理に特化しているかどうかだけ。基本的なしくみは変わらない。CPU、GPUの内部には計算のためにレジスタが存在しているが、レジスタの数は多くないし、容量も少ない。大量のデータは、メモリに保存されていて、計算する際にレジスタに持ってくる。
CPUとメモリの関係は、「ポインタが理解できない理由」に詳しく書いてあるので、興味があればどうぞ。
C言語 ポインタが理解できない理由 [改訂新版] (プログラミングの教科書)
- 作者: 朝井 淳
- 出版社/メーカー: 技術評論社
- 発売日: 2011/04/08
- メディア: 単行本(ソフトカバー)
多くのデスクトップPCでは、CPUが使うメモリとGPUが使うメモリが別になっている。特にゲームをするために、ビデオカードを拡張している場合は、ビデオカードにGPUとGPUが使うメモリが搭載されている。
ノートPCや廉価版のPCでは、CPUにGPUが統合されたオールインワンのCPUが使われている場合がある。このタイプのCPUでは、メモリは同じ。GPUが使うメモリはメインメモリの一部が割り当てられる感じになる。
GPUが使うメモリは「フレームバッファ」または単に「バッファ」と呼ばれる。ビデオメモリとかVRAMと呼ばれる場合もある。
Android端末にもGPUは搭載されている。OpenGLの3D機能を使えば、おのずとGPUが使われる。しかし、OpenGLを使わない普通のCanvasでの描画では、GPUは使用されていない模様。なんともったいない。
ちょっと調べてみる
http://techbooster.jpn.org/andriod/application/7054/
によると、Android 3.0以降なら、Cnavas描画でもGPUアクセラレーションが可能であるらしい。
AndroidManifest.xmlでandroid:hardwareAcceleratedの設定が必要なのか。
2.3だと無視されるのかなぁ...
AREarthroidで実験してみたが、あまり変化はない。3Dの部分はそのままだからなぁ...
2.3だと、Canvas#isHardwareAcceleratedメソッドが存在しないエラーになる。3.0以降じゃないとだめ。
VBO
やっと、本題のVBOである。
VBOを使わない場合、頂点データや法線データは、メインメモリ上にある。描画する際は、GPUを使うので、頂点データは、GPU側のメモリにコピーされる。コピーは描画の度に行われる。
VBOを使うと、メインメモリからGPU側のバッファに移動されて常駐するようになる。描画の度にコピーする必要がなくなるので、その分高速に描画できる。
バッファに頂点データというオブジェクトを作るので、Vertex Buffer Objectとなる。
「VBOを使うと描画を高速化できる」というわけ。
ただし、OpenGL 1.1の機能になるため、Android端末のバージョンによっては使用できない。
さて、実際のコードはどうなるのかというと、AREarthroidから一部を抜いてみよう。
int[] vboIds = new int[4]; GL11 gl11 = (GL11) gl; gl11.glGenBuffers(4, vboIds, 0); // バッファを4つ作成 vertexBufferObjectId = vboIds[0]; normalBufferObjectId = vboIds[1]; textureBufferObjectId = vboIds[2]; elementBufferObjectId = vboIds[3]; // 頂点データをGPUにアップロード gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexBufferObjectId); vertexByteBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, vertexByteBuffer.capacity(), vertexByteBuffer, GL11.GL_STATIC_DRAW); // 法線 gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, normalBufferObjectId); normalByteBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, normalByteBuffer.capacity(), normalByteBuffer, GL11.GL_STATIC_DRAW); // テクスチャ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, textureBufferObjectId); textureByteBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, textureByteBuffer.capacity(), textureByteBuffer, GL11.GL_STATIC_DRAW); // 面データもアップロード gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, elementBufferObjectId); indexBuffer.position(0); gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity() * SHORT_SIZE, indexBuffer, GL11.GL_STATIC_DRAW); // CPU側メモリのバッファはもういらない vertexBuffer = null; vertexByteBuffer = null; normalBuffer = null; normalByteBuffer = null; textureBuffer = null; textureByteBuffer = null; indexBuffer = null;
まず、glGenBuffersでバッファオブジェクトを作成する。バッファオブジェクトは番号で識別されるので、これを受けるためのint配列を渡してやる。最初の引数は作成するオブジェクトの個数。第二引数が、番号の配列。第三引数はオフセット。
gl11.glGenBuffers(4, vboIds, 0); // バッファを4つ作成
これで、4つのオブジェクトが作成される。オブジェクトの番号が配列vboIdsに入って戻ってくる。このまま使っても問題はないが、わかりやすくするために、フィールド変数で記憶する。
vertexBufferObjectId = vboIds[0]; normalBufferObjectId = vboIds[1]; textureBufferObjectId = vboIds[2]; elementBufferObjectId = vboIds[3];
vertexBufferObjectIdが、頂点データの入ったオブジェクトの番号。
normalBufferObjectIdが、法線データの入ったオブジェクトの番号。
以下は省略。
次に、バッファオブジェクトにデータを突っ込んでいく。CPU側のメモリからGPU側のメモリにデータをコピーする。
// 頂点データをGPUにアップロード gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexBufferObjectId); vertexByteBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, vertexByteBuffer.capacity(), vertexByteBuffer, GL11.GL_STATIC_DRAW);
glBindBufferで、どのバッファを使うのかを番号で指定する。
vertexByteBufferが頂点データの入ったCPU側メモリのバッファ。先頭からデータが入るように、position(0)でポインタの位置を先頭にしている。
glBufferDataで、vertexByteBufferのデータがGPU側のVBOに転送される。
GL_STATIC_DRAWは、バッファオブジェクトの用途。
同様な手法で、法線データ、テクスチャマッピングデータ、面データもVBOにしてGPU側にアップロードする。面データは、バッファの種類が異なるので注意。
VBOはこれで完成。CPU側メモリのバッファは不要になるので、nullをセット。
次に、VBOを使って描画する方法。
VBOを使わない場合は、glVertexPointerでCPU側メモリのバッファを指定すればよかったが、VBOの場合は、glBindBufferでVBOの番号を指定してから、glVertexPointerでポインタを設定する。
// 頂点バッファ設定 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); if ( useVBO ){ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexBufferObjectId); gl11.glVertexPointer(3, GL10.GL_FLOAT, 12, 0); } else { gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer); }
useVBOはVBOを使用しているかどうがのフラグ。if文のtrueブロックがVBOを使った場合の処理になる。
法線データ、テクスチャ、面データも同様に処理を行う。
glDrawArraysなどの描画関数呼び出しは変更しなくてOK。
glDrawElementsは、indexBufferの指定が、オブジェクトになるので、GL11にあるglDrawElementsを使う。
if ( useVBO ){ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, elementBufferObjectId); gl11.glDrawElements(GL10.GL_TRIANGLES, elementCount * 3, GL10.GL_UNSIGNED_SHORT, 0); } else { gl.glDrawElements(GL10.GL_TRIANGLES, indexBuffer.capacity(), GL10.GL_UNSIGNED_SHORT, indexBuffer); }
バッファオブジェクトは、使用されなくなったら、メモリから削除できる。削除は、glDeleteBuffersで行うことができる。
gl11.glDeleteBuffers(4, vboIds, 0);
引数は、glGenBuffersと同じ。
GLThreadから呼び出さないと無効なので注意。
かんたんAndroidアプリ作成入門 (プログラミングの教科書)
- 作者: 朝井 淳
- 出版社/メーカー: 技術評論社
- 発売日: 2013/04/16
- メディア: 単行本(ソフトカバー)
サイト内を検索
Copyright Atsushi Asai Google+朝井淳
[データベースの気持ちがわかる]SQLはじめの一歩 (WEB+DB PRESS plus)
- 作者: 朝井 淳
- 出版社/メーカー: 技術評論社
- 発売日: 2015/03/03
- メディア: 単行本(ソフトカバー)
Access クエリ 徹底活用ガイド ~仕事の現場で即使える
- 作者: 朝井 淳
- 出版社/メーカー: 技術評論社
- 発売日: 2018/05/25
- メディア: 大型本
コメント 0