So-net無料ブログ作成
検索選択
Android OpenGL ブログトップ
前の10件 | -

Androidアプリ OpenGLのライブ壁紙を作る [Android OpenGL]



AREarthライブ壁紙をリリースしてから、1日経過した。まだ、ダウンロード数は0である。
やはり、宣伝しないとだれも気付いてくれない。
それは、さておき、本日は、ライブ壁紙の作り方についてである。

Androidのアプリには、普通のアプリと「ライブ壁紙」の2種類がある。アプリは、使いたいときに起動して使う。ライブ壁紙は、ラウンチャーの壁紙用のアプリである。単なる壁紙は、画像データのみであるが、ライブ壁紙はプログラムとなっていて、アプリをインストールすると壁紙として使用できるようになる。ライブ壁紙をインストールして選択すると、「動く壁紙」になるわけである。

スクリーンショット-3.png  スクリーンショット-4.png  スクリーンショット-5.png

さて、ライブ壁紙を作ろう、というのが本日の趣旨である。
普通のアプリなら、アクティビティを作って、その中で何かを表示させたり、ビューやレイアウトを使ってGUIを構築すればよい。
ライブ壁紙はというと、アクティビティじゃなくて、WallpaperServiceの派生クラスを作ってやる必要がある。ふーん。WallpaperServiceは、アクティビティと異なり、ラウンチャー(ホーム画面)が表示されている時だけ動いていればよいので、状態遷移がアクティビティよりは単純。
あてずっぽうではあるが、Serviceの派生クラスなんだろうな。きっと。Serviceは画面を持たない、バックグラウンドで動作するようなアプリを作るときに、使用するクラス。

WallpaperServiceをextendsして派生クラスを作って、onCreateEngineでWallpaperService.Engineのインスタンスを作って、returnで戻せばよいようである。
こんな感じ。

public class MyWallpaperService extends WallpaperService {
    @Override
    public Engine onCreateEngine() {
        return new MyEngine();
    }
}


MyWallpaperServiceを作ってみた。
エンジンの方も作らないといけない。MyEngineは、WallpaperService.Engineの派生クラスとして、以下のように定義すればよいらしい。

class MyEngine extends WallpaperService.Engine {
    @Override
    public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        super.onSurfaceChanged(holder, format, width, height);
    }
    @Override
    public void onSurfaceCreated(SurfaceHolder holder) {
        super.onSurfaceCreated(holder);
    }
    @Override
    public void onSurfaceDestroyed(SurfaceHolder holder) {
        super.onSurfaceDestroyed(holder);
    }
}


むむむ。SurfaceViewなのね。
SurfaceViewに描画するときって、どうするんだっけ...
OpenGL使うときは、どうするのよ...
と、ここまで調べて行き詰ってしまった。

そもそも、なんで「ライブ壁紙を作ろう」と思ったかというと、AREarthroidのライブ壁紙版を作りたかったから。
AREarthroidは、OpenGLのSurfaceViewを使っている。単なるSurfaceViewならそのまま、描画ルーチンを持ってくれば、よさそうだが、OpenGLのGLSurfaceViewを持ってこれるのか...
アクティビティみたいに、簡単にはいかないのか...

多分、簡単なやり方があるんだろうなぁ... という安易な気持ちで検索していく。

WallpaperServiceとOpenGLで検索。

なんか、的中はしなかったが、いろいろ調べていくと、GLWallpaperServiceなるものを発見。
どこが正式なサイトなのかよくわからないので、URLは載せないが、検索したらすぐに、見つかると思う。

で、GLWallpaperServiceを使用することに。

GLWallpaperService.jarがあったので、これをlibsフォルダに入れる。
WallpaperService を GLWallpaperServiceに変更する。

public class MyWallpaperService extends GLWallpaperService {
    @Override
    public Engine onCreateEngine() {
        return new MyEngine();
    }
}


エンジンの方は、GLWallpaperService.GLEngineからextendsで作成する。
onCreateでレンダラーを作って、setRendererしてやると描画できる。基本、GLSurfaceView.Rendererのラッパーみたいなので、setEGLConfigChooserとか、setRenderer、setRenderModequeueEventなどが使える。

queueEventについては、こちらを参照。
setRenderModeの記事は、この記事を参照。

class MyEngine extends GLWallpaperService.GLEngine {
	@Override
    public void onCreate(SurfaceHolder surfaceHolder) {
        super.onCreate(surfaceHolder);
        setEGLConfigChooser(8, 8, 8, 8, 16, 0);
        setRenderer(new MyRenderer());
    }
}


MyRendererでレンダリングする。GLWallpaperService.Rendererインタフェースを実装する必要がある。GLWallpaperService.Renderer = GLSurfaceView.Renderer みたいなので、Rendererで必要なメソッドを実装する。

class MyRenderer implements GLWallpaperService.Renderer {
	public void onDrawFrame(GL10 gl) {
		// ここで描画
	}
	public void onSurfaceChanged(GL10 gl, int width, int height) {
	}
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
	}
}


ライブ壁紙は、アクティビティではなく、サービスなので、AndroidManifest.xmlにserviceを定義する。

AndroidManifest.xml
    <application android:label="@string/app_name"
        android:icon="@drawable/ic_launcher"
        android:theme="@style/AppTheme">
	    <service android:label="MyLiveWallpaper" android:name=".MyWallpaperService"
	        android:permission="android.permission.BIND_WALLPAPER">
	        <intent-filter>
	            <action android:name="android.service.wallpaper.WallpaperService" />
	        </intent-filter>
	        <meta-data android:name="android.service.wallpaper"
	            android:resource="@xml/wallpaper" />
	    </service>
    </application>


ライブ壁紙を選択する際に必要になる。ライブ壁紙の定義をxml/wallpaper.xmlを作成して、行う。ファイル名は、AndroidManifest.xmlで指定したものに合わせる。

res/xml/wallpaper.xml
<?xml version="1.0" encoding="utf-8"?>
<wallpaper
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:thumbnail="@drawable/ic_launcher"
    android:description="@string/app_name"
/>


これで、一応は、表示させることができた。
しかし、そのままAREarthroidのレンダラーを使うと、ビットマップや3Dデータでメモリを使い過ぎ。エンジンが頻繁に生成されている模様。
アクティビティが起動するとライブ壁紙は非表示となる。このとき、WallpaperServiceのonVisibilityChangedが発生するが、GLWallpaperServiceでは、visibilityをみて、onResume、onPauseメソッドを呼び出すようになっている。
ライブ壁紙が非表示なら、GLSurfaceViewのインスタンスは必要ないので、破棄される。描画用のGLThreadも破棄されるので、テクスチャ用のビットマップやVBOも破棄されてしまうらしく、ライブ壁紙が再表示されると、挙動がおかしい。
onResumeで、ビットマップのロードを行うことでこの問題を回避した。

関連記事





nice!(0)  コメント(0) 
共通テーマ:携帯コンテンツ

Androidアプリ開発 OpenGL VBO(Vertex Buffer Object)で高速化 [Android OpenGL]



本日は、VBOの話をしようと思う。
VBOはVertex Buffer Object。頂点データをバッファオブジェクトでGPU側で管理する方法。Android端末には、CPUとGPUが搭載されており、どちらも計算を行うのだが、GPUはグラフィックス周りの計算に特化している。
OpenGLによる3Dグラフィックスは、GPUを使って計算処理されることが多い。

GPUはグラフィックスの計算に特化しているので、グラフィックス処理ならCPUより高速に実行できる。ハードウェア回路で必要な計算ができるようになっている。
CPUとGPUの違いは、グラフィックス処理に特化しているかどうかだけ。基本的なしくみは変わらない。CPU、GPUの内部には計算のためにレジスタが存在しているが、レジスタの数は多くないし、容量も少ない。大量のデータは、メモリに保存されていて、計算する際にレジスタに持ってくる。

CPUとメモリの関係は、「ポインタが理解できない理由」に詳しく書いてあるので、興味があればどうぞ。

C言語 ポインタが理解できない理由 [改訂新版] (プログラミングの教科書)

C言語 ポインタが理解できない理由 [改訂新版] (プログラミングの教科書)

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/04/08
  • メディア: 単行本(ソフトカバー)



多くのデスクトップPCでは、CPUが使うメモリとGPUが使うメモリが別になっている。特にゲームをするために、ビデオカードを拡張している場合は、ビデオカードにGPUとGPUが使うメモリが搭載されている。
ノートPCや廉価版のPCでは、CPUにGPUが統合されたオールインワンのCPUが使われている場合がある。このタイプのCPUでは、メモリは同じ。GPUが使うメモリはメインメモリの一部が割り当てられる感じになる。

GPUが使うメモリは「フレームバッファ」または単に「バッファ」と呼ばれる。ビデオメモリとかVRAMと呼ばれる場合もある。

Android端末にもGPUは搭載されている。OpenGLの3D機能を使えば、おのずとGPUが使われる。しかし、OpenGLを使わない普通のCanvasでの描画では、GPUは使用されていない模様。なんともったいない。

ちょっと調べてみる

http://techbooster.jpn.org/andriod/application/7054/

によると、Android 3.0以降なら、Cnavas描画でもGPUアクセラレーションが可能であるらしい。
AndroidManifest.xmlでandroid:hardwareAcceleratedの設定が必要なのか。
2.3だと無視されるのかなぁ...
AREarthroidで実験してみたが、あまり変化はない。3Dの部分はそのままだからなぁ...
2.3だと、Canvas#isHardwareAcceleratedメソッドが存在しないエラーになる。3.0以降じゃないとだめ。

VBO

やっと、本題のVBOである。
VBOを使わない場合、頂点データや法線データは、メインメモリ上にある。描画する際は、GPUを使うので、頂点データは、GPU側のメモリにコピーされる。コピーは描画の度に行われる。
VBOを使うと、メインメモリからGPU側のバッファに移動されて常駐するようになる。描画の度にコピーする必要がなくなるので、その分高速に描画できる。
バッファに頂点データというオブジェクトを作るので、Vertex Buffer Objectとなる。

VBOを使うと描画を高速化できる」というわけ。

ただし、OpenGL 1.1の機能になるため、Android端末のバージョンによっては使用できない。

さて、実際のコードはどうなるのかというと、AREarthroidから一部を抜いてみよう。

    	int[] vboIds = new int[4];
    	GL11 gl11 = (GL11) gl;
    	gl11.glGenBuffers(4, vboIds, 0);	// バッファを4つ作成
    	vertexBufferObjectId = vboIds[0];
    	normalBufferObjectId = vboIds[1];
    	textureBufferObjectId = vboIds[2];
    	elementBufferObjectId = vboIds[3];
    	// 頂点データをGPUにアップロード
    	gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexBufferObjectId);
    	vertexByteBuffer.position(0);
    	gl11.glBufferData(GL11.GL_ARRAY_BUFFER, vertexByteBuffer.capacity(), vertexByteBuffer, GL11.GL_STATIC_DRAW);
    	// 法線
    	gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, normalBufferObjectId);
    	normalByteBuffer.position(0);
    	gl11.glBufferData(GL11.GL_ARRAY_BUFFER, normalByteBuffer.capacity(), normalByteBuffer, GL11.GL_STATIC_DRAW);
    	// テクスチャ
    	gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, textureBufferObjectId);
    	textureByteBuffer.position(0);
    	gl11.glBufferData(GL11.GL_ARRAY_BUFFER, textureByteBuffer.capacity(), textureByteBuffer, GL11.GL_STATIC_DRAW);
    	// 面データもアップロード
    	gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, elementBufferObjectId);
    	indexBuffer.position(0);
    	gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity() * SHORT_SIZE, indexBuffer, GL11.GL_STATIC_DRAW);
    	// CPU側メモリのバッファはもういらない
    	vertexBuffer = null;
    	vertexByteBuffer = null;
    	normalBuffer = null;
    	normalByteBuffer = null;
    	textureBuffer = null;
    	textureByteBuffer = null;
    	indexBuffer = null;


まず、glGenBuffersでバッファオブジェクトを作成する。バッファオブジェクトは番号で識別されるので、これを受けるためのint配列を渡してやる。最初の引数は作成するオブジェクトの個数。第二引数が、番号の配列。第三引数はオフセット。

    	gl11.glGenBuffers(4, vboIds, 0);	// バッファを4つ作成


これで、4つのオブジェクトが作成される。オブジェクトの番号が配列vboIdsに入って戻ってくる。このまま使っても問題はないが、わかりやすくするために、フィールド変数で記憶する。

    	vertexBufferObjectId = vboIds[0];
    	normalBufferObjectId = vboIds[1];
    	textureBufferObjectId = vboIds[2];
    	elementBufferObjectId = vboIds[3];


vertexBufferObjectIdが、頂点データの入ったオブジェクトの番号。
normalBufferObjectIdが、法線データの入ったオブジェクトの番号。
以下は省略。

次に、バッファオブジェクトにデータを突っ込んでいく。CPU側のメモリからGPU側のメモリにデータをコピーする。

    	// 頂点データをGPUにアップロード
    	gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexBufferObjectId);
    	vertexByteBuffer.position(0);
    	gl11.glBufferData(GL11.GL_ARRAY_BUFFER, vertexByteBuffer.capacity(), vertexByteBuffer, GL11.GL_STATIC_DRAW);


glBindBufferで、どのバッファを使うのかを番号で指定する。
vertexByteBufferが頂点データの入ったCPU側メモリのバッファ。先頭からデータが入るように、position(0)でポインタの位置を先頭にしている。
glBufferDataで、vertexByteBufferのデータがGPU側のVBOに転送される。
GL_STATIC_DRAWは、バッファオブジェクトの用途。

同様な手法で、法線データ、テクスチャマッピングデータ、面データもVBOにしてGPU側にアップロードする。面データは、バッファの種類が異なるので注意。

VBOはこれで完成。CPU側メモリのバッファは不要になるので、nullをセット。

次に、VBOを使って描画する方法。

VBOを使わない場合は、glVertexPointerでCPU側メモリのバッファを指定すればよかったが、VBOの場合は、glBindBufferでVBOの番号を指定してから、glVertexPointerでポインタを設定する。

		// 頂点バッファ設定
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		if ( useVBO ){
			gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexBufferObjectId);
			gl11.glVertexPointer(3, GL10.GL_FLOAT, 12, 0);
		}
		else {
			gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
		}


useVBOはVBOを使用しているかどうがのフラグ。if文のtrueブロックがVBOを使った場合の処理になる。
法線データ、テクスチャ、面データも同様に処理を行う。
glDrawArraysなどの描画関数呼び出しは変更しなくてOK。
glDrawElementsは、indexBufferの指定が、オブジェクトになるので、GL11にあるglDrawElementsを使う。

		if ( useVBO ){
			gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, elementBufferObjectId);
			gl11.glDrawElements(GL10.GL_TRIANGLES, elementCount * 3, GL10.GL_UNSIGNED_SHORT, 0);
		}
		else {
			gl.glDrawElements(GL10.GL_TRIANGLES, indexBuffer.capacity(), GL10.GL_UNSIGNED_SHORT, indexBuffer);
		}


バッファオブジェクトは、使用されなくなったら、メモリから削除できる。削除は、glDeleteBuffersで行うことができる。

    	gl11.glDeleteBuffers(4, vboIds, 0);


引数は、glGenBuffersと同じ。
GLThreadから呼び出さないと無効なので注意。

AREarthroid


かんたんAndroidアプリ作成入門 (プログラミングの教科書)

かんたんAndroidアプリ作成入門 (プログラミングの教科書)

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2013/04/16
  • メディア: 単行本(ソフトカバー)







nice!(0)  コメント(0) 
共通テーマ:携帯コンテンツ

Androidアプリ開発 OpenGL レンダーモードでonDrawFrameの呼び出しを制御 [Android OpenGL]



本日は、OpenGLの話題である。Androidでは、GLSurfaceViewを作って、RendererのonDrawFrameで描画処理を行う。onDrawFrameはGLSurfaceViewのスレッド上で実行される。
これまでのサンプルのように、何も考えずに、GLSurfaceViewを使うと、レンダラーのonDrawFrameは絶え間なく呼び出される

試しに、onDrawFrameでログを出力するようにしてみるとわかるが、えらい勢いでログが流れていくことがわかる。

onDrawFrameを任意のタイミングで実行させたい場合、GLSurfaceViewで以下のような設定をすればよい。レンダラーのセットの後で行う。

	public SampleGLSurfaceView(Context context) {
		super(context);
		this.context = context;
		setEGLConfigChooser(8, 8, 8, 8, 16, 0);
		renderer = new OpenGLRenderer();
		setRenderer(renderer);
		setRenderMode(RENDERMODE_WHEN_DIRTY);
	}


setRenderModeRENDERMODE_WHEN_DIRTYを指定する。
RENDEREMODE_WHEN_DIRTYは、GLSurfaceViewで定義されているstatic finalフィールド。
その他のレンダーモードは、RENDERMODE_CONTINUOUSLYがあり、こちらがデフォルト。

で、RENDERMODE_WHEN_DIRTYにすると、最初に一回だけonDrawFrameが呼び出され描画されるが、以降は呼び出されなくなる。
描画させたいタイミングで、GLSurfaceViewのrequestRenderを呼び出す必要がある。

requestRenderは、UIスレッドから呼び出してかまわない。queueEventを使う必要はない。onDrawFrameはGLSurfaceViewのスレッドで実行されるので注意。

サンプルアプリでは、onScrollでrequestRenderを呼び出してみた。

	public boolean onScroll(MotionEvent event1, MotionEvent event2, float distx, float disty) {
		eyepos[0] += distx * 0.01;
		eyepos[1] += disty * 0.01;
		requestRender();
		return true;
	}


スライドさせると、onScrollが呼び出され、requestRenderされるので、onDrawFrameで描画され動くようになる。
スライドさせなければ、イベントが発生せず、onDrawFrameも呼び出されない。

onDrawFrameでドロイド君を回転をさせている。レンダーモードを変更したことにより、スライドをして再描画をかけている間でないとドロイド君が回転しなくなってしまった。まぁ、これは、別のスレッドを作るなりすれば解決しそう。

物理エンジンと組み合わせるときは、こっちのレンダーモードの方がよさそう。シミュレーションのループで、位置の計算、レンダリングのリクエストを行えばよい。

連続してフル回転で描画すればそれなりに電力も消費する。RENDERMODE_WHEN_DIRTYにして、適度にフレームレートを抑えた方が省電力であろう。

実は、AREarthroid ver 1.2.6は、連続してonDrawFrameが呼び出されフル回転状態となっている。
スマホでは、バッテリーがすぐになくなる。省電力化はけっこう大事。早速、RENDERMODE_WHEN_DIRTYに変更した。ver 1.2.7で対応している。

AREarthroid ARで地球を表示するアプリ


かんたんAndroidアプリ作成入門 (プログラミングの教科書)

かんたんAndroidアプリ作成入門 (プログラミングの教科書)

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2013/04/16
  • メディア: 単行本(ソフトカバー)







nice!(0)  コメント(0) 

Androidアプリ開発 OpenGL queueEventを使う [Android OpenGL]



本日は、久しぶりにOpenGLネタである。といっても純粋にOpenGLのネタかというとそうでもなく、どちらかというとAndroidの方かな。

OpenGLで何か表示しようと思ったら、GLSurfaceViewを使う必要がある。GLSurfaceViewも、SurfaceViewもどちらも描画専用のスレッドを持っている。onDrawFrameはこの専用のスレッドで実行されている。
SurfaceViewでない単なるビューは。描画用のスレッドがない。アクティビティと同じUIスレッドで描画が行われる。マルチスレッドで動くことになると、スレッド間でのデータ通信が必要になってくる。

描画用のデータと、アクティビティで使うデータがきっちり分かれていれば、スレッド間でデータのやりとりを行う必要はない。しかし、同じアプリの中で動いているスレッドなので、普通に考えれば何かしらのデータ通信が必要。

データ通信といっても、単にメソッドを呼び出すだけの場合が多い。publicフィールドにして直接フィールドにアクセスしてしまう、という手もあるが「カプセル化」といった面からみてもこれは避けた方が無難。

アクティビティの方で、メニューのイベントやら、タップイベントやらをコールバックで処理していたとする。それを受けて「これは再描画が必要だな」っていうことで、viewのinvalidateを呼び出してはいけない。viewがSurfaceViewじゃなければ問題はない。viewがSurfaceViewだとinvalidateを呼び出した瞬間に例外となる。

じゃあ、どうすればよいかというと、postInvalidate()を呼び出せばOKみたい。しかし、GLSurfaceViewだと描画し続ける、っていうのが普通だと思う(「無効になって必要な時だけ」という描画方法にもできるみたいではあるが)。invalidate()はあまり行われない。

最初にやってエラーをくらうのは、メインスレッドの方で、OpenGLのちょっとした有効、無効を切り替えようと思った時。OpenGLのAPIを呼び出す場合、AndroidのOpenGL ESだとGL10のインスタンスが必要だが、これは適当にフィールドで覚えておいて、glRememberField.glEnable(GL10.GL_LIGHT)とかやってみたりすると、以下のようなエラーになってしまう。

E/libEGL(21579): call to OpenGL ES API with no current context (logged once per thread)


よくありがちなのは(AREarthroidでもそうだが)、テクスチャ画像の読み込みは、メインのUIスレッドで行って、onDrawFrameはなるべく止めずに、っていう感じでスレッドの役割分担をしたとき。
onCreateSurfaceでテクスチャを読み込んでいる分には、OpenGLのスレッドでやっているので問題はない。読み込みをメインスレッドでやって、ビットマップをロードするために、texImage2Dを呼び出すとこのエラーが発生する。

では、どうすればよいかというと、queueEventを使う。

queueEventはGLSurfaceViewのメソッドで、OpenGLのスレッドに実行を依頼するもの。使い方が少々まどろっこしい。
どうするかというと、queueEventの引数に、Runnableインターフェースを実装したオブジェクトのインスタンスを作って渡してやる。クラスを作ってもよいが、名前なしでインスタンスを生成してしまうことが多い。
Runnableインターフェースには、メソッドvoid run()が必要なので、このメソッドを作ってやり、メソッド内でスレッドで実行させたいコードを書く。
以下、が実際の例。

	@Override
	public void onLongPress(MotionEvent arg0) {
		queueEvent(new Runnable(){
			public void run(){
				glRememberField.glEnable(GL10.GL_LIGHT);
			}
		});
	}


SampleGLSurfaceViewのonLongPressで使ってみた。glRememberFieldは、onDrawFrameで引数のglをそのまま代入。
どこかのstaticフィールドにありそうではあるが、見つからなかったので。onDrawFrameで引数のglを代入して記憶している。使い方は間違っているかも知れない。ちょっと自信がない。

ライティングする、しないのスイッチだけなら、フィールド変数を作成して、onDrawFrameでそのフィールド変数を見て処理をわけた方が普通の作り方だと思う。

時間がかかりそうな処理を別スレッドでやらせて、処理が終わった時点で、OpenGL側にそれを伝えるわけだが。そのスレッド上でそのまま、OpenGLのAPI呼び出しをすると、call to OpenGL ES API with no current contextのエラーが発生する。なので、queueEventを使ってGLThreadで処理を行うようにすればOK。

もうひとつ。

onDrawFrameで複数のモデルを描画しているとする。ここにもうひとつ追加したいとする。リストになっているので、newしてaddすればOKなのだが、これをメインスレッドで行うと、リストが競合して、エラーになってしまう(場合がある)。
スレッドの処理タイミングによっては、競合が発生しないので、何事もなかったかのように動作する。マルチコアのCPUを持つ端末でやると発生しやすかったりする。
onDrawFrameでリストを参照して描画処理を行っている最中に、別のスレッドでリストへの追加が起きるとエラーになる。そこで、リストへの追加処理をqueueEventを使ってやるようにする。そうすれば、GLThreadで、リストへの追加が行われるので、うまくいくようになる。

AREarthroid





nice!(0)  コメント(0) 

Androidアプリ開発 OpenGL ワイヤーフレーム [Android OpenGL]



さて、AREarthroidにパーティクルで、星を描画するような拡張することを考えていたが、星が表示されても。きれいなだけで何の役にも立たないっていうのじゃぁねぇ。実際の位置に星を表示できれば、「星座を探す」とかできそうではあるが、そういうのは、専用のアプリでやってもらおう。

どうせ拡張するのなら、役に立つ機能の方がいいでしょ。ということで、ワイヤーフレーム表示について調べてみるのである。点を描画することがポイントスプライトなら、ワイヤフレームは線を描画するテクニック。昔からある3DCGの手法。

ワイヤーフレームで何を表示するかというと、マーカーへのガイドを表示しようと思っている。で、かっこよく映画やアニメのような光っている感じのインジケーターっぽいのにしたいわけである。何をいっているかよくわからないであろうから、ちょっとyoutubeで探してみる。



こんな感じがいいかな。
で、緑色に半透明で光っている部分は、ワイヤーフレームで描いていると思われるので、どうやって描けばよいのかを調べるのである(微妙に角丸みたいになっているからテクスチャマッピングかも)。

OpenGLで線を描くだけなら、GL_LINESGL_LINE_STRIPで描画できる。しかしながら、GL_LINE_STRIPは「折れ線」を描くルーチンなので、連続した頂点データをそのまま繋いでいった線を描くだけなのです。

詳細は、床井先生のページを参照

http://www.wakayama-u.ac.jp/~tokoi/opengl/libglut.html#5.2

えーと、それでサンプルではふたつの三角形で四角い面を作ってドロイド君をマッピングして表示しているが、これをそのままワイヤーフレームで表示しても、三角形が表示されてしまうわけです。
テストするには都合が悪いので、頂点数4のモデルを作ることにする。

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

public class WireFrame {
	private FloatBuffer buffer;
	private float x, y, z;

	public WireFrame(float x, float y, float z) {
		this.x = x;
		this.y = y;
		this.z = z;
		float vertex[] = {
				-1.0f, -1.0f, 0.0f,
				 1.0f, -1.0f, 0.0f,
				 1.0f,  1.0f, 0.0f,
				-1.0f,  1.0f, 0.0f,
		};
		ByteBuffer vb = ByteBuffer.allocateDirect(vertex.length * 4);
		vb.order(ByteOrder.nativeOrder());
		buffer = vb.asFloatBuffer();
		buffer.put(vertex);
		buffer.position(0);
	}

	public void draw(GL10 gl) {
		gl.glPushMatrix();
		gl.glTranslatef(x, y, z);
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, buffer);
		gl.glLineWidth(4.0f);
		gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, 4);		// GL_LINE_LOOPで描画
		gl.glPopMatrix();
	}

}


GL_LINE_STRIPでは、最後の線が表示されないため、GL_LINE_LOOPで描画する。
ワイヤフレームでの線の太さは、glLineWidthで設定する。4ドットくらいは欲しいので、4を指定してみる。単位はドットらしい。

レンダラーのフィールドでインスタンスを記憶する。

public class SampleGLSurfaceView extends GLSurfaceView implements OnGestureListener {
	private ArrayList<Model> models = new ArrayList<Model>();
	private ArrayList<WireFrame> wireframes = new ArrayList<WireFrame>();


onSurfaceCreatedでワイヤーフレームのモデルを作成。

		@Override
		public void onSurfaceCreated(GL10 gl, EGLConfig config) {
			gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
			gl.glClearDepthf(1.0f);
			eyepos[0] = 0;
			eyepos[1] = 0;
			eyepos[2] = 3;
			Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
			// テクスチャを生成
			int textures[] = new int[1];
			gl.glGenTextures(1, textures, 0);
			gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
			models.add(new Model(1, 0, 0));
			models.add(new Model(-1, 0, 0));
			models.add(new Model(1, 0, -2));
			models.add(new Model(-1, 0, -2));
			wireframes.add(new WireFrame(0, 0, 0f));
			wireframes.add(new WireFrame(0, 0, 1f));
			wireframes.add(new WireFrame(0, 0, 2f));
		}
	}


次に、onDrawFrameで描画する。
色は、ライティングしたいので、普通にアンビエント、ディフューズで設定してみる。
アルファブレンド、テクスチャはなし。

		@Override
		public void onDrawFrame(GL10 gl) {
			gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

			// ライティングをON
			gl.glEnable(GL10.GL_LIGHTING);
			// 光源を有効にして位置を設定
			gl.glEnable(GL10.GL_LIGHT0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightpos, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, white, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, white, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, white, 0);

			gl.glMatrixMode(GL10.GL_MODELVIEW);
			gl.glLoadIdentity();

			// カメラ位置を設定
			GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);

			// スムースシェーディング
			gl.glShadeModel(GL10.GL_SMOOTH);
			// フォグ
			gl.glEnable(GL10.GL_FOG);
			gl.glFogfv(GL10.GL_FOG_COLOR, black, 0);
			gl.glFogx(GL10.GL_FOG_MODE, GL10.GL_LINEAR);
			gl.glFogf(GL10.GL_FOG_START, 3.0f);
			gl.glFogf(GL10.GL_FOG_END, 10.0f);

			// ソート
			Collections.sort(models, new ModelComparator());

			for ( Model m : models ){
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
				gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
				gl.glEnable(GL10.GL_TEXTURE_2D);
				gl.glEnable(GL10.GL_BLEND);
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
				m.draw(gl);

				// 2回目の表示はハイライトを出すため
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, transparent, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, half_white, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, half_white, 0);
				gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
				m.draw(gl);
				
				m.rotate();
			}

			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, white, 0);
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, white, 0);
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, white, 0);
			gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
			gl.glDisable(GL10.GL_BLEND);
			gl.glDisable(GL10.GL_TEXTURE_2D);

			for ( WireFrame wireframe : wireframes ){
				wireframe.draw(gl);
			}
		}


とここまでで、次のような表示ができた。



次は、カリングしてみる。
線だけなので、表、裏はないと思うが...



案の定、ドロイド君は反対を向くと表示されない。線は面ではないので、表裏がない。従って、カリングすることはできない。もちろん、面として法線データも作成して、それを線画で表示しているだけなら、カリングすることは可能であると思われる。

回転させてみてわかったのだが、ポイントスプライトのように、常にこちら向きということはないようである。回転により線の太さが変化している。
ポイントスプライトと同様に、遠近方は無視されている模様。GL_POINT_DISTANCE_ATTENUATIONのLINE版があるのかどうかは不明。

同様に、デプステストしてみる。



WireFrameをループで作成するように変更。手前の方から作成している。ドロイド君の透明部分が描画されてしまうので、デプステストとともに、アルファテストも有効にした。
ワイヤーフレームの部分もフォグが有効になっている。奥の方の線が暗くなっているので、遠近感はある。ちゃんと手前の方が描画されている。

気になるアルファブレンドは?

アルファ値が0.7の緑色をワイヤフレームの色属性にして、ブレンドONにして、どうよ。



おお、なんとなくそれっぽくなったじゃん。
もうちょっと明るい緑がいいか。AREarthroidに組み込む時は赤かなぁ。

次回は、queueEvent>>





nice!(0)  コメント(0) 

Androidアプリ開発 OpenGL ポイントスプライト 点の大きさ [Android OpenGL]



前回、ポイントスプライトでドロイド君を、たくさんの星のように表示させることができた。
しかし、「何かがおかしい」ことにお気付きだろうか。



えーと、何がおかしいかというと、どの点も同じ大きさで表示されるから。つまり遠近法が無視されているっていうこと。位置座標はちゃんと3次元なので視点の移動にともなっての移動は、ちゃんと遠近法が効いている。しかし、大きさは、glPointSizeで指定されたままの大きさで変化しないので、「なんかおかしい」っていうことになっている。

ということは、glPointSizeで指定する点の大きさを視点からの距離で計算してやればOK?

その通りです。
ですが、もっと簡単にできてしまう。OpenGL 1.1の機能になってしまうが、glPointParameterfvでGL_POINT_DISTANCE_ATTENUATIONを指定してやるだけで点の大きさが遠近法で計算されるようになる。

では、さっそくやってみよう。パラメータの設定は1回だけでよいので、onSurfaceCreatedでやってしまう。

		@Override
		public void onSurfaceCreated(GL10 gl, EGLConfig config) {
			gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
			gl.glClearDepthf(1.0f);
			eyepos[0] = 0;
			eyepos[1] = 0;
			eyepos[2] = 3;
			Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
			// テクスチャを生成
			int textures[] = new int[1];
			gl.glGenTextures(1, textures, 0);
			gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
			models.add(new Model(1, 0, 0));
			models.add(new Model(-1, 0, 0));
			models.add(new Model(1, 0, -2));
			models.add(new Model(-1, 0, -2));
			ByteBuffer b = ByteBuffer.allocateDirect(3 * 4);
			b.order(ByteOrder.nativeOrder());
			FloatBuffer distance = b.asFloatBuffer();
			distance.put(new float[]{0f,0f,1f});
			distance.position(0);
			((GL11)gl).glPointParameterfv(GL11.GL_POINT_DISTANCE_ATTENUATION, distance);
		}


glPointParameterfvを呼び出すには、GL11にキャストしないといけない。OpenGL1.0しかサポートしていない端末だと、多分エラーになる(CastExceptionかなぁ...ならないかも知れない)。 その辺りはケアーしていないので注意して欲しい。
glPointParameterfvの引数がFloatBufferしか受け付けないようなので、バッファを作るのが面倒なことになっているが、基本、0,0,1といったパラメータの値をセットしているだけである。

で、動かしてみたのが、以下の動画。



ちゃんと近くにあれば大きく、遠くにあれば小さく表示されるようになった。

次回は、ワイヤーフレーム>>





nice!(0)  コメント(0) 

Androidアプリ開発 OpenGL ポイントスプライト [Android OpenGL]



本日は「ポイントスプライト」をやってみる。AREarthroidでは使用していないテクニックだが、うまくいけば組み込むかも知れない。



ポイントは「点」という意味。ポリゴンは面なので、頂点がいくつか集まって構成されている。OpenGL ESでは、三角形しか扱えないので、ポリゴンの頂点数は3となる。
OpenGLでは、面しか描けないかというとそうでもなく、GL_LINESなら線を描けるし、GL_POINTSなら点を描画することができる。OpenGL ESにも点、線の描画機能がある。

スプライトっていうのは、ゲーム用語なのかな。2Dゲーム中のキャラクタを、高速に表示させるためのしくみのことを言っていたと記憶している。

で、3DCGのポイントスプライトになると、表示する位置は、点で決めてやって、そこにいつもこちら向きにないように仮想的な面を作って、テクスチャマッピングして表示する。っていうことらしい。

パーティクルを使った粒子シミュレーションでは、多くの点を描画しないといけない。量が多くなれば、それだけ精密に見える。面より点の方がデータ量が少ないので、大量のパーティクルを作りたいときは点の方が有利。

うんちくはこれくらいにして、まずは、点を描画してみよう。

まじめにデータを作成していると日が暮れるので、乱数で生成することにした。

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

public class Points {
	private FloatBuffer buffer;			// 頂点用バッファ
	private final int numOfPoints = 1000;

	public Points() {
		ByteBuffer vb = ByteBuffer.allocateDirect(numOfPoints * 3 * 4);
		vb.order(ByteOrder.nativeOrder());
		buffer = vb.asFloatBuffer();
		float x, y, z;
		for ( int i = 0; i < numOfPoints; i++ ){
			x = (float)(Math.random() * 8.0 - 4.0);
			y = (float)(Math.random() * 8.0 - 4.0);
			z = (float)(Math.random() * 8.0 - 6.0);
			buffer.put(x);
			buffer.put(y);
			buffer.put(z);
		}
		buffer.position(0);
	}

	public void draw(GL10 gl) {
		gl.glPushMatrix();		// マトリックス記憶
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, buffer);
		gl.glDrawArrays(GL10.GL_POINTS, 0, numOfPoints);
		gl.glPopMatrix();		// マトリックスを戻す
	}

}


クラスを追加した。ソースファイルはPoints.javaである。
何をやっているかというと、1000個の点をランダムな位置に生成して描画いる。っていうだけ。

これをレンダラーで描画する。

public class SampleGLSurfaceView extends GLSurfaceView implements OnGestureListener {
	private ArrayList models = new ArrayList();
	private Points points = new Points();
	private float eyepos[] = new float[3];


Pointsのインスタンスは、フィールドpointsで作成。
onDrawFrameで、ドロイド君を描画する前でpointsのdrawを呼び出して描画っと。

		@Override
		public void onDrawFrame(GL10 gl) {
			gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

			// ライティングをON
			gl.glEnable(GL10.GL_LIGHTING);
			// 光源を有効にして位置を設定
			gl.glEnable(GL10.GL_LIGHT0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightpos, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, white, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, white, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, yellow, 0);

			gl.glMatrixMode(GL10.GL_MODELVIEW);
			gl.glLoadIdentity();

			// カメラ位置を設定
			GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);

			gl.glEnable(GL10.GL_BLEND);
			gl.glEnable(GL10.GL_TEXTURE_2D);
			// スムースシェーディング
			gl.glShadeModel(GL10.GL_SMOOTH);
			// フォグ
			gl.glEnable(GL10.GL_FOG);
			gl.glFogfv(GL10.GL_FOG_COLOR, black, 0);
			gl.glFogx(GL10.GL_FOG_MODE, GL10.GL_LINEAR);
			gl.glFogf(GL10.GL_FOG_START, 3.0f);
			gl.glFogf(GL10.GL_FOG_END, 10.0f);

			// ポイントスプライトを表示
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, white, 0);
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, white, 0);
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, white, 0);
			gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
			gl.glDisable(GL10.GL_TEXTURE_2D);
			gl.glDisable(GL10.GL_BLEND);
			points.draw(gl);

			// ソート
			Collections.sort(models, new ModelComparator());

			for ( Model m : models ){
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
				gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
				gl.glEnable(GL10.GL_TEXTURE_2D);
				gl.glEnable(GL10.GL_BLEND);
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
				m.draw(gl);

				// 2回目の表示はハイライトを出すため
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, transparent, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, half_white, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, half_white, 0);
				gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
				m.draw(gl);
				
				m.rotate();
			}
		}





おお、フォグも効いているので、宇宙空間的な感じになったではないか。
これは、AREarthroidにも組み込まないといけないか。

ここまでで、点を描画できたので、拡張して、テクスチャを貼ってやれば、ポイントスプライトの完成となる。


		@Override
		public void onDrawFrame(GL10 gl) {
			gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

			// ライティングをON
			gl.glEnable(GL10.GL_LIGHTING);
			// 光源を有効にして位置を設定
			gl.glEnable(GL10.GL_LIGHT0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightpos, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, white, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, white, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, yellow, 0);

			gl.glMatrixMode(GL10.GL_MODELVIEW);
			gl.glLoadIdentity();

			// カメラ位置を設定
			GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);

			gl.glEnable(GL10.GL_BLEND);
			gl.glEnable(GL10.GL_TEXTURE_2D);
			// スムースシェーディング
			gl.glShadeModel(GL10.GL_SMOOTH);
			// フォグ
			gl.glEnable(GL10.GL_FOG);
			gl.glFogfv(GL10.GL_FOG_COLOR, black, 0);
			gl.glFogx(GL10.GL_FOG_MODE, GL10.GL_LINEAR);
			gl.glFogf(GL10.GL_FOG_START, 3.0f);
			gl.glFogf(GL10.GL_FOG_END, 10.0f);

			// ポイントスプライトを表示
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, white, 0);
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, white, 0);
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, white, 0);
			gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
			gl.glEnable(GL10.GL_TEXTURE_2D);
			gl.glEnable(GL10.GL_BLEND);
			gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
			//ポイントスプライト
			gl.glEnable(GL11.GL_POINT_SPRITE_OES);
			gl.glActiveTexture(GL10.GL_TEXTURE0);
			gl.glTexEnvf(GL11.GL_POINT_SPRITE_OES, GL11.GL_COORD_REPLACE_OES, GL10.GL_TRUE);
			gl.glPointSize(32.0f);
			points.draw(gl);





わわ、ドロイド君がいっぱい。

次回は、ポイントスプライトの点の大きさ>>





nice!(0)  コメント(0) 

Androidアプリ開発 OpenGL フォグ [Android OpenGL]



本日の話題は「フォグ」である。3DCGにおいて、フォグといったテクニックは、遠くにあるものを「霧がかかっているよう」に白っぽく霞ませて表示する手法。

現実世界には、見えないようでいて、実は見えている「空気」というものがある。遠くにある物からの光は、それだけ空気の中を多く進んでやってくる。遠くにある物ほどぼやけた感じに見える。フォグをかければ、より現実に近い世界を表現することが可能になる。

実は、AREarthroidでもフォグをかけようと試みたのではあるが、視点位置が反映されず、フォグの導入は見送っていた。今回、あらためてチャレンジすることにした。

フォグをかけるのは簡単。glEnable(GL_FOG)として有効にすればよい。ただし、フォグのかけ方を指定しないといけない。サンプルでは、とりあえずGL_LINEARのフォグをかけることにする。
さらに、GL_LINEARでは、フォグがかかり始める視点からの距離と、終端の距離を指定する必要がある。
もうひとつ、フォグの色をGL_FOG_COLORで設定する。

			// フォグ
			gl.glEnable(GL10.GL_FOG);
			gl.glFogfv(GL10.GL_FOG_COLOR, black, 0);
			gl.glFogx(GL10.GL_FOG_MODE, GL10.GL_LINEAR);
			gl.glFogf(GL10.GL_FOG_START, 3.0f);
			gl.glFogf(GL10.GL_FOG_END, 10.0f);


フォグの色を背景色と同じ黒にしたので、背景に溶け込むようになっていくはず。
フォグのかかり始めは、後列のドロイド君がいる辺りからに設定。その奥10までいったところで完全に黒になる。

スライドにより左右、上下に視点が移動するが、これだと遠い、近いを制御しずらいので、X軸方向の移動をやめ、Z軸方向に変更する。Y軸方向はそのまま。

	@Override
	public boolean onScroll(MotionEvent event1, MotionEvent event2, float distx, float disty) {
		//eyepos[0] += distx * 0.01;
		eyepos[1] += disty * 0.01;
		eyepos[2] += distx * 0.01;
		return true;
	}


これで、左右にスライドすることで、Z方向に視点が動くようになる。

では、フォグの効果をみてみよう。



ドロイド君を視点から離すと黒くなっていく。逆に近付ければフォグの黒は薄くなっていく。フォグの色を白にすれば、霞んでいくようにみえる。ただ、その場合は背景色も白にしないとおかしいでしょう。


話は変わって、AREarthroidである。AREarthroidでもフォグをかけようとしたのではあるが、視点からの距離ではなく、原点(0,0,0)からの距離でフォグがかかってしまう状況であった。
つまり、視点を変えてもフォグのかかり具合が変化しなかったので、グラデーションはかかっているが、視点位置からの計算になっていないので、「何かおかしい絵柄」になっていた。

フォグのサンプルでも同じようにフォグをかけているし、視点の移動もしている。なんでAREarthroidではできないのじゃ?とデバッグを始めた。

違いは、glLookAtの呼び出しであることがわかった。サンプルでは、

	gl.glMatrixMode(GL10.GL_MODELVIEW);
	gl.glLoadIdentity();
	GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);


といった順番で処理を行っている。
AREarthroidでは、GL_MODELVIEWでマトリックスを選択する前で、行っている。本当のコードではないが、以下のような感じ。

	gl.glMatrixMode(GL10.GL_PROJECTION);
	gl.glLoadIdentity();
	GLU.gluPerspective(gl, 50f, aspect, 0.01f, 100f);
	GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);
	gl.glRotatef(azimuth, ox, oy, oz);
	gl.glMatrixMode(GL10.GL_MODELVIEW);
	gl.glLoadIdentity();


gluLookAtは、視点用のGL_PROJECTIONマトリックスに対して値を設定するんじゃないの?どうも違うっぽいなぁ。

むむむ。なんとしたことか。

AREarthroidでは、視点を方位で回転させるようなことをしている。じゃないとコンパスにならないから。実際、ちゃんと方角に合わせて地球は回転している。

よくわからんなぁ。

ちょっと順番かえてみよ。

	gl.glMatrixMode(GL10.GL_PROJECTION);
	gl.glLoadIdentity();
	GLU.gluPerspective(gl, 50f, aspect, 0.01f, 100f);
	gl.glMatrixMode(GL10.GL_MODELVIEW);
	gl.glLoadIdentity();
	GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);
	gl.glRotatef(azimuth, ox, oy, oz);


おお、これでも同じように表示されるじゃないか。しかし、なんか微妙に違うかも...
むしろ、いい感じになっている。

じゃあこれでフォグかけたら... あら真っ白。開始、終了距離を調整して...

あらま。ちゃんとフォグがかかった。

あー、またバージョンアップせねば。

次回は、ポイントスプライト>>





nice!(0)  コメント(0) 

Androidアプリ開発 OpenGL テクスチャを貼りつけたときのハイライト [Android OpenGL]



前回、何に使うかわからない、と書いた(GL_SRC_ALPHA, GL_ONE)でのブレンド方法であるが、使い方を発見してしまった。

まず、OpenGL ESでは、テクスチャを貼ると、ハイライトが出ない(陰影は付くのではあるが)。スペキュラの値を調整してもテクスチャ画像の色より大きくなることがない。なので、白いハイライトは出ません。ということらしい。

以下のサイトに詳しく書いてある。

http://marina.sys.wakayama-u.ac.jp/~tokoi/?date=20080820

床井先生のところはすごく丁寧に書いてある。

無印のOpenGLならglLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR)といった設定をすれば、ハイライトが出るようになるらしいのだが、OpenGL ESには、残念ながらこの機能はない(もしかすると、サポートしている端末もあるかも知れない)。

しかし、そんなサポートされているかどうかわからない機能は危なくて使えない。
これに、変わる作戦として、テクスチャマッピングして一度描画してから、その後で同じポリゴンをテクスチャマッピングしないで描画する、といった方法がある。もちろん、単純に再描画したら上書きされてしまうので、2回目はアルファブレンドして描画する。

		float half_white[] = {1.0f, 1.0f, 1.0f, 0.5f};
		float transparent[] = {0.0f, 0.0f, 0.0f, 0.0f};

		@Override
		public void onDrawFrame(GL10 gl) {
			gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
			// ライティングをON
			gl.glEnable(GL10.GL_LIGHTING);
			// 光源を有効にして位置を設定
			gl.glEnable(GL10.GL_LIGHT0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightpos, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, white, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, white, 0);
			gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, yellow, 0);
			gl.glMatrixMode(GL10.GL_MODELVIEW);
			gl.glLoadIdentity();
			// カメラ位置を設定
			GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);
			
			gl.glEnable(GL10.GL_BLEND);
			gl.glEnable(GL10.GL_TEXTURE_2D);
			// スムースシェーディング
			gl.glShadeModel(GL10.GL_SMOOTH);

			// ソート
			Collections.sort(models, new ModelComparator());

			for ( Model m : models ){
				// 最初はマッピングして描画
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
				gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
				gl.glEnable(GL10.GL_TEXTURE_2D);
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
				m.draw(gl);
				if ( blendMode == 1 ){
					// 2回目はハイライトのみ描画したい
					gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, transparent, 0);
					gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, half_white, 0);
					gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, half_white, 0);
					gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
					gl.glDisable(GL10.GL_TEXTURE_2D);
					gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);

					m.draw(gl);
				}
				m.rotate();
			}
		}





むむ、やってみたが、テクスチャマッピングしないと透けている部分も描画してしまうので、四角に絵を貼りつけたように見えてしまう。透明部分がない画像をテクスチャマッピングする分にはこの方法でOKかも知れない。

そこで、テクスチャマッピング有効で、ブレンド方法(GL_SRC_ALPHA, GL_ONE)で2回目の描画をしたらそれっぽく表示できた。

			for ( Model m : models ){
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
				gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
				gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
				gl.glEnable(GL10.GL_TEXTURE_2D);
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
				m.draw(gl);
				if ( blendMode == 1 ){
					gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, transparent, 0);
					gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, half_white, 0);
					gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, half_white, 0);
					gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
					// gl.glDisable(GL10.GL_TEXTURE_2D);
					gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
					m.draw(gl);
				}
				m.rotate();
			}
		}




使い方間違ってる?

次回は、フォグ>>





nice!(0)  コメント(0) 

Androidアプリ開発 OpenGL アルファブレンド 合成方法 [Android OpenGL]



アルファブレンドは「わかりにくい」のではあるが、いろいろな効果を出すことができる。glBlendFuncで指定する引数の組み合わせで、ブレンド方法を変化させることができる。理解を深めるため、以下の3パターンでブレンドさせてみることにした。

			if ( blendMode == 0 ){
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
			}
			else if ( blendMode == 1 ){
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
			}
			else if ( blendMode == 2 ){
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_SRC_COLOR);
			}


blendModeはクラスのフィールドで、ロングタップすると切り替わるようにした。これで、キャプチャしたのが、以下の動画である。



最初のブレンド方法は、GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
このブレンド方法はいたって普通の合成となる。
ドロイド君の画像は、アルファ値が0か1のどちらかしかないようである(半透明な部分がない)。半透明な部分がある画像でやったらもっと面白かったかも知れない。

次のブレンド方法は、GL_SRC_ALPHA, GL_ONE
この方法だと、重なっている部分が白く発光した感じになる。

最後のブレンド方法は、GL_SRC_ALPHA, GL_SRC_COLOR
この方法だと、透けている感じになるものの、アルファ値が0の部分は透けない。発光した感じにはならない。

うーん、GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA以外の使い道ってあるのか...
GL_SRC_ALPHA, GL_ONEは、発光させたいときに使うらしいが...
やっぱり難しい。

次回は、テクスチャを貼りつけたときのハイライト>>





nice!(1)  コメント(0) 
前の10件 | - Android OpenGL ブログトップ

AREarthroidPro ARで地球を表示するアプリ

Copyright Atsushi Asai Google+朝井淳

[改訂第4版]SQLポケットリファレンス

[改訂第4版]SQLポケットリファレンス

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2017/02/18
  • メディア: 単行本(ソフトカバー)

[データベースの気持ちがわかる]SQLはじめの一歩 (WEB+DB PRESS plus)

[データベースの気持ちがわかる]SQLはじめの一歩 (WEB+DB PRESS plus)

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2015/03/03
  • メディア: 単行本(ソフトカバー)
C言語 ポインタが理解できない理由 [改訂新版] (プログラミングの教科書)

C言語 ポインタが理解できない理由 [改訂新版] (プログラミングの教科書)

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/04/08
  • メディア: 単行本(ソフトカバー)

かんたんAndroidアプリ作成入門 (プログラミングの教科書)

かんたんAndroidアプリ作成入門 (プログラミングの教科書)

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2013/04/16
  • メディア: 単行本(ソフトカバー)