Androidアプリ開発 OpenGL ワイヤーフレーム

さて、AREarthroidにパーティクルで、星を描画するような拡張することを考えていたが、星が表示されても。きれいなだけで何の役にも立たないっていうのじゃぁねぇ。実際の位置に星を表示できれば、「星座を探す」とかできそうではあるが、そういうのは、専用のアプリでやってもらおう。
どうせ拡張するのなら、役に立つ機能の方がいいでしょ。ということで、ワイヤーフレーム表示について調べてみるのである。点を描画することがポイントスプライトなら、ワイヤフレームは線を描画するテクニック。昔からある3DCGの手法。
ワイヤーフレームで何を表示するかというと、マーカーへのガイドを表示しようと思っている。で、かっこよく映画やアニメのような光っている感じのインジケーターっぽいのにしたいわけである。何をいっているかよくわからないであろうから、ちょっとyoutubeで探してみる。

こんな感じがいいかな。
で、緑色に半透明で光っている部分は、ワイヤーフレームで描いていると思われるので、どうやって描けばよいのかを調べるのである(微妙に角丸みたいになっているからテクスチャマッピングかも)。
OpenGLで線を描くだけなら、GL_LINESGL_LINE_STRIPで描画できる。しかしながら、GL_LINE_STRIPは「折れ線」を描くルーチンなので、連続した頂点データをそのまま繋いでいった線を描くだけなのです。
詳細は、床井先生のページを参照

えーと、それでサンプルではふたつの三角形で四角い面を作ってドロイド君をマッピングして表示しているが、これをそのままワイヤーフレームで表示しても、三角形が表示されてしまうわけです。
テストするには都合が悪いので、頂点数4のモデルを作ることにする。

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
public class WireFrame {
private FloatBuffer buffer;
private float x, y, z;
public WireFrame(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
float vertex[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f,  1.0f, 0.0f,
-1.0f,  1.0f, 0.0f,
};
ByteBuffer vb = ByteBuffer.allocateDirect(vertex.length * 4);
vb.order(ByteOrder.nativeOrder());
buffer = vb.asFloatBuffer();
buffer.put(vertex);
buffer.position(0);
}
public void draw(GL10 gl) {
gl.glPushMatrix();
gl.glTranslatef(x, y, z);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, buffer);
gl.glLineWidth(4.0f);
gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, 4);		// GL_LINE_LOOPで描画
gl.glPopMatrix();
}
}

GL_LINE_STRIPでは、最後の線が表示されないため、GL_LINE_LOOPで描画する。
ワイヤフレームでの線の太さは、glLineWidthで設定する。4ドットくらいは欲しいので、4を指定してみる。単位はドットらしい。
レンダラーのフィールドでインスタンスを記憶する。

public class SampleGLSurfaceView extends GLSurfaceView implements OnGestureListener {
private ArrayList<Model> models = new ArrayList<Model>();
private ArrayList<WireFrame> wireframes = new ArrayList<WireFrame>();

onSurfaceCreatedでワイヤーフレームのモデルを作成。

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f);
eyepos[0] = 0;
eyepos[1] = 0;
eyepos[2] = 3;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
// テクスチャを生成
int textures[] = new int[1];
gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
models.add(new Model(1, 0, 0));
models.add(new Model(-1, 0, 0));
models.add(new Model(1, 0, -2));
models.add(new Model(-1, 0, -2));
wireframes.add(new WireFrame(0, 0, 0f));
wireframes.add(new WireFrame(0, 0, 1f));
wireframes.add(new WireFrame(0, 0, 2f));
}
}

次に、onDrawFrameで描画する。
色は、ライティングしたいので、普通にアンビエント、ディフューズで設定してみる。
アルファブレンド、テクスチャはなし。

@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// ライティングを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, white, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, white, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, white, 0);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
// カメラ位置を設定
GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);
// スムースシェーディング
gl.glShadeModel(GL10.GL_SMOOTH);
// フォグ
gl.glEnable(GL10.GL_FOG);
gl.glFogfv(GL10.GL_FOG_COLOR, black, 0);
gl.glFogx(GL10.GL_FOG_MODE, GL10.GL_LINEAR);
gl.glFogf(GL10.GL_FOG_START, 3.0f);
gl.glFogf(GL10.GL_FOG_END, 10.0f);
// ソート
Collections.sort(models, new ModelComparator());
for ( Model m : models ){
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.glEnable(GL10.GL_TEXTURE_2D);
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
m.draw(gl);
// 2回目の表示はハイライトを出すため
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, transparent, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, half_white, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, half_white, 0);
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
m.draw(gl);
m.rotate();
}
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, white, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, white, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, white, 0);
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
gl.glDisable(GL10.GL_BLEND);
gl.glDisable(GL10.GL_TEXTURE_2D);
for ( WireFrame wireframe : wireframes ){
wireframe.draw(gl);
}
}

とここまでで、次のような表示ができた。

次は、カリングしてみる。
線だけなので、表、裏はないと思うが...

案の定、ドロイド君は反対を向くと表示されない。線は面ではないので、表裏がない。従って、カリングすることはできない。もちろん、面として法線データも作成して、それを線画で表示しているだけなら、カリングすることは可能であると思われる。
回転させてみてわかったのだが、ポイントスプライトのように、常にこちら向きということはないようである。回転により線の太さが変化している。
ポイントスプライトと同様に、遠近方は無視されている模様。GL_POINT_DISTANCE_ATTENUATIONのLINE版があるのかどうかは不明。
同様に、デプステストしてみる。

WireFrameをループで作成するように変更。手前の方から作成している。ドロイド君の透明部分が描画されてしまうので、デプステストとともに、アルファテストも有効にした。
ワイヤーフレームの部分もフォグが有効になっている。奥の方の線が暗くなっているので、遠近感はある。ちゃんと手前の方が描画されている。
気になるアルファブレンドは?
アルファ値が0.7の緑色をワイヤフレームの色属性にして、ブレンドONにして、どうよ。

おお、なんとなくそれっぽくなったじゃん。
もうちょっと明るい緑がいいか。AREarthroidに組み込む時は赤かなぁ。

投稿者プロフィール

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