OpenGL ES(OpenGL for Embedded Systems)とは主に組込みシステム上で動作する2D/3DグラフィックスAPIです。
クロスプラットフォーム、ロイヤリティフリーなOpenGLのサブセットです。
iOS,Andoroid,PS3,Nintendo 3DS等で採用されています。
OpenGL ES - The Standard for Embedded Accelerated 3D Graphics= http://www.khronos.org/opengles/
OpenGL ESには大きくわけて2つのバージョンがあります。
OpenGL ES 1.x
固定機能パイプラインにて描画されます。
[固定機能パイプライン]
頂点座標 → モデルビュー変換 → 射影変換 → ビューポート変換 → 描画
OpenGL ES 2.x
「固定機能パイプライン」の変わりにプログラマブルシェーダを使用します。プログラマブルシェーダとは頂点情報の変換やピクセル描画方法をプログラマが独自に設定でき、高度な特殊処理が利用できます。
頂点座標 → プログラマブルシェーダ → 描画
今回はOpenGL ES(1.x)(固定パイプライン)について説明をします。
X軸を横、Y軸を上、Z軸を奥行きとします。 図1:OpenGL ESの座標
OpenGL ESはステートマシンです。ステータスを変えて処理、ステータスを変えて処理…という具合に各命令は最後に指定された行列に対して実行されます。
OpenGL ESでは2つの行列を使用します。
モデルを見る方向や視角度等を定める「射影変換行列」、モデルの移動、回転、拡大/縮小を定める「モデル変換行列」があります。
行列は4x4の行列です。
点(X,Y,Z,W)の座標の集合です。
三次元の点は通常 (X, Y, Z, 1) として内部的に表現されています。
実際は三次元座標は(Y/W, Y/W, Z/W) となります。
wがゼロの場合は3次元ベクトルとなります。
頂点座標
モデルビュー変換とはモデルビュー変換行列から平行移動、回転、拡大縮小などを設定します。
移動頂点座標とモデル変換行列から自分で計算できますが、通常は便利な命令があるのでそちらを利用すると良いでしょう。
移動:glTranslatef()
回転:glRotatef()
拡大/縮小:glScalef ()
図2:移動 図3:回転 図4:拡大/縮小
射影変換とは射影変換行列からモデルをどのように表示するかを指定します。
視線の方向、角度、表示する画面の縦横比を設定します。
シーンを写す画面の形状(比率)を指定します。
図5:射影変換とビューポート
w,h:ビューポート(画面比率) near:近クリップ面 far:遠クリップ面 画面比率はw/h、奥行きはnear〜farの範囲が表示されます。
OpenGL ESはOpenGLからいくつかの機能が削除されたサブセットです。
以下の機能(抜粋)が削除されていますので削除された命令が記述されたOpenGLのプログラムを動作させる場合には、代替えできる命令で置き換える必要があります。
AndroidではOpenGL ESはJavaとNDK(Android Native Development Toolkit)で動作させることができます。 ここではJavaで動作させる場合を解説します。
AndroidアプリケーションでOpenGLを使用する際の基本的な構成について以下の図のようになります。
図6:AndroidでのOpenGL ES
AndroidのOpenGL ESで使用する2つのクラスについて解説します。
GLSurfaceView
GLSurfaceViewはSurfaceViewのサブクラスでOpenGL ESの処理を行う機能が実装されています。
・UIスレッドとは別に専用のスレッドでレンダリングします。
・連続したレンダリング、又はオンデマンドのレンダリングに対応しています。
・OpenGLの呼び出しのトレースとエラーチェックができます。
メソッド
onTouchEvent()
通常のViewと同じようにタッチスクリーンの操作のハンドリングを記述します。
(例:タッチ操作でモデルの移動、回転、拡大/縮小等)
GLSurfaceView.Renderer
フレームのレンダリングを行います。
プログラマはこのクラスを継承したクラスを作成し、GLSurfaceViewに登録(setRenderer())する必要があります。
メソッド
onSurfaceCreated()
サーフェースが作成されるときに呼び出されます。
端末がスリープ状態からレジュームされた時などにコンテキストが失われる場合があります。
コンテキスとが失われるとコンテキストに関連付けられたリソースが削除されますのでレンダリングを続ける為にはリソースを再作成する必要があります。
通常はこのメソッド内で行うと良いでしょう。
onSurfaceChanged()
画面サイズが変更されたときに呼び出されます。
通常はここでビューポートの設定をすると良いでしょう。
onDrawFrame()
フレームを描画する時に呼び出されます。
このメソッド内で描画処理を行うようにプログラムを記述する必要があります。
アクティビティのonResume(), onPause() でGLSurfaceViewのonResume(),onPause()を呼び出します。
これで Android のアクティビティのライフサイクルに合わせた動作となります。
アクティビティが一時停止(onPause())するとOpenGLのレンダリングは一時停止し、アクティビティが回復(onResume())するとOpenGLのレンダリング処理は再開します。
GLSurfaceView は OpenGL ES をデバッグするための便利な機能があります。
GLSurfaceView.setDebugFlags() メソッドで OpenGL ES 対する呼び出しのログ出力と エラーチェックを有効な状態に設定します。
GLSurfaceView のコンストラクタで setRenderer() を呼び出す前にこのメソッドを呼び出す必要があります。
setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
GLSurfaceView.DEBUG_LOG_GL_CALLS:
OpenGL ESの命令を実行した時にデバッガ(LogCat)に出力します。
GLSurfaceView.DEBUG_CHECK_GL_ERROR:
OpenGL ESの命令を実行した時にエラーがあった場合、デバッガ(LogCat)に出力します。
図7:出力結果
現在Androidはさまざまな端末で動作しています。ある端末はOpenGL ES 1.1とOpenGL ES 2.0の両方に対応していますが、別の端末はOpenGL ES 1.1のみ対応しているかもしれません。
また同じバージョンであっても搭載しているグラフィックチップによって機能が制限されている場合もあります。
プログラマはターゲットとする端末で動作させる為に動作する端末のOpenGL ESの機能を把握し、プログラミングする必要があります。
どの機能がサポートされているかはglGetString() に以下のパラメータで呼び出すことで確認できます。
GL_EXTENSIONS:
GL_RENDERER:グラフィックチップ名
GL_VENDOR:グラフィックチップ(ドライバ)のベンダー名
GL_VERSION :OpenGLのバージョン
OpenGL ES(1.0)で制限される機能一覧
立方体を表示し、タッチ操作による回転を行うプログラムです。
アクティビティ
001:package com.beatcraft.opengl1; 002: 003:import com.beatcraft.opengl1.GLView; 004: 005:import android.app.Activity; 006:import android.os.Bundle; 007: 008:public class OpenGL1 extends Activity { 009: GLView mGLView; 010: 011: @Override 012: public void onCreate(Bundle savedInstanceState) { 013: super.onCreate(savedInstanceState); 014: mGLView = new GLView(this); 015: setContentView(mGLView); 016: } 017: @Override 018: protected void onPause() { 019: super.onPause(); 020: mGLView.onPause(); 021: } 022: @Override 023: protected void onResume() { 024: super.onResume(); 025: mGLView.onResume(); 026: } 027:}
9行目: GLSurfaceView の派生クラスのGLViewを変数としています。
14行目: GLViewのインスタンスを作成
15行目: GLViewをアクティビティに張り付けます。
20行目:アクティビティが一時停止(onPause())になるのでGLViewを一時停止させます。
25行目: アクティビティが再開(onResume())するのでGLViewを再開させます。
ビュー(GLView)
001:package com.beatcraft.opengl1; 002: 003:import android.content.Context; 004:import android.opengl.GLSurfaceView; 005:import android.util.Log; 006:import android.view.MotionEvent; 007: 008:public class GLView extends GLSurfaceView { 009: private GLRenderer mGLRenderer; 010: private final float TOUCH_SCALE_FACTOR = 180.0f / 320; 011: private float mPreviousX; 012: private float mPreviousY; 013: 014: public GLView(Context context) { 015: super(context); 016: mGLRenderer = new GLRenderer(); 017: setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS); 018: setRenderer(mGLRenderer); 019: } 020: 021: @Override 022: public boolean onTouchEvent(MotionEvent event) { 023: float x = event.getX(); 024: float y = event.getY(); 025: switch (event.getAction()) { 026: case MotionEvent.ACTION_MOVE: 027: float dx = x - mPreviousX; 028: float dy = y - mPreviousY; 029: mGLRenderer.mRotate_x += dx * TOUCH_SCALE_FACTOR; 030: mGLRenderer.mRotate_y += dy * TOUCH_SCALE_FACTOR; 031: } 032: mPreviousX = x; 033: mPreviousY = y; 034: 035: return true; 036: } 037:}
8行目: GLSurfaceViewを継承したGLViewを定義しています。
9行目: Rendererのインターフェースを実装したクラスGLRendererを変数としています。
16行目: GLRendererのインスタンスを作成
17行目: OpenGL ESのデバッグモード設定
18行目: GLRendererのインスタンスをGLSurfaceViewに登録しています。
21行目〜:画面をタッチした際の処理です。
タッチしながら移動したとき(MotionEvent.ACTION_MOVE)の移動量をX、Y軸で回転角に置き換え、GLRendererの変数にセットしています。
レンダラ(GLRenderer)
001:package com.beatcraft.opengl1; 002: 003:import java.nio.ByteBuffer; 004:import java.nio.ByteOrder; 005:import java.nio.FloatBuffer; 006: 007:import javax.microedition.khronos.egl.EGLConfig; 008:import javax.microedition.khronos.opengles.GL10; 009: 010:import android.opengl.GLSurfaceView.Renderer; 011: 012:public class GLRenderer implements Renderer { 013: … 124: @Override 125: public void onSurfaceCreated(GL10 gl, EGLConfig arg1) { 126: // 座標の初期化 127: gl.glLoadIdentity(); 128: // デプスバッファのテスト機能を有効にする 129: gl.glEnable(GL10.GL_DEPTH_TEST); 130: // 陰面消去の動作を設定 131: gl.glDepthFunc(GL10.GL_LEQUAL); 132: // ライトを有効にする 133: gl.glEnable(GL10.GL_LIGHTING); 134: // どの光源を使用するか指定 135: gl.glEnable(GL10.GL_LIGHT0); 136: 137: // 頂点配列、法線配列をBufferに設定 138: setBuffer(); 139: } 140: 141: @Override 142: public void onSurfaceChanged(GL10 gl, int width, int height) { 143: // 画面の縦横比率 144: float ratio = (float) width / height; 145: // ビューポートの設定 146: gl.glViewport(0, 0, width, height); 147: // 射影行列の指定 148: gl.glMatrixMode(GL10.GL_PROJECTION); 149: gl.glLoadIdentity(); 150: // 画角の設定(left,right,bottom,top,near,far) 151: gl.glFrustumf(-ratio, ratio, -1, 1, 1, 1000); 152: }
125行目: サーフェースが作成されるときに呼び出されます。
142行目:サーフェースのサイズが変更されたときにonSurfaceChanged() が呼び出されます。
146行目:ビューポートの設定。幅と高さから適正な比率に設定します(図5参照)
148行目: 射影行列の指定。これ以降の命令は射影変換行列に対して行われます。
151行目:視体積の設定。 手前の小さな四角形、奥の大きな四角形に挟まれた空間を設定します(図5参照)
レンダラ(GLRenderer) 続き
154: @Override 155: public void onDrawFrame(GL10 gl) { 156: // 表示画面とデプスバッファのクリア 157: gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); 158: // モデルビュー行列の指定 159: gl.glMatrixMode(GL10.GL_MODELVIEW); 160: // 座標の初期化 161: gl.glLoadIdentity(); 162: // 移動(Z軸:奥に移動させる) 163: gl.glTranslatef(0, 0, -5f); 164: // 回転 165: gl.glRotatef(mRotate_x, 0, 1, 0); 166: gl.glRotatef(mRotate_y, 1, 0, 0); 167: // 頂点配列を有効にする 168: gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); 169: // 法線配列を有効にする 170: gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); 171: // 頂点配列のセット 172: gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer); 173: // 法線配列のセット 174: gl.glNormalPointer(GL10.GL_FLOAT, 0, mNormalBuffer); 175: // 描画(GL_TRIANGLES:三角形) 176: gl.glDrawArrays(GL10.GL_TRIANGLES, 0, mVertexBuffer.remaining() / 3); 177: } … 193:}
155行目:描画の度に実行される
159行目:モデルビュー行列の指定。これ以降の命令はモデルビュー行列に対して行われます。
165-166行目:タッチ操作から計算した回転角を設定(図3参照)
176行目:立方体の頂点配列から三角形(GL_TRIANGLES,)を描画
スクリーンショット
画面をタッチしてスライドさせると立方体が回転します。
5.3Dモデルデータ(.obj)の表示
.objファイルについて Wavefront Technologies社のAdvanced Visualizerというアプリケーションの出力フォーマットです。多くの3Dアプリケーションでサポートされています。
拡張子は.obj、ファイル形式はテキスト形式、格納されるデータは、頂点座標、法線座標、テクスチャ座標です。
・コメント
#で始まる行はコメント行となります。
・頂点座標
vで始まり、頂点の座標が並びます
・テクスチャ座標
vtで始まり、テクスチャ頂点の座標が並びます
・法線座標
vn で始まり、法線ベクトルの座標が並びます
・face
faceは頂点に対応する頂点座標、テクスチャ座標、法線座標のインデックスを指しています。
fで始まり、以下の形で記述されます。
f 数値/数値/数値/ ... -> 頂点座標/テクスチャ座標/法線座標…
f 数値//数値 ...-> 頂点座標//法線座標...
f 数値 ... -> 頂点座標...
インデックスは1から始まる値で、ファイルの先頭から連番になります。
# # cube.obj # v 0.0 0.0 0.0 v 0.0 0.0 1.0 v 0.0 1.0 0.0 v 0.0 1.0 1.0 v 1.0 0.0 0.0 v 1.0 0.0 1.0 v 1.0 1.0 0.0 v 1.0 1.0 1.0 f 1//2 7//2 5//2 1//2 3//2 7//2 f 1//6 4//6 3//6 vn 0.0 0.0 1.0 vn 0.0 0.0 -1.0 vn 0.0 1.0 0.0 vn 0.0 -1.0 0.0 vn 1.0 0.0 0.0 vn -1.0 0.0 0.0 f 1//6 2//6 4//6 f 3//3 8//3 7//3 f 3//3 4//3 8//3 f 5//5 7//5 8//5 f 5//5 8//5 6//5 f 1//4 5//4 6//4 f 1//4 6//4 2//4 f 2//1 6//1 8//1 f 2//1 8//1 4//1
3Dモデルデータ(.obj)を表示した例