Androidアプリ開発 OpenGLを使う GLSurfaceView

Androidには標準でOpenGLが入っている。OpenGLは2D/3DでのCGを作成するためのグラフィックスライブラリ。Androidに搭載されているOpenGLはモバイル端末向けに機能が限定された「OpenGL ES」といったパッケージになる。
androidでの2Dでの描画は(OpenGLを使わなくても)、Canvasを使って行うこともできる。
OpenGLを使うとGPUでのハードウェア・アクセラレーションが可能になり、高速に描画できる。また、3DCGはCanvasではできない。
androidアプリでは、アクティビティにビューを乗っけることでビューを表示することができる。ビューにはテキストとか、エディットといった種類のビューが用意されているので、アクティビティにテキストビューを追加すれば、テキストが表示される。
ビューは、入れ子にすることができ、レイアウトでどこに配置するかを決定できる。細かい話はOpenGLと離れていってしまうので、またの機会にしよう。
さて、androidアプリでOpenGLでなにか表示しようと思ったら、GLSurfaceViewの派生クラスを作ってやる必要がある。サーフェースビューは、普通のビューとは異なり、専用の描画スレッドを持っていて、高速に描画できるらしい。OpenGL版のサーフェースビューがGLSurfaceViewということになる。
じゃあ、早速作ってみるべし。

public class SampleGLSurfaceView extends GLSurfaceView {
}

クラスの定義はこんな感じでいいでしょう。名前はSampleGLSurfaceViewとしたが、ここは適当に好きな名前にしてもよい。
Eclipseのウイザードでクラスを作成すると、importが自動的に行われるが、パスまではめんどうみてくれないので、android.opengl.GLSurfaceViewをインポートする。

import android.opengl.GLSurfaceView;
public class SampleGLSurfaceView extends GLSurfaceView {
}

GLSurfaceViewのエラー赤線は消えるが、クラス名のところにエラーの赤線が出る。
「コンストラクタが必要」となるので、コンストラクタを作成する。このとき、Contextを引数に持つやつを作っておく。ContextっていうのはメインのActivityのこと。少し意味が違うかも知れないが、アプリケーションの「プロセスを特定するためのもの」、と思っておけばよい。
引数でもらったContextは、そのままスーパークラスのコンストラクタに渡してやる。そうしないと「引数が必要」といった感じのエラーになる。
Eclipseのクイックフィックス機能を使って、「コンストラクタを作成」をクリックしてやれば、自動的にこんな感じに作成してくれる。

import android.content.Context;
import android.opengl.GLSurfaceView;
public class SampleGLSurfaceView extends GLSurfaceView {
public SampleGLSurfaceView(Context context) {
super(context);
// TODO 自動生成されたコンストラクター・スタブ
}
}

Eclipse、超便利。
TODOのところにコードを書いていく。

import android.content.Context;
import android.opengl.GLSurfaceView;
public class SampleGLSurfaceView extends GLSurfaceView {
public SampleGLSurfaceView(Context context) {
super(context);
renderer = new OpenGLRenderer();
setRenderer(renderer);
}
}

コンストラクタでは、「レンダラー」を作ってセットしている。
OpenGLで何か描画しようと思ったら、レンダラーでやらないといけない。
「えー、ビューだけじゃだめぇ...」
そうなんです。なんと、めんどくさいことにOpenGLでは「ビュー」と「レンダラー」を作ってやる必要がある。レンダラーは、android.opengl.GLSurfaceView.Rendererを実装したクラスで、これでレンダリングをする。
レンダリングっていうのは3DCGでの用語で、「バーチャル空間に配置されている3次元データを2次元のスクリーン上に描画する」っていうことかな。
GLSurfaceViewの方は、androidのSurfaceViewのOpenGL版、と捉えればよいと思う。
Rendererの方は、OpenGLライブラリ関数のラッパークラス、と捉えればOKでしょう。
なので、OpenGLでの描画のメインはRendererに作成していく。GLSurfaceViewには、ビューが作成されたときや、大きさが変更されたときなど、画面上のウイジットやコントロールといった感じの機能を受け持つことになる。
前置きはこれくらいにして、Rendererを作成しよう。Rendererは、別のソースファイルに作成してもいいが、何かとViewと連携することも多いと思うので、クラス内のクラスで作成することにした。

public class SampleGLSurfaceView extends GLSurfaceView {
// レンダラークラス
class OpenGLRenderer implements Renderer {
}
private OpenGLRenderer renderer;
// サーフェースビューのコンストラクタ
public SampleGLSurfaceView(Context context) {
super(context);
renderer = new OpenGLRenderer();
setRenderer(renderer);
}
}

クラスOpenGLRendererはRendererインターフェースを実装したクラス。継承ではなく、インターフェースの実装でレンダラーを作成する。OpenGLRendererは私が考えた名前。ここは好きな名前に変更してよい。
レンダラーのインスタンスを作成したら、setRendererでレンダラーとビューをつなげる。直接、setRenderereでnewしたインスタンスを設定してもよいが、後から使用するかも知れないので、クラスのフィールドrendererで記憶しておく。
Rendererはインターフェースなので、実装すべきメソッドがいくつか存在する。
OpenGLRendereにエラーの赤線が表示されており、「実装すべきメソッドがないよ」と教えてくれている。面倒なのでEclipseでクイックフィックスしてしまおう。
「実装されていないメソッドの追加」をクリックすると、自動的に3つのメソッドが作成される。

// レンダラークラス
class OpenGLRenderer implements Renderer {
@Override
public void onDrawFrame(GL10 gl) {
// TODO 自動生成されたメソッド・スタブ
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO 自動生成されたメソッド・スタブ
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO 自動生成されたメソッド・スタブ
}
}

作成されたメソッドは全部で3つ。そのひとつが、onDrawFrameである。ここで描画を行う。
その他に、onSurfaceCreated、onSurfaceChangedがあるが、こいつらはとりあえず使わない。
Frameは「フレーム」なので、映画や、ビデオのアニメーションのようにフレーム毎に描画が行われることを意味している。つまり、onDrawFrameは「連続して呼び出し続けられる」。
これに比べ、onSurfaceCreated、onSurfaceChangedの呼び出される頻度は少ない。
描画する際に、方向を少しづつ変えていけば、表示される像が移動してみえることになり、まさにアニメーションのように物が動いてみえる。
さっそくやってみたいところだが、まず、ファーストステップとしては、「単に黒く塗り潰すだけ」にする。「急がば回れ」である。

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLSurfaceView;
public class SampleGLSurfaceView extends GLSurfaceView {
// レンダラークラス
class OpenGLRenderer implements Renderer {
@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
}
private OpenGLRenderer renderer;
// サーフェースビューのコンストラクタ
public SampleGLSurfaceView(Context context) {
super(context);
renderer = new OpenGLRenderer();
setRenderer(renderer);
}
}

これで、レンダラーが完成。
onSurfaceCreatedにもコードを書いてしまったが、まぁ、御愛嬌っていうことで。へへ。
後は、アクティビティで「表示すべきビュー」をSampleGLSurfaceViewにすれば、そのように表示される。
アクティビティは以下のように作成してみた。

public class TestOpenGLActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
// OpenGL用のビューを作る
SampleGLSurfaceView view = new SampleGLSurfaceView(this);
// アクティビティにビューを設定
setContentView(view);
}
}

これで完成。真黒なアクティビティが表示できたら「成功」。

関連記事
AREarthWebGL AREarthroidの地球表示部分をWebGL化

投稿者プロフィール

asai
asai
システムエンジニア
喋れる言語:日本語、C言語、SQL、JavaScript