ARGENTO CUORE

Category : objファイルを読み込む

--.--.--[--] スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

2010.01.21[木] Android OpenGLでobjファイルを読み込む編 part 5

とりあえず現在のソースコードをまるごと載せてみる。

・複数テクスチャ
・グループ
・材質設定

にはまだ対応してません。

あとすごく遅い。
読み込む時点では頂点数やテクスチャ座標の数が不明なので、ArrayListを使ってaddしてるんですが、
配列と比べて遅いだろうから、あらかじめ数を取得して中間ファイルを作るようにしたほうが良いのかも…。
再配置も、Android上でやるのではなくて、PC上であらかじめ計算してそれを読み込ませたほうが良いかな。

現状mtlにはテクスチャ名を取得しに行っている以外何もしていないので、この読み込みもかなりの無駄…。

どなたかこのコードをレビューしていただけたら感謝しまくりです。

package com.gmail.argent_cuore.graphics;

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.lang.reflect.Field;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.content.Context;
import android.util.Log;

/**
* Object3D
* objファイル形式のデータを処理し、OpenGL上で扱えるデータへと変換する。
*/
public class Object3D {
final String class_name = getClass().getName();

private static Class raw_class;
private static Class drawable_class;
private final static int RAW = 0;
private final static int DRAWABLE = 1;

private static Context context = null;

private String current_material = null; //現在読み込み中のマテリアル名

private float[] vertex_data = null;
private byte[] index_data = null;
private float[] texture_data = null;

private String texture_name = null;


/**
* コンストラクタ
*/
public Object3D(Context c, Class raw, Class drawable){
context = c;
raw_class = raw;
drawable_class = drawable;
}


public Bitmap getBitmap(){
Bitmap bmp = null;
Field f = selectField(DRAWABLE,texture_name);
int id = 0;
try{
id = f.getInt(drawable_class);
}catch(IllegalAccessException e){}
if (id!=0) {
bmp = BitmapFactory.decodeResource(context.getResources(), id);
}
return bmp;
}

/**
* MTLファイルを読む。
*/
private void interpretMTL(BufferedReader buffer){
String line = null;
try {
while((line = buffer.readLine()) != null) {
if (line.length() > 0) {
char c = line.charAt(0);
if (c == 0x23) { //コメント行

} else if (c == 0x09) { //タブ
String s[] = line.split(" ");
if (s[0].equals(new String(new byte[] { 0x09 }) + "map_Kd")) { //[TAB]mak_Kdとイコール
if (current_material != null) {
texture_name = s[1];
} else {
Log.e(class_name, "マテリアル宣言がない状態で材質が定義されている可能性があります");
}
}
} else {
String s[] = line.split(" ");
if (s[0].equals("newmtl")) {
current_material = s[1];
}
}
}
}
}catch(IOException e){
Log.e(class_name, e.toString());
}
}

/**
* objファイル名を引数に受け取る。
* 解釈して頂点座標、テクスチャ座標、面データのインデックス値を生成。
* 他のは無視。
*/
public void interpretOBJ(String filename){
ArrayList<Byte> indices = new ArrayList<Byte>();
ArrayList<Float> vertices = new ArrayList<Float>();
ArrayList<Float> textures = new ArrayList<Float>();
ArrayList<Integer> texnum = new ArrayList<Integer>();

BufferedReader buff_obj = createBufferFromRawFile(filename);
String line;
try {
while ((line = buff_obj.readLine()) != null){
if (line.length() > 0) { //空行は無視
char c1 = line.charAt(0); //一文字目
char c2 = line.charAt(1); //二文字目
char c3 = line.charAt(2); //三文字目
if (c1 == 0x20) { //スペースはスキップ
continue;

} else if (c1 == 0x23) { //#はコメント行であるため解釈しない
continue;

} else if (c1 == 0x6D && c2 == 0x74 && c3 == 0x6C) { //mtl(lib)
String s[] = line.split(" ");
BufferedReader buff_mtl = createBufferFromRawFile(s[1]); //mtlファイルをロード
interpretMTL(buff_mtl); //mtlファイルを解釈
continue;

} else {
switch (c1) {
case 0x76: //v
switch (c2) {
case 0x20: //v(space)
String vs[] = line.split(" ");
vertices.add(Float.parseFloat(vs[1])); //x
vertices.add(Float.parseFloat(vs[2])); //y
vertices.add(Float.parseFloat(vs[3])); //z
break;

case 0x6E: //vn
break;
case 0x74: //vt
String vts[] = line.split(" ");
textures.add(Float.parseFloat(vts[1])); //u
textures.add(Float.parseFloat(vts[2])); //t
break;
default:
Log.w(class_name, "v" + c2 + "というラベルに対する処理が定義されていません");
break;
}
break;
case 0x66: //f
switch (c2) {
case 0x20: //space
String s[] = line.split(" ");

//indicesは三角ポリゴンの面を構成する頂点座標へのインデックス値
//texnumは、頂点座標に対応するテクスチャ座標へのインデックス値
//1番目と2番目しか読まない
String ss1[] = s[1].split("/");
indices.add((byte)(Integer.parseInt(ss1[0])-1)); //0からはじまるようマイナス1
texnum.add((int)Integer.parseInt(ss1[1])-1);

String ss2[] = s[2].split("/");
indices.add((byte)(Integer.parseInt(ss2[0])-1));
texnum.add((int)Integer.parseInt(ss2[1])-1);

String ss3[] = s[3].split("/");
indices.add((byte)(Integer.parseInt(ss3[0])-1));
texnum.add((int)Integer.parseInt(ss3[1])-1);
break;
default:
break;
}
break;
default:
Log.w(class_name, c1 + "というラベルに対する処理が定義されていません");
break;
}
}
}
}
}catch(IOException e){
}

//-------------------------------------------------------
//f行の頂点座標の指定にしたがって新しいリストを作成する
//-------------------------------------------------------
ArrayList<Float> vertices2 = new ArrayList<Float>();
ArrayList<Float> textures2 = new ArrayList<Float>();
int count_max = indices.size();

index_data = new byte[count_max];
for (int i=0; i<count_max; i++){
//頂点座標をindicesの配列に従って再配置
int num = indices.get(i)*3;
vertices2.add(vertices.get(num));
vertices2.add(vertices.get(num+1));
vertices2.add(vertices.get(num+2));

//テクスチャ座標をtexnumの配列に従って再配置
num = texnum.get(i)*2;
textures2.add(textures.get(num));
textures2.add(textures.get(num+1));

//インデックスデータは1から順に並ぶだけ
index_data[i] = (byte)i;
}

//再配置済みの頂点座標を配列にセット
count_max = vertices2.size();
vertex_data = new float[count_max];
for (int i=0; i<count_max; i++){
vertex_data[i] = vertices2.get(i);
}

//テクスチャ座標を配列にセット
count_max = textures2.size();
texture_data = new float[count_max];
for (int i=0; i<count_max; i+=2){
texture_data[i] = textures2.get(i);
texture_data[i+1] = 1.0f - textures2.get(i+1); //t座標は上下が反転するため1.0から引く
}
}

private BufferedReader createBufferFromRawFile(String filename){
BufferedReader buffer = null;
Field f = selectField(RAW,filename);
int id = 0;
try{
id = f.getInt(raw_class);
}catch(IllegalAccessException e){}
return loadFileFromFieldId(id);
}

private BufferedReader loadFileFromFieldId(int id){
InputStream is = context.getResources().openRawResource(id);
return new BufferedReader(new InputStreamReader(is));
}

private Field selectField(int resource_type, String name){
Field f = null;
try {
if (resource_type == RAW){
f = raw_class.getField(name);
} else if (resource_type == DRAWABLE) {
f = drawable_class.getField(name);
}
}catch(NoSuchFieldException e){}

if (f==null){
Log.e(class_name, name + "というフィールドが存在しません。");
}
return f;
}

/**
* 頂点座標のデータをint型の配列で返す。
* interpretOBJ()を呼ぶ前に実行しちゃダメ。
*/
public int[] getVertexDataAsInt(){
int[] vd = null;
if ( vertex_data != null) {
vd = new int[vertex_data.length];
for(int i=0; i<vertex_data.length; i++){
vd[i] = (int)(vertex_data[i] * 65535); //浮動小数点数を固定小数点数に変換
}
}
return vd;
}

public float[] getVertexData(){
return vertex_data;
}

public byte[] getIndexData(){
return index_data;
}

public float[] getTexCoords(){
return texture_data;
}
}

スポンサーサイト

2010.01.20[水] Android OpenGLでobjファイルを読み込む編 part 4

前回part3で、できたと思っていたら、痛恨のミスを犯していたというか、まったく理解が足りてなかったというか。

問題が起きたのは、

・頂点を共有するトライアングルポリゴンが二枚以上
・共有している頂点に対応するテクスチャ座標が異なる

という情報の入ったobjファイルをロードしたときでした。

glVertexPointer
glTexCoordPointer

で頂点座標とテクスチャ座標を設定し、

glDrawElements

で描画していたのですが、頂点座標とテクスチャ座標のデータが入った配列要素は、1対1で設定しなければいけなかったのと、手元の最初に作ったサンプルのobjファイルではある頂点座標に対するテクスチャ座標は固定だったため、

「そりゃ、同じ頂点座標だから同じテクスチャ座標になるに決まってるじゃん」

とでかいミスを犯して、

頂点座標数 < テクスチャ座票数

だったのを、ある頂点座標に対応するテクスチャ座標のうち重複するものを削除して、

頂点座票数 = テクスチャ座票数

にしてました……。
でも、

トライアングルポリゴン2つで構成された四角形に、3DCGソフトでUVマップしたテクスチャを貼り付けようとすると問題発覚。

tex.png

上記のようなテクスチャを張り付けると表示が壊れました。
残るは、

頂点座標+重複する頂点座標を加算 = テクスチャ座標

にしないとだめなのかなー増えるのやだなーと思って、色々情報を漁ってみたんですけど、glDrawElements描画時、頂点座標とテクスチャ座標のデータの並び的なオプションもなさそうで……。
結局頂点増やしました。
テクスチャ座標指定の重複を削除するより処理がシンプルー。

しかしロード長いです…。
かなりゴリゴリ書き殴っているので、高速化しなければ……。

以下、蛇足
少しでも早くなってくれればと思い、浮動小数点から固定小数点で計算するようにしました。
変換は、

浮動小数点数 x 65535 = 固定小数点数

らしいです?
iPhoneなどは固定小数点数を使わない方が逆によいみたいーだけどもってないし測ったことない。
どうなんだろ。

2010.01.11[月] Android OpenGLでobjファイルを読み込む編 part 3

3Dテクスチャ

作った円柱にandroidのマスコットのテクスチャを張り付けて表示~。
objファイルロード&テクスチャ適用完了です。が、テストとかまだやってないので読み込めないobjとか絶対あります。これから一度ロードするクラスを書き直して機を見てブログにアップロードしようかなと思います。

テクスチャ座標と頂点座標って1対1で対応しているものなのですが(OpenGL上だと)、objファイルだと、三角形を定義するf行に、この三角形のこの頂点座標に対してX番目のテクスチャ座標を使ってねっていう感じで定義されているので、それを解釈して処理しないとだめでした。
読み込み時に処理するため、ロード時間を短くするために、OpenGLで直に解釈できるファイルにコンバートするツールを書いて、それを読み込んだ方がAndroidのバッテリーやCPUにも優しい感じがします……。

まだグループ分けとかの解釈をしていない&アニメーションってどういう風なデータをmm3dが出力するかわかっていないので、ゲームにするには程遠い状態です。

というかまともな3Dのあたり判定をやったことがないので、そこが鬼門?
二点の距離で判定して押し戻すといった感じの処理しか書いたことない。
当たる可能性のある壁(オブジェクト)だけを判別して、それに対してのあたり判定をするーとか、なんか考えるだけで面倒くさそう、難しそう。数学ー……。

お勉強しなきゃ。


2010.01.08[金] Android OpenGLでobjファイルを読み込む編 part 2

数日前に、カウンターなるものをブログにつけてみました。
FC2が用意しているのをぺたっと設定してやるだけです。べんりー。
でも、11人の日が3日連続なんですよね。わーい、自分を除けば10人!?とか思ってたんですけど、これって検索エンジンのクローラーとかもカウントされるのかなー。ぬか喜び……。

さて、part2と言っても、まだ完成していないので今日できたところまでの進行を以下。
とりあえず、

・モデルの頂点データ、面データを解釈して3Dモデルを表示

まではできたんですけど、Misfit Model 3DでUVテクスチャを扱う方法がよくわからなくてハマってます……日本語のマニュアルないかなー。しばらくモデリングをたのしんでみよー。

マテリアル(テクスチャ、材質等)は、mtlファイルに保存されるのですが、頂点データと面データのみを扱うので、objのみを読み込んでいきます。

objのファイルフォーマットをテキストファイルで閲覧すると見られる以下のような
v float float float

f hoge1/piyo1/fuga1 hoge1/piyo1/fuga1 hoge1/piyo1/fuga1
という二種類の行を解釈するようにします。

vはvertex、fはfaceです。
fはスラッシュで区切られたいくつかのデータが並びますが、Misfit Model 3Dは三角ポリゴンを取り扱うように作られており、hoge1 -> hoge2 -> hoge3の順(時計回り=CW)に並ぶ座標を面として扱いますよーという意味をもっています。詳しくはOpenGLなりなんなりのCGの参考書を見れば大抵書かれているかも。piyo、fugaについては今回は無視。

v float float float <- 1番目の座標
v float float float <- 2
v float float float <- 3

と、頂点座標は書かれている順に番号がふられており、faceデータはそれと結びつけられるようになっています。

これで、座標と面データを得られるので、あとは普通にOpenGLとして描画してやればOK。
注意するのは、OpenGLはデフォルトだとCCW(反時計回り)なので、CWに変更しておく。
あと、ポリゴンは三角ポリゴンなので、GL_TRIANGLESで描画するようにしておく。TRIANGLE_STRIPが高速だと聞くけど、色々考えながらモデリングしないとダメそうなので(TRIANGLE_STRIP化するようなコンバータとかあるのかな……一筆書きみたいな……)、速度が遅すぎて気になってどうしようもなくなったら考えます……。
それ以前に、floatで扱っている座標データをintに直そうか迷い中。

ambientとか、CGソフトの材質の設定画面を見ているとすごい懐かしい。
昔授業でOpenGL習ったときとかは、こういうのいっぱいつかってなんかやってた。

けども、Androidは速度が出ないので、マテリアル設定の大半(というかテクスチャ以外?)は無視することになりそーな感じも? ベンチマークを取ったサイトとかあるかなー……。どれくらいのことをしてどれくらいのFPSが出るのか感覚がわかりません(パソコンだと、適当なマテリアルバリバリのサンプルプログラムでさえ簡単に数百FPS出るし……)。

で、若干プログラム的なメモ。
Androidでは、R.hogepiyo.fugaという形でリソースのID(int)にアクセスできますが、このfugaを動的に変更したかったので、リフレクションなるものを初使用。以下、コードです。

Class cls = R.drawable.class;
try{
Field f = cls.getField(取得したいフィールド名(String));
int res_id = f.getInt(cls);
}catch(NoSuchFieldException e){
}catch(IllegalAccessException e){}

みたいな感じで、getInt()でフィールドの値を取得してます。
これでobjファイルに書かれたmtlファイルをロードし、mtlファイルに書かれたtexture.pngファイルも読み出せます!
めでたしーめでたしー。

2010.01.07[木] Android OpenGLでobjファイルを読み込む編 part 1

ポリゴン表示とテクスチャ表示ができたら、次にやりたくなるのは自分でモデリングしたオブジェクトを表示させることでしょう?ということで頑張ります。

モデリングソフトはWindowsでは色々あるけど、Linuxで動作するのはBlenderくらいだよなー、インターフェース独特だったよなー、モデリング機能だけで良いからLightWaveのModelerもしくはMetasequoiaみたいなのないかなーと右往左往。

ファイル形式は、シンプルで必要十分な情報を兼ね備えたobjが好ましいです。昔OpenGLでモデル表示したときに、ファイルフォーマットを調べて読み込めば良いとかいうとこまで頭が回らず、方眼紙に表示したいモデルの三面図を鉛筆で書いて必死にvertexデータとfaceデータをメモ帳に書き写して、それを読み込むようにしていた頃が懐かしいです。そんなこんなでobjのvとfを見たときに、これしかないと思った次第です。

話は戻ってモデリングソフトですが、Windows、MacOS X、Linuxで動作するMisfit Model 3Dというソフトを発見しました。GUIツールキットにはQT(キュートです、キュート!)が使われているらしいです。

Xubuntu9.10では、synapticパッケージマネージャからmm3dで検索すると出てきます。簡単にインストールできます。
ポリゴンモデリング、テクスチャに対応しており、アニメーションもできるそうですが、画面がとてもシンプルでわかりやすい。更にWavefrontが開発したobjファイルフォーマットを出力可能ということで、高機能ではないものの、今回の目的に合致した素晴らしいソフトです。

obj ファイルフォーマット


で、てきとーに作ったobjファイルを読み込ませることにしました。
でも、実行環境はAndroidなので、普通のPCみたくファイルを読み込むことができませんでした。今回はそこを重点的にメモ書きしておきます。

アプリケーションが元々もっているデータは、リソースとしてアクセスするようにします(これでよいのか?)。
テクスチャもres/drawable/hoge.pngという感じで設置していたので、同じ表示物?ということでres/drawable/hogepiyo.objみたいな形でobjファイルを設置しておきます。

このファイルへのアクセスですが、

context.getResourcesで得られるResourcesオブジェクトに色々なメソッドが用意されています。
で、ファイルを読むためにInputStreamを返してくれるopenRawResourceというのがあるんですが、Rawって何……? Raw画像? あれ……。

InputStream is = context.getResources().openRawResource(R.drawable.hogepiyo.obj);
でinputStreamオブジェクトを新規に作成し、
BufferedReader br = new BufferedReader(new InputStreamReader(is));
でBufferedReaderを作ります。内心、なんだか、こんなにnewして良いのだろうかと不安になります。openRawResourceで読み込んで良いのかとても気がかりです。

でも読み込んでしまえたのだから仕方ない(ぁ

InputStreamReaderは、InputStreamで読み込んだバイトの羅列を文字データとして読み直します。objファイルはasciiなので、ISO-8859-1を指定してもしなくてもきっと大丈夫です(このファイルフォーマットで読み込んでくれなきゃ死んじゃうなんてときは、引数の二番目に文字コードを指定してあげてください)。

BufferedReaderは、バッファを使うことで高速化される(らしい)です。測ったことないのでほんとなのかは知りません。

バッファ上のテキストデータは、read(),readLine()などと言ったメソッドで取得できます。一文字ずつだったり、長さや位置を指定したり、一行ずつだったり。

きちんと読み込めているかどうかを確認するために以下のコードを書き加える。

String line;
try {
while ((line = br.readLine()) != null){
Log.i("readLine", line);
}
}catch(IOException e){
//ぎゃー
}

シェルから、

$adb logcat

しておき、きちんとデータが表示されればとりあえず読み込み完了です。

openRawResourceが気になる……(つづく)

Scala Feed

scala feed

FC2カウンター

プロフィール

RoNor

Author:RoNor
得意呪文はScalaですって言えるようになるのが夢です。
デスマーチ中、パーティメンバーの防御力を向上させたりさせなかったり。

検索フォーム

QRコード

QRコード

Copyright © 2009-2010 ARGENTO CUORE and RoNor All rights reserved.

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。