AndroidのGLSurfaceViewにおけるOpenGL ES 1.0と1.1のひな形

とりあえずOpenGL ESで3Dのモデルを表示する時の手順です。AndroidのGLSurfaceViewを想定していますが、iOSでも参考になるかもしれません。

インデントがおかしいのはWordpressの仕様なので我慢してください。import文等は省略しています。

■GLSurfaceViewの作り方

public class MainActivity extends Activity {

 GLSurfaceView glView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 //setContentView(R.layout.activity_main);

 glView = new GLSurfaceView(this);
 // デバッグ時には次のフラグを立てると自動でエラーチェックされる(ただし遅い)
 glView.setDebugFlags(GLSurfaceView.DEBUG_CHECK_GL_ERROR);
 // OpenGL ES 2.0互換のEGLを選択するときは次のようにする
 // 注意:AndroidManifestにuses-featureが必要
 //glView.setEGLContextClientVersion(2);

 glView.setRenderer(new GLTest(this));
 setContentView(glView);
 }

 @Override
 public void onResume() {
 super.onResume();
 glView.onResume();
 }

 @Override
 public void onPause() {
 super.onPause();
 glView.onPause();
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
 getMenuInflater().inflate(R.menu.activity_main, menu);
 return true;
 }

}

■OpenGL ES 1.0

頂点バッファは1つだけです。1頂点ごとに座標とUV(厳密にはUVではなくST)が入っているものとします(調べた所、多くのサンプルは座標で1バッファ、UVで1バッファそれぞれ用意していますが、今時そのような使い方はあまりしないと思います)。法線やライティングはなしです。インデックスバッファを使っています(トライアングルリスト)。マテリアルはテクスチャ以外無視します(テクスチャも要らなければnullにすればとりあえず動きます)。

8千頂点、1.5万面(4.5万ポリゴン)のとある3Dモデルを動かしました(残念ながら借り物なので著作権上公開できません)。

class GLTest implements GLSurfaceView.Renderer {

 int winWidth_;
 int winHeight_;

 Activity act_; // リソースの読み込みに使うアクティビティ
 int[] texID_; // テクスチャID
 FloatBuffer modelVtx_; // 頂点バッファ
 ShortBuffer modelIdx_; // インデックスバッファ

 GLTest(Activity act) {
 act_ = act;
 InputStream is = act.getResources().openRawResource(R); // モデルデータ読み込み
 // 頂点バッファをmodelVtx_に、インデックスバッファをmodelIdx_に格納
 // 各バッファはByteBuffer.allocateDirect([バイト数]).as***Buffer()で生成する
 }

 @Override
 public void onDrawFrame(GL10 gl) {
 int glerr = GL10.GL_NO_ERROR;

 // モデル・ビュー変換
 gl.glMatrixMode(GL10.GL_MODELVIEW);
 gl.glLoadIdentity();
 GLU.gluLookAt(gl, 0, 0, -20, 0, 0, 0, 0, 1, 0); // 視点の指定

 // 射影変換
 gl.glMatrixMode(GL10.GL_PROJECTION);
 gl.glLoadIdentity();
 GLU.gluPerspective(
 gl, 45.0f,
 (float)winWidth_ / winHeight_, 0.2f, 120.0f); // 透視変換の指定

 // ビューポート
 gl.glViewport(0, 0, winWidth, winHeight);

 // レンダーターゲットのクリア
 gl.glClearColor(0.1f, 0.2f, 0.4f, 1.0f);
 gl.glClear(
 GL10.GL_DEPTH_BUFFER_BIT | GL10.GL_STENCIL_BUFFER_BIT
 | GL10.GL_COLOR_BUFFER_BIT);

 // (必要に応じて)カリングの指定(有効のほうが好ましい)
 gl.glDisable(GL10.GL_CULL_FACE);
 //gl.glFrontFace(GL10.GL_CCW);
 //gl.glCullFace(GL10.GL_BACK);

 // 深度バッファの有効・無効指定
 gl.glEnable(GL10.GL_DEPTH_TEST);

 // (必要に応じて)アルファブレンドの指定(無効のほうが好ましい)
 //gl.glEnable(GL10.GL_ALPHA);

 // テクスチャの有効・無効指定
 gl.glEnable(GL10.GL_TEXTURE_2D);

 // UV座標配列と頂点座標配列を有効化
 gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

 // UV座標配列の指定
 //gl.glActiveTexture(GL10.GL_TEXTURE0);
 gl.glBindTexture(GL10.GL_TEXTURE_2D, texID_[0]);
 modelVtx_.position(3); // UVが頂点バッファのどの要素番号から始まるかを指定
 gl.glTexCoordPointer(2, GL10.GL_FLOAT, STRIDE, modelVtx_); // STRIDEは1頂点あたりのバイト数

 // 頂点座標配列の指定
 modelVtx_.position(0); // 座標が頂点バッファのどの要素番号から始まるかを指定
 gl.glVertexPointer(3, GL10.GL_FLOAT, STRIDE, modelVtx_);

 // インデックスバッファの指定
 modelIdx_.position(0);
 gl.glDrawElements(
 GL10.GL_TRIANGLES, modelIdx_.limit(),
 GL10.GL_UNSIGNED_SHORT, modelIdx_);

 // コマンドのフラッシュとエラーチェック
 gl.glFlush();
 if((glerr = gl.glGetError()) != GL10.GL_NO_ERROR) {
 String msg = "OpenGLでエラー@onDrawFrame : " + glerr;
 Log.e("GLTest", msg);
 }
 }

 @Override
 public void onSurfaceChanged(GL10 gl, int width, int height) {
 winWidth_ = width;
 winHeight_ = height;
 }

 @Override
 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
 int glerr = GL10.GL_NO_ERROR;

 // テクスチャの読み込み
 Bitmap bmp = BitmapFactory.decodeResource(act_.getResources(), R);
 texID_ = new int[1];
 gl.glGenTextures(texID_.length, texID_, 0);
 gl.glBindTexture(GL10.GL_TEXTURE_2D, texID_[0]);
 GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);

 // エラーチェック
 if((glerr = gl.glGetError()) != GL10.GL_NO_ERROR) {
 String msg = "OpenGLでエラー@onSurfaceCreated : " + glerr;
 Log.e("GLTest", msg);
 }
 }
}

■OpenGL ES 1.1

頂点バッファオブジェクト(VBO)とインデックスバッファオブジェクト(OpenGLでの正式な言い方が分からないorz)を使います。インデックスバッファはOpenGLではElelemt Array Bufferというらしいです。

class GLTest implements GLSurfaceView.Renderer {

 int winWidth_;
 int winHeight_;

 Activity act_;
 int[] vboID_; // VBO
 int[] iboID_; // インデックスバッファ
 int[] texID_;
 FloatBuffer modelVtx_;
 ShortBuffer modelIdx_;

 GLTest(Activity act) {
 // モデル読み込みは同じなので省略
 }

 @Override
 public void onDrawFrame(GL10 gl) {
 int glerr = GL10.GL_NO_ERROR;
 GL11 gl11 = (GL11)gl; // OpenGL ES 1.1 APIを使う

 // 座標変換や初期化は同じなので省略

 // UV座標配列の代わりにVBOを指定
 gl.glBindTexture(GL10.GL_TEXTURE_2D, texID_[0]);
 gl11.glTexCoordPointer(2, GL10.GL_FLOAT, PmdReader.PMD_VERTEX_STRIDE, 4 * 3);

 // 頂点座標配列の代わりにVBOを指定
 gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, vboID_[0]);
 gl11.glVertexPointer(3, GL10.GL_FLOAT, PmdReader.PMD_VERTEX_STRIDE, 0);

 // インデックスバッファの指定
 gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, iboID_[0]);
 gl11.glDrawElements(
 GL10.GL_TRIANGLES, modelIdx_.limit(),
 GL10.GL_UNSIGNED_SHORT, 0);

 gl.glFlush();
 if((glerr = gl.glGetError()) != GL10.GL_NO_ERROR) {
 String msg = "OpenGLでエラー@onDrawFrame : " + glerr;
 Log.e("GLTest", msg);
 }
 }

 @Override
 public void onSurfaceChanged(GL10 gl, int width, int height) {
 winWidth_ = width;
 winHeight_ = height;
 }

 @Override
 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
 int glerr = GL10.GL_NO_ERROR;
 GL11 gl11 = null;

 // OpenGL ES 1.1をサポートしていない環境を弾く
 if(!(gl instanceof GL11)) {
 throw new UnsupportedOperationException("OpenGL ES 1.1 cannot use");
 }
 gl11 = (GL11)gl;

 // VBOを作る
 vboID_ = new int[1];
 gl11.glGenBuffers(1, vboID_, 0);
 gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, vboID_[0]);
 gl11.glBufferData(
 GL11.GL_ARRAY_BUFFER, 4 * modelVtx_.limit(),
 modelVtx_, GL11.GL_STATIC_DRAW);

 // インデックスバッファを作る
 iboID_ = new int[1];
 gl11.glGenBuffers(1, iboID_, 0);
 gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, iboID_[0]);
 gl11.glBufferData(
 GL11.GL_ELEMENT_ARRAY_BUFFER, 2 * modelIdx_.limit(),
 modelIdx_, GL11.GL_STATIC_DRAW);

 // テクスチャ作成は同じなので省略

 if((glerr = gl.glGetError()) != GL10.GL_NO_ERROR) {
 String msg = "OpenGLでエラー@onSurfaceCreated : " + glerr;
 Log.e("GLTest", msg);
 }
 }
}

■VBOを使う利点

  1. 速い(噂では3割速い)
  2. Bufferのpositionをいちいち変更しなくていい

■ハマったところ

  1. モデルデータがバイナリ形式のとき、エンディアン(バイトオーダ)を合わせなければならない(ByteBuffer.order()で明示的にフォーマットのエンディアンを指定することで自動的に変換してくれる)
  2. ByteBufferはダイレクトバッファでなければならない。さもなくばOpenGLにデータが渡されない(allocateDirect()必須)
  3. ByteBufferにputなりwriteなりで書き終えたら、flip()で終点をマークしてポインタを先頭に戻さなければならない(rewind()やposition(0)でもよいが、終点マークはされない(らしい))
  4. 引数の単位を合わせる。度orラジアン、バイト数or要素数など(基本的には、OpenGLは度とバイト数、Bufferは要素数)

なお、この後別のモデルを表示したいときは、必要に応じてglBindBufferでバインドしたオブジェクトを解放してください(glBindBufferの第2引数に0を指定)。

ところで、GLSurfaceViewではglDeleteTexturesとかglDeleteBuffersとかは要らないんでしょうかね?

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中