Androidアプリ開発 OpenGL デプステスト

本題とはずれるが、syntax highlighterをバージョンアップしてみた。気が付かれたであろうか。行の強調表示をしたかったので。highlight:[1,2]とかやっても、全然強調表示されないので、おかしいなぁ...と思ったらバージョンが1.5とか古すぎだったようである。
textareaじゃなくて、preにしないといけなくなり、全部変更するのが大変だった。
さて、本題に移ろう。前回、視点を変更することができた。少し、右上から見ている感じになった。しかし、「なんかおかしい」ところがあるのに気付かれたであろうか。
もう一回、みてみよう。

視点が、右側にあるので、右の三角形の方が近くにある。そのため、遠近法で大きく表示されている(これはおかしくない)。しかしである。右と左の三角形がちょうど重なる位置にくると、左の三角形の方が手前にあるかのように表示されてしまう。
どうしてこのようなことになるかというと、三角形の描画の順番にある。後で描画された三角形の方が「画面に上書き」されてしまうのである。
じゃあ、三角形を描く「順番を変えたらちゃんと表示できるか」っていうと、まぁそのとおり。最初に左の三角形を描いてやれば、右の方が後で描かれるようになるので、ちゃんと隠れて表示される。しかしである、よくよく考えてみて欲しい。視点を左側に移動したら...
そう、順番は計算で決めてやらなければならない。視点とモデルの距離を計算して、大きい順にソートして大きい方から順番に描いていけばOK。だが、めんどうである。
OpenGLでそのような機能があればよいのだが、残念ながら、そのような便利機能は、ない。やるのなら自分でコードを書いてやらないといけない。まぁ、ソートなので、やればできなくもないが... モデル単位にやるのであれば、よいが、ポリゴン単位でやるとなるとけっこう大変かも。
デプステスト
実は、ソートするより簡単な方法がある。「デプステスト」を使えばよいのである。デプステストは、デプスバッファを使って、隠れている面を表示しないテクニック。カリングの「陰面消去」と似ているが別のテクニック。
しくみは単純。ポリゴンを描画すると画面(フレームバッファ)にピクセル(点)が描画されるわけだが、このときデプスバッファにもこのポリゴンの深度も書き込むようにする。他のポリゴンを描画するときに、デプスバッファの深度と今描画しようとしている深度を比較して、「小さい=手前にある」場合にピクセルの書き込みを行う(そう、単純でもなかったか)。
深度(デプス)をバッファで記憶しておく、っていうのがミソかなぁ。深度っていっているのは、視点からの距離。バッファは、メモリ領域のことなんだけど、フレームバッファは画面の表示用のメモリ領域。デプスバッファは深度が記憶されているメモリ領域っていうこと。フレームバッファに書き込まれるとそれがそのまま、画面に表示される。デプスバッファの内容は表示されないが、隠れている面を表示しないことに使用される。
OpenGLでの「テスト」というのは、試験してみて描画するかどうか決める、といったもの。OpenGLではデプステストの他アルファテストっていうのもある。
説明が長くなってしまったが、実際にやってみることにしよう。
まず、デプスバッファを使います、と宣言する必要がある。これは、GLSurfaceViewでレンダラーをセットする前でsetEGLConfigChooserを使って行う。

// サーフェースビューのコンストラクタ
public SampleGLSurfaceView(Context context) {
super(context);
setEGLConfigChooser(8, 8, 8, 8, 16, 0);
renderer = new OpenGLRenderer();
setRenderer(renderer);
}

これまた、引数がたくさんあるが、最初の4つはフレームバッファのサイズ指定。RGBAの各サイズを決定している。アルファチャネルは今のところ使っていないが、ぴったりの設定が存在しないと選択してくれないため、ごく一般的と思われる、RGBA8888を指定している。
次の16がデプスバッファのサイズ。単位はビットである。16ビットの大きさっていうことです。その次は、ステンシルバッファのサイズ。ここは今のところ使用しないので0とした。
デプスバッファの確保はこれでよし。次に、デプステストを行って描画するように指示をする。これは、glEnableでGL_DEPTH_TESTを有効にすればOK。デプステストでは、どのような場合にテストに合格するかを指定できる。普通は、視点に近い場合に描画する、なので、GL_LEQUALを指定すればよい。これがデフォルト。
さらに、描画時にデプスバッファを更新しないといけない。これを指示するスイッチがある。glDepthMaskで指定する。trueにすると書き込みが行われる。
おっと、いけない。デプスバッファをクリアするのを忘れていた。フレームを描き始める前に、glClearでバッファをクリアしないといけない。今までは、フレームバッファのRGBAの部分を背景色(黒)にクリアするのみであった。glClearは引数を指定すれば、デプスバッファをクリアすることもできる。GL_DEPTH_BUFFER_BITも付け加えてデプスバッファをクリアしよう。
クリアする値は、背景色と同様に、onSurfaceCreatedで指定することにする。

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f);
}

onDrawFrameで、デプスバッファのクリア、GL_DEPTH_TESTの有効化を行う。

@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// カメラ位置を設定
GLU.gluLookAt(gl, 0, 0, 3, 0, 0, 0, 0, 1, 0);
// ライティングをON
gl.glEnable(GL10.GL_LIGHTING);
// 光源を有効にして位置を設定
gl.glEnable(GL10.GL_LIGHT0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightpos, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, red, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, blue, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, yellow, 0);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();		// マトリックス記憶
gl.glTranslatef(1, 0, 0);
gl.glRotatef(angle, 0, 1, 0);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glCullFace(GL10.GL_BACK);
// デプステスト
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glDepthMask(true);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
gl.glShadeModel(GL10.GL_FLAT);
model.draw(gl);
gl.glPopMatrix();		// マトリックスを戻す
// ふたつめの描画
gl.glPushMatrix();		// マトリックス記憶
gl.glTranslatef(-1, 0, 0);
gl.glRotatef(angle, 0, 1, 0);
gl.glCullFace(GL10.GL_BACK);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
// スムースシェーディング
gl.glShadeModel(GL10.GL_SMOOTH);
model.draw(gl);
gl.glPopMatrix();		// マトリックスを戻す
angle += 0.5;			// 回転角度は最後に計算
}


ちゃんと表示されるようになった。

投稿者プロフィール

asai
asai
システムエンジニア
喋れる言語:日本語、C言語、SQL、JavaScript