OpenGL ESの初期化と描画

Androidでは、OpenGLを携帯機器向けに最適化したOpenGL ESを使うことが出来ます。

OpenGL ESには、いくつかバージョンがありますが、現行のAndroid端末で利用できるのは1.0/1.1系列と2系列です。今回は、まずOpenGL ES 1.0を初期化し、Android上でOpenGL ESによる描画を行える状態になるまでのコードを作ってみることにしましょう。

Androidでは、OpenGL ESによる描画をSurfaceView上に行うことになっています。SurfaceViewに対して各種の設定を行い、OpenGL ESのAndroid版システムと結びつける形になるのですが、システムとのやりとりや初期化を簡単にまとめたサブクラスとしてGLSurfaceViewクラスが用意されているので、今回はこれを使ってみることにしましょう。

GLSurfaceViewクラスを使う場合は、OpenGL ESの初期化と描画関数の設定はごく単純です。GLSurfaceViewのインスタンスを作成して適当なGUIコンポーネント上に配置したら、後は描画やイベント処理を行うレンダラーをsetRenderer()で設定すれば、OpenGL ESを使うことが出来ます。
その他、アプリのポーズ/レジューム時にはGLSurfaceViewに通知する必要がありますが、これもメソッドを一つ呼び出すだけです。

レンダラーは、GLSurfaceView.Rendererインターフェースを実装(いくつかのabstructメソッドをオーバーライド)したオブジェクトです。今回はAndroidアプリケーションのアクティビティにGLSurfaceViewオブジェクトの配置とGLSurfaceView.Rendererインターフェースの実装をまとめてしまうことにします。

GLSurfaceView.Rendererインターフェースでオーバーライドすべきメソッドは、以下のとおりです。

  • onSurfaceCreated()
    GLSurfaceViewが作られたときに呼ばれます。
  • onSurfaceChanged()
    サーフェスの大きさが変わったときに呼ばれます。
  • onDrawFrame()
    OpenGL ESのシステムが作成したスレッドから断続的に呼ばれます。ここで渡されるオブジェクトに対して、OpenGL ESの機能を利用した描画処理を行います。

GLSurfaceViewを作成すると、自動的にGLSurfaceView用のスレッドが作られ、そのスレッドがonDrawFrame()を呼び出す形で描画処理が行われます。OpenGL ESを利用する描画はonDrawFrame()にまとめて記述する形になるわけですが、その際は(AndroidアプリのUIを駆動する)メインスレッドとは別のスレッドで実行される点に注意が必要です。

onDrawFrame()内からUIにアクセスしたり、メインスレッドやゲーム用に起動するメインループスレッドで管理している変数に不用意にアクセスすると、スレッド周りのやっかいな問題に直面する可能性があります。

とりあえず、今回はonSurfaceChanged()で「サーフェス全体をOpenGL ESの描画領域とする」処理を記述し、描画関数onDrawFrame()では、適当な色で描画領域全体を塗りつぶす(クリアする)ことにしましょう。

onSurfaceChanged()では、サーフェスの大きさが引数として渡されるので、それをそのままglViewport()に渡して描画領域の大きさとします。今回は、アクティビティの画面全体にGLSurfaceViewを配置してその全体(つまりAndroidアプリ全面)を描画領域にしてみました。
Android端末の画面サイズは多様ですから、実際に使うときにはGLSurfaceViewを全画面ではなく画面の一部だけを使うように配置したり、GLSurfaceViewの全体ではなく一部を描画領域に設定する場合もあるかもしれませんね。

onDrawFrame()では、glClearColor()で色(RGBA)の設定を行った後にglClear()でフレームバッファ(GL_COLOR_BUFFER_BIT)をクリアします。また、onDrawFrame()が自動的に呼び出される様子を確認するために、glClearColor()に設定する色を毎回変更するようにしてみました。

コード全体は、以下のとおりです。各種の宣言や色の変更に絡むコードを除くとほんの数個のメソッド呼び出しで、OpenGL ESを使えるようになるわけですね。

今回は、AndroidアプリでOpenGL ES 1.0(javax.microedition.khronos.opengles.GL10)を使うよう初期化しましたが、OpenGL ES 2.0でも基本的な流れは同じです。

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.os.Bundle;
import android.app.Activity;

public class MainActivity extends Activity implements Renderer {

	private GLSurfaceView mGLView;
	private float mBGD, mBGDD;

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        mBGD = 0;
        mBGDD = 0;

        mGLView = new GLSurfaceView(this);

        mGLView.setRenderer(this);

        setContentView(mGLView);

    }

    @Override
    protected void onPause() {

    	super.onPause();
    	mGLView.onPause();

    }

    @Override
    protected void onResume() {

    	super.onResume();
    	mGLView.onResume();

    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
    	gl.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {

    	gl.glClearColor(mBGD, mBGD * mBGD, 1.0f - mBGD, 1.0f);

    	mBGD += mBGDD;

    	if (mBGD >= 1.0f) {
    		mBGDD = -0.01f;
    	}

    	if (mBGD <= 0) {
    		mBGDD = 0.01f;
    	}

    	gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

    }

}

創作プログラミングの街