- 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開発用)






inserted by FC2 system