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;
}
しかし、これはチャンスとして機能したと思います。uniform
sはランダムにアクセスされることを意図していないため、この「テクニック」がすべてのデバイスで機能するとは限りません。
この男は、マトリックスをテクスチャとして渡しています。これはかなりクールなアイデアです。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要件がありますが、それらの要件を満たすすべてのデバイスで機能するソリューションが欲しいです。