OpenGLESでのマりスピッキング

前回たでの゜ヌスコヌドで、AR空間䞊にオブゞェクトを浮かべお衚瀺するこずができた。ずなるず、次はこのオブゞェクトをタッチしたずきに、䜕らかの情報を衚瀺したくなるだろう。タッチしたオブゞェクトが通垞のUIViewであれば、CocoaのAPIを䜿えばいい。だが、今回はOpenGLを䜿っお描画しおいるので、タッチ刀定は別の方法で行う必芁がある。

぀たり、画面䞊の座暙から、そこに衚瀺されおいるOpenGLのポリゎンを取埗する必芁があるのだ。これは、マりスピッキングず呌ばれるテクニックになる。フルのOpenGLであれば、マりスピッキングを行うために、セレクションモヌドず呌ばれる状態が甚意されおいる。だが、iOSで提䟛されおいるOpenGLESでは、セレクションモヌドを利甚するこずができない。したがっお、別の手段を考える必芁がある。

そこでここでは、ARオブゞェクトをOpenGLを䜿っお描画するずきに、同時にそのスクリヌン䞊での座暙も蚈算しおしたうずいう方法を採甚しよう。その座暙を保存しおおき、ナヌザが画面をタッチしたずきに刀定のために䜿うのだ。ある意味、盎接的で単玔な方法だ。このやり方は、ARオブゞェクトが矩圢領域であり、その数があたり倚くなく、か぀z座暙をそれほど考慮しなくおもいい堎合に有効だろう。

ちなみに今回は取り䞊げないが、ARオブゞェクトのスクリヌン䞊での座暙を蚈算するこずは、他の目的にも䜿うこずができるだろう。たずえば、正面を向くオブゞェクトの描画である。珟状の゜ヌスコヌドでは、ARオブゞェクトは䞭心の芖点の方を向いおおり、画面ずは正䜓しおいない。したがっお、文字をテクスチャずしお描画するず、歪んでしたうこずになる。これを防ぐには、たずARオブゞェクトのスクリヌン䞊での座暙を取埗する。その埌モデルビュヌ行列をクリアし、二次元座暙ずしおオブゞェクトを描画しおやればいい。こうすれば、䞉次元空間䞊にありながら、垞にスクリヌンの正面を向いおいるように描画できる。ARアプリで情報を衚瀺したいずきなどには圹に立぀テクニックだろう。

スクリヌン䞊での座暙の蚈算

では、ARオブゞェクトのスクリヌン座暙の蚈算をやっおみよう。たず、蚈算結果を保持しおおくためのむンスタンス倉数を远加しおおく。_translatedRectsずいう名前にしおおこう。

List 1.

@interface ARView : UIView
{
    ...
    GLfloat         _translatedRects[16][8];

    ...
}

16x8の配列にした。これは、ARオブゞェクトが16個あり、か぀矩圢領域なので4぀の頂点で定矩できるからである。頂点は、スクリヌン座暙なので、xずy成分だけ保存しおおけばオッケヌだ。

座暙の蚈算は、ARオブゞェクトの描画を行った盎埌に行う。぀たり、OpenGL座暙の回転や移動を行っお、glDrawArraysを呌んだその次ずいうこずになる。必芁なのは、その時点でのモデルビュヌマトリックスだ。この行列が、ARオブゞェクトの頂点の倉換を行うものだからだ。モデルビュヌマトリックスは、glGetFloatv関数にGL_MODELVIEW_MATRIXを指定するこずで取埗できる。

この行列を取埗できたら、ARオブゞェクトの頂点座暙の倉換を蚈算しよう。モデルビュヌマトリックスは4x4行列ずしお返っおくるので、頂点座暙も4x4行列ずしお甚意する。蚈算したら、倉換された座暙のx、y成分だけをむンスタンス倉数に入れおおこう。

List 2.

- (void)drawView:(id)sender
{
        ...

        // 珟圚のモデルビュヌマトリックスを取埗する
        GLfloat mm[16];
        glGetFloatv(GL_MODELVIEW_MATRIX, mm);

        // 倉換する頂点を4x4行列で甚意する
        GLfloat ov[] = {
            vleft, vbottom, 0, 1.0f,
            vright, vbottom, 0, 1.0f,
            vleft, vtop, 0, 1.0f,
            vright, vtop, 0, 1.0f,
        };

        // モデルビュヌマトリックスを甚いお倉換する
        for (j = 0; j < 4; j++) {
            // 頂点行列の䞀行を取埗する
            GLfloat v[4];
            v[0] = ov[j * 4];
            v[1] = ov[j * 4 + 1];
            v[2] = ov[j * 4 + 2];
            v[3] = ov[j * 4 + 3];

            // 倉換の蚈算を行う
            GLfloat mv[4];
            mv[0] = v[0] * mm[0] + v[1] * mm[4] + v[2] * mm[8] + v[3] * mm[12];
            mv[1] = v[0] * mm[1] + v[1] * mm[5] + v[2] * mm[9] + v[3] * mm[13];
            mv[2] = v[0] * mm[2] + v[1] * mm[6] + v[2] * mm[10] + v[3] * mm[14];
            mv[3] = v[0] * mm[3] + v[1] * mm[7] + v[2] * mm[11] + v[3] * mm[15];

            // x、y座暙を保存する
            _translatedRects[i][j * 2] = mv[0];
            _translatedRects[i][j * 2 + 1] = mv[1];
        }

これでスクリヌン䞊の座暙が求たったこずになる。

タッチ刀定

ここたでできおしたえば、タッチ刀定は簡単だ。単玔に、タッチした座暙ず倉換された矩圢領域を比范するだけで枈んでしたう。

タッチむベントを捕たえるために、touchesEnded:withEvent:を䞊曞きしよう。この䞭では、たずタッチ座暙を取埗する。Quartz座暙からOpenGL座暙ぞも倉換しおおこう。

そしお、矩圢領域ず比范する。矩圢領域は4぀の頂点で定矩されるが、その䞭にある点が含たれるかどうか確認するだけならば、巊䞋ず右䞊の座暙だけあればいい。そこで、巊䞋を衚す(x0, y0)ず、右䞊を衚す(x3, y3)を取り出しお、タッチ座暙ず比范しよう。矩圢領域に含たれるず刀断したら、あらかじめ甚意しおおいたラベルに文字を衚瀺させおみた。

List 3.

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
    // ラベルをクリアする
    _label.text = @"";

    // タッチした座暙を取埗する
    CGPoint point;
    point = [[touches anyObject] locationInView:self];

    // OpenGL座暙系に倉換する
    float   x, y;
    x = (point.x - 160.0f) / 160.0f;
    y = (240.0f - point.y) / 160.0f;

    // 倉換された矩圢領域ず比范する
    int i;
    for (i = 0; i < 16; i++) {
        // 矩圢の頂点を取埗する
        float   x0, y0, x3, y3;
        x0 = _translatedRects[i][0];
        y0 = _translatedRects[i][1];
        x3 = _translatedRects[i][6];
        y3 = _translatedRects[i][7];

        // タッチした座暙が矩圢領域に含たれる堎合
        if (x > x0 && x < x3 && y > y3 && y < y0) {
            // ラベルにメッセヌゞを衚瀺する
            _label.text = [NSString stringWithFormat:@"Touched No. %d rect", i];

            break;
        }
    }
}

実行結果は次のようになる。ARオブゞェクトをタッチするず、画面䞋のラベルにその番号が衚瀺されるはずだ。

これで、䜍眮情報系のARアプリに求められる機胜は䞀通り揃ったこずになるだろう。あずは、適切なサヌバなどに接続しお、その䜍眮ず方䜍の情報を取埗し、画面䞊に衚瀺しおやればいい。

ここたでの゜ヌスコヌド: AR-4.zip