強火で進め

このブログではプログラム関連の記事を中心に書いてます。

glReadPixels()、glDrawTexiOES()コマンドを使ってレンダリング後の画像を利用する


3Dレンダリング後の画像を利用するプログラムを作ってみました。

例えばこのようにはちゅねがレンダリングされた画像を利用し、

その画像を左上に小さく描画してみることにします。

描画にはちょっと情報の少ないコマンドですが

glDrawTexiOES()

を使って描画します。このコマンドでテクスチャに格納された画像データを画面上に2Dに近い扱いで描画することが可能です。

使い方をざっと説明するとこんな感じです。

		GLint rect[] = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
		glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, rect);
		glDrawTexiOES(0.0, SCREEN_HEIGHT-h, 0.0, w, h);

glTexParameteriv() コマンドで現在、bindされているテクスチャのどの部分(rect)を使うかを指定し、glDrawTexiOES() コマンドで実際に描画します。

glTexParameteriv() で指定したサイズと glDrawTexiOES() コマンドで指定したサイズが異なる場合はリサイズされて描画される様です。

glDrawTexiOESの引数について細かい説明をしておきます。

glDrawTexiOES(X座標, Y座標, Z座標, 幅, 高さ)

X,Y座標についてはそのままの意味です。

Z座標については1.0以下の値でないと表示されませんでした。「値 < 1.0」の範囲で設定してみて下さい。

幅、高さについてもそのままの意味です。描画先のサイズ指定となります。

公式のドキュメントとしてはこちらの
※英語です。

Khronos OpenGL ES API Registry
http://www.khronos.org/registry/gles/

このドキュメントを参照して下さい。

とりあえず、レンダリング後のデータを取得するために glReadPixels() を実行したところエラーとなりました。色々と試してみた所、iPhoneでは GL_RGB での取得は出来ず。 GL_RGBA でないとエラーとなるようでした(現在の画面設定と同じピクセルフォーマットでないとダメってことかな?)。

ちなみにどこでエラーが発生しているかはこんな記述をエラーが発生していそうな部分の前後に記述して特定しました。

	GLenum err = glGetError();
	if(err)
		NSLog(@"0x%X error", err);

エラーコードに対応するエラーの内容についてはこちらを参照下さい。

エラー発生箇所の特定にもっと効率的な方法を知ってる方がいましたらコメント欄に情報お願いします<(_ _)>

そしてなんとか表示成功!しかし、背景が透過してます。

こういうのが良い場合も多いと思いますけど透過しない表示もしたいところ。
どうするればできるのかなぁと思ってとりあえずパッと思いついたアルファテストをOFFにすればどうだろと思い試してみました。

		glDisable(GL_ALPHA_TEST); // アルファテストの無効化
		// 元画面の半分のサイズで左下に描画
		int w=SCREEN_WIDTH*0.5;
		int h=SCREEN_HEIGHT*0.5;
		glDrawTexiOES(0.0, SCREEN_HEIGHT-h-1, 0.0, w, h);
		glEnable(GL_ALPHA_TEST); // アルファテストの有効化


成功ですw OpenGL、とっても素直な実装で一段と好きになりましたw

せっかくレンダリング画像を取得してるのでグレースケール化などしてみたのが最初の画像のこれです。

今回、解説した部分のプログラムはこの様になります。最適化についてはあまり考慮してないので実際に使うときは修正して使用して下さい。

あと、 glTexImage2D() についてなのですがこの様にレンダリング時に毎回呼ぶのは効率が悪いだろうと思い、初期化処理の部分で1回だけ呼ぶ様に修正したものも作ったのですがなぜかそちらのプログラムの方が動作が重かったです。詳細はこれから調べてみる予定ですが何か情報を知ってる方はコメント欄にて情報お願いします<(_ _)>

#define SCREEN_WIDTH		320.0
#define SCREEN_HEIGHT		480.0
#define MIKU_2DDRAW_WIDTH	512
#define MIKU_2DDRAW_HEIGHT	512
(途中省略)
	if (!miku2DDrawTexture) {
		miku2DDrawTexture = (GLubyte*)malloc(MIKU_2DDRAW_WIDTH * MIKU_2DDRAW_HEIGHT * 4);
	}
	if (miku2DDrawTexture) {
		glPixelStorei(GL_PACK_ALIGNMENT, 4);
		glReadPixels(0,0,
					 SCREEN_WIDTH, SCREEN_HEIGHT,
					 GL_RGBA,
					 GL_UNSIGNED_BYTE,
					 miku2DDrawTexture);
		// グレースケール化
		GLubyte g;
		int x, y;
		GLubyte *p = miku2DDrawTexture;
		for (y=0; y<SCREEN_HEIGHT; y++) {
			for (x=0; x<SCREEN_WIDTH; x++) {
				g = (*p + *(p+1) + *(p+2) ) / 3.0;
				*p = g;
				*(p + 1) = g;
				*(p + 2) = g;
				p += 4;
			}
		}
//		glClear(GL_COLOR_BUFFER_BIT);
		glBindTexture(GL_TEXTURE_2D, miku2DDrawTexName);
		glTexImage2D(GL_TEXTURE_2D, 
					 0,						// MIPMAPのテクスチャ解像度(使用しないときは0)
					 GL_RGBA,				// OpenGL内部でのピクセルデータのフォーマット
					 MIKU_2DDRAW_WIDTH,		// 幅
					 MIKU_2DDRAW_HEIGHT,	// 高さ
					 0,						// テクスチャの境界線	
					 GL_RGBA,				// メモリ上(格納前)のピクセルデータのフォーマット
					 GL_UNSIGNED_BYTE,		// メモリ上(格納前)のピクセルデータのデータ型
					 NULL					// メモリ上(格納前)のピクセルデータへのポインタ
		);
		glTexSubImage2D(GL_TEXTURE_2D, 
						0, 				// MIPMAPのテクスチャ解像度(使用しないときは0)
						0,				// X方向のオフセット
						0,				// Y方向のオフセット
						SCREEN_WIDTH,
						SCREEN_HEIGHT, 
						GL_RGBA,
						GL_UNSIGNED_BYTE, miku2DDrawTexture);
		GLint rect[] = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
		glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, rect);
		glDisable(GL_ALPHA_TEST); // アルファテストの無効化
		// 元画面の半分のサイズで左上に描画
		int w=SCREEN_WIDTH*0.5;
		int h=SCREEN_HEIGHT*0.5;
		glDrawTexiOES(0.0, SCREEN_HEIGHT-h-1, 0.0, w, h);
		glEnable(GL_ALPHA_TEST); // アルファテストの有効化
	}

(2008/12/19追記)
サンプルコードはこちら

(2008/12/12追記)
※厳密にはグレースケール化するときは以下の割合いで行う方が綺麗に結果となります。今回はプログラムの簡略化と処理速度を稼ぐために省略しました。

pix = r*0.299 + g*0.587 + b*0.114;