Hatena::ブログ(Diary)

Android開発-fs随筆- RSSフィード

2010-08-30

頂点配列を読み込んでみたけど、表示はまだ

AndroidのAssetsって1MBくらいまでしか使えないのかな。

大きすぎますと言われて使えなかったよ。

表示に使おうとしてるメタセコイアの素材を500kB位のものでやったら読み込みまではできた。

あとは表示していく感じだけど、いまいちまだわかりません。

FloatBufferどうやって使ったらいいのか。さっきまた大きすぎと言われて怒られました。

2010-08-29

メタセコイア形式の概要が飲み込めた

次のものをざっくりと読んだ。

http://www.metaseq.net/metaseq/format.html

  • MQOファイルは改行ごとに読み込んでいけばよい(たぶん)
  • チャンクという塊になってデータが入っている。
  • Materialチャンクに材質の情報
  • Objectのチャンクに頂点データとかの情報

とりあえず表示させるには、Objectチャンクの頂点情報を使えばよさそうね。

Androidメタセコイア形式のデータを表示させたい!

表題の通り、Androidメタセコイア形式のデータを表示させたいと考えている。

そんなわけで、まずはファイルフォーマットでも読むべきだろう。

http://www.metaseq.net/metaseq/format.html

先人たちの実装を参考にするのもいいかもしれないなあ。

http://www.ric.hi-ho.ne.jp/keiweb/reader_java.html

http://mnu.sakura.ne.jp/colum/java/1.shtml

http://kurusugawa.jp/2007/07/17/java3d%E3%81%A7metasequoia%E3%81%AE%E3%83%A2%E3%83%87%E3%83%ABmqo%E3%82%92%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B/

とりあえず、ファイルフォーマットの理解から始めよう。

本当なら、今週末で片付ける予定だったんだけど、情熱が足りなくて敗北しました(ぐだぐだしてた)。

2010-08-26

作業中

三角形を回転させているんだけど、

Matrix.setRotateEulerM()

Matrix.setRotateM()

使うよりも、クォータニオンを使って

Matrix.multiplyMM()

したほうがいいかもしれない。

2010-08-25

[][]AndroidでOpenGLES2.0 Lesson2

さて、Lesson1からだいぶ間があいてしまいましたね。すいませんです。

では、Lesson2に進んでいきますよ。

今日はおもむろに三角形を表示します。

Activityはこんな感じ。

	private GLSurfaceView mGLSurfaceview;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		mGLSurfaceview = new GLSurfaceView(this);
		mGLSurfaceview.setEGLContextClientVersion(2);

		mGLSurfaceview.setRenderer(new MyGLRenderer(this));

		setContentView(mGLSurfaceview);
	}

ポイントは前回忘れていた、OpenGLバージョンしていをしているところです。

		mGLSurfaceview.setEGLContextClientVersion(2);

これでOpenGLES2.0を使うことができます。

ではお次はRendererですね。

/*packageやimportは省略*/
public class MyGLRenderer implements Renderer {

	public MyGLRenderer(Context context) {	}

	//フレーム描画メソッド
	public void onDrawFrame(GL10 glUnused) {	}

	//画面のサイズが変更されたときにビューポートと投影変換用の行列を変更するメソッド
	public void onSurfaceChanged(GL10 glUnused, int width, int height) {	}

	//最初に画面が生成されたときにいろいろ初期化するメソッド
	public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {	}

	//createProgramに呼ばれる。シェーダをロードするメソッド
	private int loadShader(int shaderType, String source) {	}

	//onSurfaceCreatedに呼ばれる。二つのシェーダを準備するメソッド
	private int createProgram(String vertexSource, String fragmentSource) {	}

	//GLのエラーを拾うメソッド。みんなで使う。
	private void checkGlError(String op) {	}

	//以下フィールドたち
	private static final int FLOAT_SIZE_BYTES = 4;
	private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;

	//描画する三角形の頂点座標の配列。
	private static final float[] mTriangleVerticesData = { 0.0f, 0.5f, 0.0f,
			-0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f };
	//頂点座標の配列はGLに渡すときBufferにするんよ!
	private FloatBuffer mTriangleVertices;
	
	//頂点シェーダ
	private final String mVertexShader =
			"uniform mat4 uMVPMatrix;\n" +
			"attribute vec4 aPosition;\n" +
			"void main() {\n" +
			"  gl_Position = uMVPMatrix * aPosition;\n" +
			"}\n";

	//フラグメントシェーダ
	private final String mFragmentShader =
			"precision mediump float;\n" +
			"void main() {\n" + "  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" +
			"}\n";


	private float[] mMVPMatrix = new float[16];//下の三つを乗算した行列
	private float[] mProjMatrix = new float[16];//投影変換用行列
	private float[] mMMatrix = new float[16];//モデルビュー変換用行列
	private float[] mVMatrix = new float[16];//視野変換用行列

	//GLのどこに登録したか覚えておくハンドルだよ
	private int mProgram;
	private int muMVPMatrixHandle;
	private int maPositionHandle;

	private Context mContext;
	private static String TAG = "MyGLRenderer";
}

では、処理順序に沿ってメソッドを解説していきます。

まずはコンストラクタです。

コンストラクタでは、一応呼び出し元をフィールド記憶してます。こうすると落ち着くね。

次に、三角形の頂点をfloatの配列からFloatBufferに変換してます。OpenGLに渡すときはBufferにしなきゃいけないけど、人間配列使ったほうが見やすいので、ここでGL用に変換しときます。

	//コンストラクタ
	public MyGLRenderer(Context context) {
		mContext = context;
		mTriangleVertices = ByteBuffer.allocateDirect(
				mTriangleVerticesData.length * FLOAT_SIZE_BYTES).order(
				ByteOrder.nativeOrder()).asFloatBuffer();
		mTriangleVertices.put(mTriangleVerticesData).position(0);
	}

では、次は画面が生成されるのでonSurfaceCreatedが動くよ。

ここでは、引数のGL10を使わないから注意してね。

まず、シェーダプログラム作成するcreateProgramを呼び出します。(後述)

シェーダプログラム作成されるとmProgramに値が入るよ。

	public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
		// OpenGLES2.0を使うときは引数のGL10は使いません。
		mProgram = createProgram(mVertexShader, mFragmentShader);
		if (mProgram == 0) {
			return;
		}

次に頂点シェーダのAttributeであるaPositionのハンドル整数)を取っておきます。

あとでこのハンドルを使ってGL経由で頂点を代入します。

		maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
		checkGlError("glGetAttribLocation aPosition");
		if (maPositionHandle == -1) {
			throw new RuntimeException(
					"Could not get attrib location for aPosition");
		}

同じように頂点シェーダのUniformであるuMVPMatrixのハンドルを取っておきます。

		muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
		checkGlError("glGetUniformLocation uMVPMatrix");
		if (muMVPMatrixHandle == -1) {
			throw new RuntimeException(
					"Could not get attrib location for uMVPMatrix");
		}

最後にmVMatrixを初期化しておきますが。ここでは、視点が「0,0,-5(z軸方向に-5手前)」で、向いてる方向が「0,0,0」で、カメラの上が「0,1,0(y軸が上)」と初期化されます。

		Matrix.setLookAtM(mVMatrix, 0, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
	}

これで、onSurfaceCreatedメソッドはおしまい

途中でcreateProgramを呼び出しているので、次はそれを見てみましょう。

引数にはシェーダソースコードフィールド定義してるやつ)を与えています。

loadShaderメソッドを使って頂点シェーダコンパイルして、頂点シェーダハンドルを得ています。

このハンドルはあとで、プログラムに登録するときに使います。プログラムは頂点シェーダフラグメントシェーダを登録して動かす単位です。

	private int createProgram(String vertexSource, String fragmentSource) {
		int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
		if (vertexShader == 0) {
			return 0;
		}

頂点シェーダと同じように、フラグメントシェーダをロードしてハンドルを得ます。

		int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
		if (pixelShader == 0) {
			return 0;
		}

つぎにプログラムを作ります。

これに先程の頂点シェーダフラグメントシェーダのハンドルをアタッチしてリンクします。

問題がなければプログラムハンドルを呼び出し元(onSurfaceCreated)に返却します。

		int program = GLES20.glCreateProgram();
		if (program != 0) {
			GLES20.glAttachShader(program, vertexShader);
			checkGlError("glAttachShader");
			GLES20.glAttachShader(program, pixelShader);
			checkGlError("glAttachShader");
			GLES20.glLinkProgram(program);
			int[] linkStatus = new int[1];
			GLES20
					.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus,
							0);
			if (linkStatus[0] != GLES20.GL_TRUE) {
				Log.e(TAG, "Could not link program: ");
				Log.e(TAG, GLES20.glGetProgramInfoLog(program));
				GLES20.glDeleteProgram(program);
				program = 0;
			}
		}
		return program;
	}

さて、頂点シェーダフラグメントシェーダをロードするためにloadShaderが呼び出されていますね。その中を見てみましょう。

シェーダコンパイルして、問題がなければハンドルをリターンするようになっています。

	private int loadShader(int shaderType, String source) {
		int shader = GLES20.glCreateShader(shaderType);
		if (shader != 0) {
			GLES20.glShaderSource(shader, source);
			GLES20.glCompileShader(shader);
			int[] compiled = new int[1];
			GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
			if (compiled[0] == 0) {
				Log.e(TAG, "Could not compile shader " + shaderType + ":");
				Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
				GLES20.glDeleteShader(shader);
				shader = 0;
			}
		}
		return shader;
	}

さて、これでonSurfaceCreatedイベントは終わりです。

次はonSurfaceChangedです。

このイベントでは、画面のサイズが分かりますので、そのサイズを使ってビューポートの指定と投影用の行列初期化をします。

	public void onSurfaceChanged(GL10 glUnused, int width, int height) {
		//GL10は使わない
		GLES20.glViewport(0, 0, width, height);
		float ratio = (float) width / height;
		Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
	}

では、これで準備ができました。あとはフレームを描画するだけです。

最初に背景を真っ青に塗ります。綺麗だ!

	public void onDrawFrame(GL10 glUnused) {
		//GL10は使わない
		GLES20.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
		GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

これからハンドルしたプログラムを使用することをGLに伝えます。

		GLES20.glUseProgram(mProgram);
		checkGlError("glUseProgram");

aPositionのハンドルを使って頂点座標のFloatBufferをシェーダのaPositionに紐付けます。

		GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT,
				false, 12, mTriangleVertices);
		checkGlError("glVertexAttribPointer maPosition");
		GLES20.glEnableVertexAttribArray(maPositionHandle);
		checkGlError("glEnableVertexAttribArray maPositionHandle");

あとで回転させたいのでangleを用意しておき、その後モデルビュー変換用の行列初期化します。

		float angle = 0.0f;
		// モデルビュー変換行列(指定した軸を中心に回転)
		Matrix.setRotateM(mMMatrix, 0, angle, 0.0f, 0.0f, 1.0f);

そして視野行列モデルビュー行列を乗算します。

		// 視野行列*モデルビュー行列
		Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);

最後投影行列と乗算します。

		// 投影行列
		Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);

できたmMVPMatrixをハンドルを使ってシェーダ内のuMVPMatrixと紐付けます(代入ですね)。

		GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);

そして、三角形の描画を行ないます。

		GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
		checkGlError("glDrawArrays");
	}

これで、描画ができますね。

最後GL用のエラーを検出するメソッド、checkGLErrorを見ましょう。

エラーを探して、Logに出力してます。こうすればLogCatで見れますね。

	private void checkGlError(String op) {
		int error;
		while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
			Log.e(TAG, op + ": glError " + error);
			throw new RuntimeException(op + ": glError " + error);
		}
	}

以上で、三角形が表示されますね。

2010-08-08

[][]AndroidでOpenGLES2.0 Lesson1

さて今日から始まりましたAndroidでOpenGSES2.0です。

最初から飛ばしていきますよー。

まずは適当なActivityを作成します。

onCreateは次のような感じ。

    private GLSurfaceView mGLSurfaceview;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mGLSurfaceview = new GLSurfaceView(this);
        mGLSurfaceview.setRenderer(new MyGLRender());
        
        setContentView(mGLSurfaceview);
    }

GLSurfaceViewはOpenGLを扱うSurfaceViewだよ。

これのsetRendererを呼び出して、GLSurfaceView.Rendererをimplementsしたインスタンスを設定します。

このRendererが描画を担当しますよ。

Rendererを見てみましょう。

public class MyGLRender  implements GLSurfaceView.Renderer {

	public void onDrawFrame(GL10 gl) {
		// TODO Auto-generated method stub
		GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
		GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
	}

	public void onSurfaceChanged(GL10 gl, int width, int height) {
		// TODO Auto-generated method stub	
	}
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		// TODO Auto-generated method stub	
	}
}

RendererではonDrawFrameで描画の処理を書きます。

今回は二つのOpenGLESのコマンドを実行してます。

GLES20.glClearColorは画面をクリアするときの色を指定します。

GLES20.glClearは実際に画面をクリアします。

要するに背景の色を指定して、背景を描画しているわけですね。

色を変えたければ、glClearColorの引数の色を変えれば良いですよ。

引数はそれぞれRGBA(赤、緑、青、透明度)に対応しています。0.0〜1.0の値を使ってね。