2011/08/21

RenderScriptでカルーセルエフェクト

前回のエントリーでRenderScriptを使ってページめくりの効果を実装してみました。となれば、次はもちろんYouTubeアプリのカルーセル表示ですよね。

既に、3D空間にテクスチャーを貼りつけた矩形を表示する方法はわかりました。なので、これも比較的簡単に実装できそうですね。

■カルーセル表示の実装
今回は、円周上に10面の矩形を並べてグルグル回して遊んでみましょう。前回のエントリーと基本的なところは変わらないので、RenderScriptの中身だけ見ていきます。

1. 矩形の座標を求める(carousel.rs)
10面の矩形を円周上に並べるということは、円に内接する正十角形の座標を求めれば良いですね。ということで、公式通り下図のように求めます。


 コードは以下の部分が該当しますが、これは演算済みのものを初期値として持たせておいても構わないですね。まぁ、このコードも初期化時の1回しか呼ばれないので、大した負荷にはならないとは思いますが。
static void initBitmaps() {
    // ビットマップのアドレスを格納
    Bitmaps_t *b = bitmap;
    b->data = gTex_00; b++;
    b->data = gTex_01; b++;
    b->data = gTex_02; b++;
    b->data = gTex_03; b++;
    b->data = gTex_04; b++;
    b->data = gTex_05; b++;
    b->data = gTex_06; b++;
    b->data = gTex_07; b++;
    b->data = gTex_08; b++;
    b->data = gTex_09; b++;

    // 正十角形の一辺の長さを得る
    len = RADIUS * 2 * sin(M_PI/NUM_ITEMS);

    // 矩形の座標を求める
    float angle;
    for (int i = 0; i < NUM_ITEMS; i++) {
        angle = i * 360 / NUM_ITEMS;
        vertices[i*3] = sin(angle * M_PI / 180) * RADIUS;
        vertices[i*3 + 1] = 0;
        vertices[i*3 + 2] = -cos(angle * M_PI / 180) * RADIUS;
    }
}
2. カメラを配置(carousel.rs) 矩形を空間に配置したら、後はカメラを配置すれば良いです。これはモデルビューの座標を設定するところでやればいいですね。
static void displayCarousel() {    
 
    ※コードは該当部分だけ抜粋してます

    // モデルビューの座標行列の設定
    rs_matrix4x4 matrix;
    rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -400.0f); // カメラ位置
    rsMatrixRotate(&matrix, rot, 0.0f, 1.0f, 0.0f);
    rsgProgramVertexLoadModelMatrix(&matrix);

}
この辺りの値は、並べる矩形の数や円周の半径の大きさにもよるので、トライ&エラーで合わせ込む他は無いんでしょうかね?本当は演算で求められたらすっきりするのでしょうが。

3. 矩形の回転(carousel.rs)
矩形の回転は矩形自体を円周に沿って移動させるのでは無くて、単純にカメラをY軸方向に回転させれば同じ効果を得られます。

ので、こちらもモデルビューの座標を設定する部分でカメラの向きを指定するようにします。
static void displayCarousel() {    

    ※コードは該当部分だけ抜粋してます
    
    // モデルビューの座標行列の設定
    rs_matrix4x4 matrix;
    rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -400.0f);
    rsMatrixRotate(&matrix, rot, 0.0f, 1.0f, 0.0f); // カメラを回転
    rsgProgramVertexLoadModelMatrix(&matrix);

}
ユーザーがフリックした場合、はじめは勢い良く回って徐々に減速して止めたいので、描画毎に回転する角度を徐々に小さくするような演算を行ってます。
static void displayCarousel() {    

    ※コードは該当部分だけ抜粋してます

    // カメラの回転角度を演算
    // gVxがフリックの強さ
    if (gVx != 0) {
        rot = rot + gVx;
        gVx = gVx * 0.95;
        if (fabs(gVx) < 0.1) {
            gVx = 0;
        }
    }
    
    // モデルビューの座標行列の設定
    rs_matrix4x4 matrix;
    rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -400.0f); // カメラ位置
    rsMatrixRotate(&matrix, rot, 0.0f, 1.0f, 0.0f); // カメラを回転
    rsgProgramVertexLoadModelMatrix(&matrix);

}
また、カメラは正面を向いた状態では矩形の方を向いていませんので、下図のように18°(18 = 360÷10÷2)だけ回転させたものを初期値として持たせます。


最後に、矩形描画に関する部分のすべてのコードを以下に載せて置きます。
#define NUM_ITEMS 10
static int rot = 360/NUM_ITEMS/2; // カメラの角度の初期値
static void displayCarousel() {
    // Vertexシェーダーの設定
    rsgBindProgramVertex(gProgVertex);

    // 透視変換の設定(透視投影)
    rs_matrix4x4 proj;    
    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 1500.0f);    
    rsgProgramVertexLoadProjectionMatrix(&proj);
    
    // Fragmentシェーダーの設定
    rsgBindProgramStore(gProgStoreBlendNone);
    rsgBindProgramFragment(gProgFragmentTexture);
    rsgBindProgramRaster(gCullNone);
    rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp);

    // カメラの回転角度を演算
    // gVxがフリックの強さ
    if (gVx != 0) {
        rot = rot + gVx;
        gVx = gVx * 0.95;
        if (fabs(gVx) < 0.1) {
            gVx = 0;
        }
    }
    
    // モデルビューの座標行列の設定
    rs_matrix4x4 matrix;
    rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -400.0f); // カメラ位置
    rsMatrixRotate(&matrix, rot, 0.0f, 1.0f, 0.0f); // カメラの回転
    rsgProgramVertexLoadModelMatrix(&matrix);

    // 矩形の描画
    Bitmaps_t *b = bitmap;
    for (int i = 0; i < 10; i++) {
        rsgBindTexture(gProgFragmentTexture, 0, b->data);
        rsgDrawQuadTexCoords(
            vertices[i*3],
            -(len/2),
            vertices[i*3+2],
            0,1,
            vertices[i*3],
            len/2,
            vertices[i*3+2],
            0,0,
            vertices[i == 9 ? 0 : (i+1)*3],
            len/2,
            vertices[i == 9 ? 0 + 2 : (i+1)*3 + 2],
            1,0,
            vertices[i == 9 ? 0 : (i+1)*3],
            -(len/2),
            vertices[i == 9 ? 0 + 2 : (i+1)*3 + 2],
            1,1
        );
        b++;
    }
}
■まとめ
上記の通り、比較的簡単にAndroidタブレットで使われているカルーセル表示を実装することが出来ました。今回、実装するにあたっては、clockmakerさんのPapervision3D本を参考にさせていただいてます。

Flashには昔からこのような3Dのエフェクトを多用したテクニックが確立されてますので、RenderScriptでクールなエフェクトを用いたUIを作る際に参考にできる情報が沢山あると思います。

また、はじめからRenderScriptでゴリゴリ実装して思った動作にならなかった場合は、デバッグが非常にやりずらいので、予めPapervision3Dとかを使って試作するなどして勘所を掴んでおけば、デバッグもスムーズに進むかも知れません。

では、いつものようにコードはここに置いておきます。また、実行時の動画も以下に貼りつけておきます。


0 件のコメント:

コメントを投稿