今週の進み具合 #14 - 2D ハードウェアスキニング

進み具合 (2014年6月12日 - 6月23日)

ハードウェアスキニングを実装しました。

(動画) スキンメッシュに対して頂点シェーダでのハードウェアスキニング

  • スプライトフォント
  • ベクトルテクスチャによるフォントレンダリング
  • ゲーム内エディタの作成
  • キーボード入力の取得
  • ハードウェアスキニング

距離フィールドによるフォントレンダリング

フォントレンダリングやってみました。事前に libGDX Hiero を使ってフォントテクスチャとグリフのテクスチャアトラスを作成してから、実行時にロードして描画しています。内部的には 1 文字を UTF-32 エンコーディングの文字として扱っています。そのため UTF-8, UTF-32 エンコーディングの文字列をレンダリングすることが可能です。 これでフレームレートなどのデバッグ表示がとても楽になります。

また距離フィールドによるベクトルテクスチャを使っているので、テクスチャの解像度はそこそこに、文字サイズを大きくしても綺麗に描画することができるようになりました。ピクセルシェーダに渡すパラメータによって、フォントの太さも自由自在に変えられるので重宝します。図 1 では、ゲーム内エディタ(in-game editor) のラベル表示にフォントレンダリングを利用しています。

ゲーム内エディタのフォントレンダリングにベクトルテクスチャを使用している例
(図 1) ゲーム内エディタのフォントレンダリングにベクトルテクスチャを使用している例

ハードウェアスキニング

左から「スキンドメッシュ」「スキンドメッシュ + ワイアーフレーム表示」「ワイアーフレーム表示」「ワイアーフレーム表示 + ジョイント(間接)の表示」を表している
(図 2) 左から「スキンドメッシュ」「スキンドメッシュ + ワイアーフレーム表示」「ワイアーフレーム表示」「ワイアーフレーム表示 + ジョイント(間接)の表示」を表している

やっとハードウェアスキニングを実装しました。頂点シェーダを使って、各頂点につき最大 4 つのボーン行列から重みつき平均を求めて、最終的な頂点の位置を計算しています。図 2 では実際に 64 個の間接から作成した行列パレットを定数バッファにコピーして、GPU 上でスキニングを行っています。 GLSL での頂点シェーダのコード例を以下に示します。

#version 330

layout(location = 0) in vec4 PositionTextureCoord;
layout(location = 1) in vec4 Weights;
layout(location = 2) in ivec4 JointIndices;

uniform SkinningConstants {
    vec4 SkinMatrices1[64];
    vec4 SkinMatrices2[64];
};

// ...

void main()
{
    mat3x2 skinning = mat3x2(0.0);
    for (int i = 0; i < 4; ++i)
    {
        int jointIndex = JointIndices[i];
        if (jointIndex < 0) {
            break;
        }

        mat3x2 boneMatrix = mat3x2(
            vec2(SkinMatrices1[jointIndex].xy),
            vec2(SkinMatrices1[jointIndex].zw),
            vec2(SkinMatrices2[jointIndex].xy));

        skinning += boneMatrix * Weights[i];
    }

    vec2 position = (skinning * vec3(PositionTextureCoord.xy, 1.0)).xy;

    gl_Position = vec4(position.xy, 0.0, 1.0) * ModelViewProjection;
    Out.TextureCoord = PositionTextureCoord.zw;
}

3D のスキニングとほとんど変わりませんが、大きく違うところは、2 次元なので 3x2 行列で変換できるということです。3D の場合だと 4x3 行列、もしくはボーンの動きを回転とスケールだけに制限して 3x3 行列で表すことがほとんどです。3x2 行列で足りるので、実質 float の 6 個分(= 3 x 2)つまり vec4 と vec2 で 1 つのボーン行列を表現できます。

苦戦したこと

ハードウェアスキニング自体は半日ちょっととさくっと作ることができました。時間がかかったのは、アセットをロードしてスキンドメッシュを作成する部分でした。スケルトンデータを作るために Spine を利用していますが、ドキュメンテーションを見る限り、スキニングに関するフォーマットが見当たらず、コーディングが難航しました。なんとかエクスポートした Spine の JSON ファイルからフォーマットを推測することができ、スキンドメッシュの読み込みを実装し終えました。

最も時間がかかったのは言うまでもなく、テクスチャとアニメーションデータの作成です…(汗)

Spine について

2D スケルタルアニメーションを作る上で Spine というソフトウェアを利用しています。Spine は libGDX で作られており、マルチプラットフォームです。また多くのゲームエンジン向けにランタイムが用意されており、C のランタイムもオープンソースとして公開されています。見たところ、C で書かれたランタイムについては、ソフトウェアでのスキニングを実装しているようです。
作っているエンジンでは、これらのランタイムを利用せず、スクラッチでスケルタルアニメーションを実装しました。スキニングについても同様に、公開されているランタイムを使用していないため、頂点シェーダによるハードウェアスキニングを実装することができました。どうしてもインスタンシングなどで頂点数が増えると CPU で処理するには限界があるので、今回は GPU によるスキニングを採用してみました。

今後のアニメーションシステムについて

これまで 2D スケルタルアニメーションや剛体アニメーション、スプライトアニメーション、そしてハードウェアスキニングを実装してきました。アニメーションについてはまだまだ課題が残っています。例えばモーフィングや IK (逆運動学)などです。今後実装していきたいのはずばりアニメーションブレンディングです。アニメーションを手作業で作成するのはとても時間がかかり苦労するので、組み合わせ爆発を起こさないためにもアニメーションブレンディングを実装していきます。

今週の予定

  • アニメーションステート
  • スキニング周りのリファクタリング

(来週もこのペースで "今週の進み具合 #15" が更新できますように…。お楽しみに。)

Leave a Reply