- Android開発環境でのObjファイルの読み込み - |
Android開発環境でObjファイルを読むためのクラスを作成しました。 改変等もご自由にOK。 使い方を示すためのソースコード。 package Project.testJavaGLES; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Bitmap.Config; import android.opengl.GLSurfaceView; import android.opengl.GLUtils; import android.util.Log; import android.widget.ImageView; public class CRenderGLES implements GLSurfaceView.Renderer { private Context mContext; private int width; private int height; public CRenderGLES(Context context) { mContext = context; } //objファイルのオブジェクト Obj obj; //描画する頂点、法線、テクスチャ配列 private float[] obj_v; private float[] obj_vn; private float[] obj_vt; //ライトの設定 float lightPos[] = { 1.0f, 1.0f, 2.0f, 0.0f }; float lightColor[] = { 1.0f, 1.0f, 1.0f, 1.0f }; float lightSpecular[] = { 1.0f, 1.0f, 1.0f, 1.0f }; public void initGLES(GL10 gl) { gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); //点と線のアンチエイリアス処理を有効にする gl.glEnable(GL10.GL_POINT_SMOOTH); gl.glEnable(GL10.GL_LINE_SMOOTH); gl.glEnable(GL10.GL_NORMALIZE); /*法線を有効化*/ gl.glEnable(GL10.GL_DEPTH_TEST); /*デプスを有効化*/ gl.glShadeModel(GL10.GL_SMOOTH); /*スムースシェーディングを有効化*/ /*とりあえず画面をこの色でクリア*/ gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); /*ビューポートを設定する*/ gl.glViewport(0, 0, (int)width, height); /*投影変換行列スタックを操作対象とする*/ gl.glMatrixMode(GL10.GL_PROJECTION); /*P行列スタックをクリア(単位行列にする)*/ gl.glLoadIdentity(); //透視投影 float aspect = (float)width / height; gl.glFrustumf(-aspect, aspect, -1.0f, 1.0f, 1.0f, 10.0f); /*幾何変換スタックの操作を対象とする*/ gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); } @Override public void onDrawFrame(GL10 gl) { drawObjFileData(gl); } private void drawObjFileData(GL10 gl) { initGLES(gl); //カラーバッファ、デプスバッファのクリア gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); //現在のモデルビュー行列を保存 gl.glPushMatrix(); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); gl.glEnable(GL10.GL_CULL_FACE); /*glCullFaceで指定された面を削除する*/ gl.glCullFace(GL10.GL_BACK); /*削除する面を指定する(ここでは裏面)*/ gl.glEnable(gl.GL_LIGHTING); gl.glEnable(gl.GL_LIGHT0); gl.glLightfv(gl.GL_LIGHT0, gl.GL_POSITION, makeFloatBuffer(lightPos)); gl.glShadeModel(gl.GL_SMOOTH); //平行移動 gl.glTranslatef(0.0f, 0.0f, -4.0f); for(int i = 0; i < obj.getObjSize(); i++) { MtlData mtl; float[] ambient = new float[4]; //環境光の反射成分 float[] diffuse = new float[4]; //拡散反射の成分 float[] specular = new float[4]; //鏡面反射の成分 float specular_angle; //鏡面反射の角度 //mtlデータを取得 mtl = obj.getMtlSeparateMtlData(i); //mtlファイルの環境光の反射成分を代入 ambient[0] = mtl.ka.x; //赤(R) ambient[1] = mtl.ka.y; //緑(G) ambient[2] = mtl.ka.z; //青(B) ambient[3] = 1.0f; //mtlファイルの拡散反射の成分を代入 diffuse[0] = mtl.kd.x; //赤(R) diffuse[1] = mtl.kd.y; //緑(G) diffuse[2] = mtl.kd.z; //青(B) diffuse[3] = 1.0f; //mtlファイルの鏡面反射の成分を代入 specular[0] = mtl.ks.x; //赤(R) specular[1] = mtl.ks.y; //緑(G) specular[2] = mtl.ks.z; //青(B) specular[3] = 1.0f; //mtlファイルの鏡面反射の角度を代入 specular_angle = mtl.ns; gl.glMaterialfv(gl.GL_FRONT_AND_BACK, gl.GL_AMBIENT, makeFloatBuffer(ambient)); //環境光反射の設定 gl.glMaterialfv(gl.GL_FRONT_AND_BACK, gl.GL_DIFFUSE, makeFloatBuffer(diffuse)); //拡散反射の設定 gl.glMaterialfv(gl.GL_FRONT_AND_BACK, gl.GL_SPECULAR, makeFloatBuffer(specular)); //鏡面反射の設定 gl.glMaterialf(gl.GL_FRONT_AND_BACK, gl.GL_SHININESS, specular_angle); //鏡面反射の鋭さの設定 //テクスチャがあるかどうかを判定 if(mtl.map_kd.length() > 0) { //テクスチャを有効 gl.glEnable(GL10.GL_TEXTURE_2D); //テクスチャオブジェクトの指定 gl.glBindTexture(GL10.GL_TEXTURE_2D, mtl.textureID); } //法線配列を有効化 gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); //描画する法線配列を取得 obj_vn = obj.getMtlSeparateNormals(i); //描画する法線配列を指定 gl.glNormalPointer(GL10.GL_FLOAT, 0, makeFloatBuffer(obj_vn)); //テクスチャ配列を有効化 gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glTexEnvx(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE); //描画するテクスチャ配列を取得 obj_vt = obj.getMtlSeparateTexCoods(i); //描画するテクスチャ配列を指定 gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, makeFloatBuffer(obj_vt)); //頂点配列を有効化 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); //描画する頂点配列を取得 obj_v = obj.getMtlSeparateVertices(i); //描画する頂点配列を指定 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, makeFloatBuffer(obj_v)); //頂点配列、色配列の内容を描画する gl.glDrawArrays(GL10.GL_TRIANGLES, 0, (int)obj_v.length/3); gl.glDisableClientState(GL10.GL_NORMAL_ARRAY); gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); if(mtl.map_kd.length() > 0) { //テクスチャを有効 gl.glDisable(GL10.GL_TEXTURE_2D); } } gl.glDisable(GL10.GL_CULL_FACE); gl.glEnable(gl.GL_LIGHT0); gl.glEnable(gl.GL_LIGHTING); gl.glPopMatrix(); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { this.width = width; this.height = height; //Globalクラスにglを格納する Global.gl = gl; //objファイルの内容を開く obj = new Obj(mContext, "testpensan.obj"); } //システム上のメモリ領域を確保するためのメソッド public static final FloatBuffer makeFloatBuffer(float[] arr) { ByteBuffer byteBuf = ByteBuffer.allocateDirect(arr.length*4); byteBuf.order(ByteOrder.nativeOrder()); FloatBuffer floatBuf = byteBuf.asFloatBuffer(); floatBuf.put(arr); floatBuf.position(0); return floatBuf; } } ポイントは以下です。 @Override public void onSurfaceChanged(GL10 gl, int width, int height) { this.width = width; this.height = height; //Globalクラスにglを格納する Global.gl = gl; //objファイルの内容を開く obj = new Obj(mContext, "testpensan.obj"); }onSurfaceChanged()は最初に実行されるので、ここでobjを作成しています。 objは、Objクラスのオブジェクトです。 Objクラスコンストラクタにcontextと開きたいobjファイル名を指定します。 ファイルの場所は、「data/data/(パッケージ名)/files/(objファイル名)」に置いて下さい。 objファイルはすべて三角面にして置いて下さい。 描画処理はonDrawFrame()ですが、その中ではdrawObjFileData()を呼び出すのみで、 実体はdrawObjFileData()に書かれています。 objファイルの描画処理は、drawObjFileData()の中に書かれた以下のfor文で行っています。 for(int i = 0; i < obj.getObjSize(); i++) { MtlData mtl; float[] ambient = new float[4]; //環境光の反射成分 float[] diffuse = new float[4]; //拡散反射の成分 float[] specular = new float[4]; //鏡面反射の成分 float specular_angle; //鏡面反射の角度 //mtlデータを取得 mtl = obj.getMtlSeparateMtlData(i); //mtlファイルの環境光の反射成分を代入 ambient[0] = mtl.ka.x; //赤(R) ambient[1] = mtl.ka.y; //緑(G) ambient[2] = mtl.ka.z; //青(B) ambient[3] = 1.0f; //mtlファイルの拡散反射の成分を代入 diffuse[0] = mtl.kd.x; //赤(R) diffuse[1] = mtl.kd.y; //緑(G) diffuse[2] = mtl.kd.z; //青(B) diffuse[3] = 1.0f; //mtlファイルの鏡面反射の成分を代入 specular[0] = mtl.ks.x; //赤(R) specular[1] = mtl.ks.y; //緑(G) specular[2] = mtl.ks.z; //青(B) specular[3] = 1.0f; //mtlファイルの鏡面反射の角度を代入 specular_angle = mtl.ns; gl.glMaterialfv(gl.GL_FRONT_AND_BACK, gl.GL_AMBIENT, makeFloatBuffer(ambient)); //環境光反射の設定 gl.glMaterialfv(gl.GL_FRONT_AND_BACK, gl.GL_DIFFUSE, makeFloatBuffer(diffuse)); //拡散反射の設定 gl.glMaterialfv(gl.GL_FRONT_AND_BACK, gl.GL_SPECULAR, makeFloatBuffer(specular)); //鏡面反射の設定 gl.glMaterialf(gl.GL_FRONT_AND_BACK, gl.GL_SHININESS, specular_angle); //鏡面反射の鋭さの設定 //テクスチャがあるかどうかを判定 if(mtl.map_kd.length() > 0) { //テクスチャを有効 gl.glEnable(GL10.GL_TEXTURE_2D); //テクスチャオブジェクトの指定 gl.glBindTexture(GL10.GL_TEXTURE_2D, mtl.textureID); } //法線配列を有効化 gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); //描画する法線配列を取得 obj_vn = obj.getMtlSeparateNormals(i); //描画する法線配列を指定 gl.glNormalPointer(GL10.GL_FLOAT, 0, makeFloatBuffer(obj_vn)); //テクスチャ配列を有効化 gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glTexEnvx(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE); //描画するテクスチャ配列を取得 obj_vt = obj.getMtlSeparateTexCoods(i); //描画するテクスチャ配列を指定 gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, makeFloatBuffer(obj_vt)); //頂点配列を有効化 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); //描画する頂点配列を取得 obj_v = obj.getMtlSeparateVertices(i); //描画する頂点配列を指定 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, makeFloatBuffer(obj_v)); //頂点配列、色配列の内容を描画する gl.glDrawArrays(GL10.GL_TRIANGLES, 0, (int)obj_v.length/3); gl.glDisableClientState(GL10.GL_NORMAL_ARRAY); gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); if(mtl.map_kd.length() > 0) { //テクスチャを有効 gl.glDisable(GL10.GL_TEXTURE_2D); } }for文のループ回数はobjファイル内のusemtlで区分けされた数をループします。 objオブジェクトは、usemtlの切れ目の単位で、頂点、法線、テクスチャ座標を持つようにしているためです。 (例) usemtl mat1 f 436/273/273 434/274/274 437/275/275 f 77/277/277 71/278/278 437/279/279 f 205/281/281 77/282/282 437/283/283 f 434/285/285 435/286/286 437/287/287 f 436/289/289 433/290/290 447/291/291 f 444/293/293 445/294/294 447/295/295 f 455/297/297 457/298/298 458/299/299 f 267/301/301 272/302/302 458/303/303 f 272/305/305 359/306/306 458/307/307 f 456/309/309 455/310/310 458/311/311 f 454/313/313 457/314/314 468/315/315 f 466/317/317 465/318/318 468/319/319 f 428/3105/3105 53/3106/3106 47/3107/3107 f 427/3109/3109 428/3110/3110 47/3111/3111 f 429/3113/3113 35/3114/3114 29/3115/3115 f 28/3117/3117 429/3118/3118 29/3119/3119 f 430/3121/3121 41/3122/3122 35/3123/3123 f 429/3125/3125 430/3126/3126 35/3127/3127 f 427/3129/3129 47/3130/3130 41/3131/3131 f 430/3133/3133 427/3134/3134 41/3135/3135 f 431/3137/3137 59/3138/3138 53/3139/3139 ・ ・ ・ usemtl mat2 f 469/3537/3537 89/3538/3538 83/3539/3539 f 435/3541/3541 469/3542/3542 83/3543/3543 f 470/3545/3545 95/3546/3546 89/3547/3547 f 469/3549/3549 470/3550/3550 89/3551/3551 f 100/3553/3553 95/3554/3554 470/3555/3555 ・ ・ ・objファイルのfの部分でusemtlが指定されています。 mat1が適用される範囲の各種データをobj[0]、mat2が適用される範囲のデータをobj[1]が持っているというイメージです。 ※ 実際にはobj[]で中身へアクセスすることはできませんので、あくまでイメージとしてお考え下さい。 では本題です。 描画順はobjの先頭から、objが持つ頂点や法線データやマテリアルを取り出して描画する、 ということを繰り返し行います。 まず、MtlDataクラスオブジェクトをobj.getMtlSeparateMtlData(i)で取得しています。 取得したマテリアルは別の配列(ambient[]等)に格納します。 アルファ成分(ambient[3]等)は1.0f固定とします。 実際に適用する時はglMaterialfv()で行っています。 glMaterialfv()の第1引数はGL_FRONT_AND_BACKと指定しないとマテリアル適用されません(仕様?) 謎ですね。 makeFloatBuffer()は配列をFloatBufferに変換するメソッドです。 調べればすぐ分かりますが、ダウンロードできるソースに含んであります。 if(mtl.map_kd.length() > 0)で、テクスチャがあるかどうかを判定します。 mtlファイルのmap_kd行があった場合、mtl.map_kdにテクスチャファイル名を持つようにしていますので、 このようにして判定することができます。 その次からは、glEnableClientState()で法線・テクスチャ・頂点配列を有効にして、 各データを配列で取得します。 objの中の配列データの取り出し方はObjクラスの次のメソッドを使います。 法線ベクトル配列の取り出しは、getMtlSeparateNormals(i) 2次元テクスチャ座標配列の取り出しは、getMtlSeparateTexCoods(i) 頂点座標配列の取り出しは、getMtlSeparateVertices(i) この3メソッドは、float型配列を返します。 これらのメソッドは、あるusemtlが適用される範囲のデータをfloat型配列で取得するためのメソッドです。 例えば、 getMtlSeparateVertices(0) とすると、 objファイル内を上から順に見て、最初に出てくるusemtlで指定されているマテリアルが 適用される範囲の頂点座標配列を返します。 getMtlSeparateNormals()やgetMtlSeparateTexCoods()は 返す値が法線ベクトル配列か2次元テクスチャ座標配列かの違いです。 obj.getObjSize()でusemtlでデータが区切られた数が分かるので、その数分for()でループして データ配列を取得して描画を繰り返します。 ここでは使っていませんが、objが持つすべての頂点データ・法線データを取得するためには、 法線データは、getNormalsArray() 頂点データは、getVerticesArray() を使います。 また、テクスチャは、getTexCoodsArray()を使えば取得できますが、 3次元座標データとなっており、z成分は-999.0が入るようになっています。 getMtlSeparate*()で各配列データを取得したら、glDrawArrays()で描画すれば、 あるusemtlが適用される範囲のデータが描画されます。 これをusemtlの数だけ繰り返せばobjファイル全体の描画が完了します。 ●ソースのダウンロード Obj File Loader(android開発用) |