AndroidでGPUスキニングを確実に実装するにはどうすればよいですか?


10

Androidでキャラクタースキニングを機能させようとしています。

アイデアはかなりバニラです:私にはスキニングマトリックスがあり、各頂点と共に、最大4つのマトリックスインデックスと4つの対応する重みを送信します。それらを頂点シェーダーで合計し、各頂点に適用します。

これは、ゲームのiOSバージョンの頂点シェーダーで行っていることです(法線は問題ありません)。

attribute vec4 in_pos;
attribute vec4 in_normal;
attribute vec2 in_texture_coords;
attribute vec4 in_bone_index;
attribute vec4 in_bone_weight;

varying vec2 fs_texture_coords;

uniform mat4 world_view_projection;
uniform mat4 bones[@bind_matrix_count];

void main()
{
    // Skinning
    vec4 transformed_pos =
        ((in_pos * bones[int(in_bone_index.x)]) * in_bone_weight.x) +
        ((in_pos * bones[int(in_bone_index.y)]) * in_bone_weight.y) +
        ((in_pos * bones[int(in_bone_index.z)]) * in_bone_weight.z) +
        ((in_pos * bones[int(in_bone_index.w)]) * in_bone_weight.w);

    gl_Position = world_view_projection * transformed_pos;
    fs_texture_coords = in_texture_coords;
}

そして、それはかなりうまくいきます。ただし、Androidの同じコードを使用すると、一部のデバイス(特にNexus 7 2013)では、uniform定数でないインデックスでにアクセスできません。言い換えると、これを行うことはできません。

bones[int(in_bone_index.w)]

bones[some_non_constant]は常にと評価されるためbones[0]、まったくおかしくありません。最悪なのは、シェーダーコンパイラがこれをうまくコンパイルできることです。

この男はまったく同じ問題を抱えているようでした。彼は、マトリックスではなくベクトルとしてユニフォームにアクセスすることでそれを解決しました。私も同じことをしましたが、実際にはうまくいきました!

attribute vec4 in_pos;
attribute vec4 in_normal;
attribute vec2 in_texture_coords;
attribute vec4 in_bone_index;
attribute vec4 in_bone_weight;

varying vec2 fs_texture_coords;

uniform mat4 world_view_projection;
uniform vec4 bones[@bind_matrix_count * 4]; // four vec4's for each matrix

void main()
{
    // Skinning
    mat4 skin_0 = mat4(
        bones[4 * int(in_bone_index.x) + 0],
        bones[4 * int(in_bone_index.x) + 1],
        bones[4 * int(in_bone_index.x) + 2],
        bones[4 * int(in_bone_index.x) + 3]);
    mat4 skin_1 = mat4(
        bones[4 * int(in_bone_index.y) + 0],
        bones[4 * int(in_bone_index.y) + 1],
        bones[4 * int(in_bone_index.y) + 2],
        bones[4 * int(in_bone_index.y) + 3]);
    mat4 skin_2 = mat4(
        bones[4 * int(in_bone_index.z) + 0],
        bones[4 * int(in_bone_index.z) + 1],
        bones[4 * int(in_bone_index.z) + 2],
        bones[4 * int(in_bone_index.z) + 3]);
    mat4 skin_3 = mat4(
        bones[4 * int(in_bone_index.w) + 0],
        bones[4 * int(in_bone_index.w) + 1],
        bones[4 * int(in_bone_index.w) + 2],
        bones[4 * int(in_bone_index.w) + 3]);
    vec4 transformed_pos =
        ((in_pos * skin_0) * in_bone_weight.x) +
        ((in_pos * skin_1) * in_bone_weight.y) +
        ((in_pos * skin_2) * in_bone_weight.z) +
        ((in_pos * skin_3) * in_bone_weight.w);

    gl_Position = world_view_projection * transformed_pos;
    fs_texture_coords = in_texture_coords;
}

しかし、これはチャンスとして機能したと思います。uniformsはランダムにアクセスされることを意図していないため、この「テクニック」がすべてのデバイスで機能するとは限りません。

この男は、マトリックスをテクスチャとして渡しています。これはかなりクールなアイデアです。4x32 OES_texture_floatテクスチャを作成しました。各テクセルは行列の行で、各テクスチャ行は行列全体です。私はこれにアクセスします:

attribute vec4 in_pos;
attribute vec4 in_normal;
attribute vec2 in_texture_coords;
attribute vec4 in_bone_index;
attribute vec4 in_bone_weight;

varying vec2 fs_texture_coords;

uniform mat4 world_view_projection; // A texture!
uniform sampler2D bones;

void main()
{
    // Skinning
    mat4 bone0 = mat4(
        texture2D(bones, vec2(0.00, in_bone_index.x / 32.0)),
        texture2D(bones, vec2(0.25, in_bone_index.x / 32.0)),
        texture2D(bones, vec2(0.50, in_bone_index.x / 32.0)),
        texture2D(bones, vec2(0.75, in_bone_index.x / 32.0)));
    mat4 bone1 = mat4(
        texture2D(bones, vec2(0.00, in_bone_index.y / 32.0)),
        texture2D(bones, vec2(0.25, in_bone_index.y / 32.0)),
        texture2D(bones, vec2(0.50, in_bone_index.y / 32.0)),
        texture2D(bones, vec2(0.75, in_bone_index.y / 32.0)));
    mat4 bone2 = mat4(
        texture2D(bones, vec2(0.00, in_bone_index.z / 32.0)),
        texture2D(bones, vec2(0.25, in_bone_index.z / 32.0)),
        texture2D(bones, vec2(0.50, in_bone_index.z / 32.0)),
        texture2D(bones, vec2(0.75, in_bone_index.z / 32.0)));
    mat4 bone3 = mat4(
        texture2D(bones, vec2(0.00, in_bone_index.w / 32.0)),
        texture2D(bones, vec2(0.25, in_bone_index.w / 32.0)),
        texture2D(bones, vec2(0.50, in_bone_index.w / 32.0)),
        texture2D(bones, vec2(0.75, in_bone_index.w / 32.0)));
    vec4 transformed_pos =
        ((in_pos * bone0) * in_bone_weight.x) +
        ((in_pos * bone1) * in_bone_weight.y) +
        ((in_pos * bone2) * in_bone_weight.z) +
        ((in_pos * bone3) * in_bone_weight.w);

    gl_Position = world_view_projection * transformed_pos;
    fs_texture_coords = in_texture_coords;
}

実際、これはかなりうまくいきました... Galaxy Note 2で試してみるまでは。今回texture2Dは、頂点シェーダーでは使用できないというコンパイラーの不満が出ました!

つまり、GPUが頂点シェーダーでのテクスチャアクセスをサポートしているかどうか、およびOES_texture_floatをサポートしているかどうかを確認しています。もしそうなら、私はテクスチャアプローチを使用しています。そうでない場合は、ベクトルアプローチを使用しています。

ただし、すべてのプラットフォームでテクスチャアプローチを使用できるわけではなく、ベクターアプローチはたまたま機能しています。すべてのデバイスで確実に機能する頂点シェーダーにスキニングマトリックスを渡す方法があるかどうか知りたいのですが。

Android 4.1以降のように、最低限必要なOS要件がありますが、それらの要件を満たすすべてのデバイスで機能するソリューションが欲しいです。


まあ、私は代替案を考えることはできません、TBH私があなたの最善の策は、どちらかが利用可能であるかどうかに応じて両方の手法を使用することです?)。
concept3d 2014年

@ concept3d:わからない、おそらくこの問題を解決するために設計されたAndroid 4.1以降で存在することが保証されている拡張機能、または同じ結果で概念的に異なる何かを行う 私は多くのトピックの一般的な概念にかなり熟練していると思いますが、Androidプラットフォームに関する私の知識は非常に限られているため、この問題に対する純粋に技術的な回避策があると考えていました。
パンダパジャマ2014年

Nexus 7でスキンを機能させることができなかったのは、これが理由かもしれません:/ありがとう、あなたの質問が私の目を開きました!
非同期2014年

回答:


4

これは、Nexus 7(Adreno GPU)による非準拠の動作です。あなたは「ユニフォームはランダムにアクセスされることを意図していない」と言いますが、仕様の付録Aによると:

ユニフォーム(サンプラーを除く)

頂点シェーダーでは、配列インデックス付けのすべての形式のサポートが必須です。フラグメントシェーダーでは、インデックス作成のサポートは、定数インデックス式に対してのみ必須です。

ここでの議論から、このバグは均一行列配列にのみ適用されるため、ベクトルを使用した回避策は確実に機能し、他のGPUに移植できる可能性があります(ランダムな均一インデックスが少なくともMaliおよびPowerVR GPUで機能することがわかっています)。


うーん、そうだと思います。以前にそのスレッドを読んだことがあると思いますが、この問題を確認するQualcommからの確認はありません。
パンダパジャマ2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.