AR空間を構築する
「現実」であるカメラ画像の上に重ねるための、AR空間を構築することを考えよう。AR空間は、自分を中心とした球体の空間として捉えることができるだろう。この球体中に種々のオブジェクトを配置する。そしてiPhoneのカメラ機能を使って、その一部を切り取って表示するわけだ。
このようなことから、AR空間を実現するための技術としては、3D空間を取り扱うことが求められる。その実装としては、OpenGLを使うのが適当だろう。そこで、まずはサンプルアプリにOpenGL環境を組み込むとしよう。サンプルのプロジェクトには、ARViewというUIViewのサブクラスがある。このビューでOpenGLの表示を行うものとする。
UIViewでOpenGLの表示を行うには、まずビューのレイヤーとしてCAEAGLLayerを利用する。そして、OpenGLのコンテキストを表すEAGLConextを作成してやればいい。その後は、OpenGLの関数を使って設定を行っていく。OpenGLをiOSで用いることは、本連載の第18回から始まる「タワーディフェンスゲームの作り方」で詳しく解説しているので、そちらも参照してほしい。
ここでは、OpenGL環境のセットアップを行っているソースコードを紹介しよう。初期化メソッドから呼び出すことになるメソッドである。
List 1.
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
- (void)_init
{
// レイヤーの設定をする
CAEAGLLayer* eaglLayer;
eaglLayer = (CAEAGLLayer*)self.layer;
eaglLayer.opaque = NO;
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
nil];
// OpenGLコンテキストを作成する
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
[EAGLContext setCurrentContext:_context];
// フレームバッファを作成する
glGenFramebuffersOES(1, &_defaultFramebuffer);
glGenRenderbuffersOES(1, &_colorRenderbuffer);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, _defaultFramebuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, _colorRenderbuffer);
[_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
glFramebufferRenderbufferOES(
GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, _colorRenderbuffer);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &_backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &_backingHeight);
}
まずlayerClassメソッドを上書きし、利用するレイヤークラスを指定している。そして_initメソッドの中では、レイヤーの設定およびOpenGLコンテキストの作成を行っている。これでUIViewとOpenGLが関連付けられた。後は、フレームバッファの作成などのOpenGLの設定である。このメソッドは、initWithFrame:またはinitWithCoder:といったUIViewの初期化メソッドから呼び出されることになる。
センサと表示を連動させる
次に、ARViewと各種センサを連動させることを考えよう。加速度、電子コンパス、ジャイロといったセンサの使い方は、前回解説した。
前回のソースコードでは、センサから取得した計測値はARAppDelegateが受け取ることになっていた。そこで、この値をARViewに設定できるようにしてやろう。そのために、ARViewに次のようなプロパティを追加する。
List 2.
@interface ARView : UIView
{
// デバイスの位置情報
CMAcceleration _gravity;
float _heading;
float _roll;
float _pitch;
float _yaw;
...
}
// プロパティ
@property (nonatomic) CMAcceleration gravity;
@property (nonatomic) float heading;
@property (nonatomic) float roll;
@property (nonatomic) float pitch;
@property (nonatomic) float yaw;
...
加速度の値を保持するためのCMAcceleration型のgravity、電子コンパスの値を保持するためのheading、ジャイロセンサーの値を保持するためのroll、pitch、yawを、それぞれインスタンス変数とプロパティとして追加した。ARAppDelegateは、センサの計測値を受け取ったら、これらのプロパティの値に設定してやればいい。
このようにしてARViewにセンサの値が渡ってきたら、それをOpenGLの表示と連動させる。これは、OpenGLの座標を回転させることに相当する。得られたセンサの値のうちどれを使うかは、アプリケーションの目的によって変わるだろう。今回は、電子コンパスと加速度を使うことにしてみた。電子コンパスで得られた方位の角度でy軸の回転を行い、加速で得られる値でx軸の回転を行う。電子コンパスの値を使うので、実際の方位との連携がとりやすい。地図上のデータを、AR空間にマッピングしたいときに適切だろう。
OpenGLの描画ルーチンは、ディスプレイリンクを使うことで呼び出すことにしよう。drawView:というメソッドを作り、これをディスプレイリンクから呼び出すように設定する。drawView:の中では、まず始めに、センサの値による回転を行うことになる。
List 3.
- (void)startAnimation
{
// ディスプレイリンクを有効化する
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView:)];
[_displayLink setFrameInterval:1];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)drawView:(id)sender
{
...
// 加速度による回転を行う
glRotatef(_gravity.z * -90.0f, 1.0f, 0, 0);
// 電子コンパスによる回転を行う
glRotatef(_heading, 0, 1.0f, 0);
...
startAnimationメソッドは、ディスプレイリンクの有効化を行うものだ。このメソッドを呼ぶことで、drawView:が定期的に呼び出されることになる。
drawView:メソッドから、OpenGLの描画に関わるすべてを行う。まず行うのは、センサと連動した視点の回転だ。加速度の値はx、y、z軸ごとに取得することができるが、このうちz軸成分のみをつかって、OpenGLのx軸回転を行う。つまり、ユーザが下を見ているか上を見ているかという、上下の動きを反映させている。次に、電子コンパスの値を使って、OpenGLのy軸回転を行う。これは、ユーザが北を見ているか東を見ているかという、その場での左右の回転を反映させることになる。
オブジェクトを表示する
視点の回転を行ったら、後は種々のオブジェクトを表示すればいい。ここは通常のOpenGLのソースコードになるだろう。AR空間上にポリゴンを配置していけばいい。このサンプルでは、水平線上にぐるりと16個の正方形を表示させてみた。
List 4.
- (void)drawView:(id)sender
{
...
// ポリゴンを作成する
int i;
for (i = 0; i < 16; i++) {
// 現在の行列を保存する
glPushMatrix();
// オブジェクトの位置を決定する
glRotatef(360.0f / 16 * i, 0, 1.0f, 0);
glTranslatef(0, 0, -2.0f);
// ポリゴンの頂点を設定する
GLfloat vleft, vright, vtop, vbottom;
vleft = -0.2f;
vright = 0.2f;
vtop = -0.2f;
vbottom = 0.2f;
GLfloat squareVertices[] = {
vleft, vbottom,
vright, vbottom,
vleft, vtop,
vright, vtop,
};
glVertexPointer(2, GL_FLOAT, 0, squareVertices);
// ポリゴンの色を設定する
const GLubyte squareColors[] = {
16 * i, 255 - (16 * i), 255, 255,
16 * i, 255 - (16 * i), 255, 255,
16 * i, 255 - (16 * i), 255, 255,
16 * i, 255 - (16 * i), 255, 255,
};
glColorPointer(4, GL_UNSIGNED_BYTE, 0, squareColors);
// ポリゴンを描画する
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// 以前の行列に戻す
glPopMatrix();
}
...
これでオブジェクトが配置されることになる。
サンプルアプリを起動すると、カメラが表示される。デバイスを手にもって地面と垂直に立てると、正方形が横に並んでいるはずだ。そのままくるくると回転すると、カメラ画像の移動に合わせて正方形も移動する。上下を見れば、正方形は画面外に消えていくだろう。これが、基本的なAR空間となっているのだ。
ここまでのソースコード: AR-3.zip