J'essaie de faire en sorte que le skin skining fonctionne sur Android.
L'idée est assez vanille: j'ai mes matrices de skinning, et avec chaque sommet, j'envoie jusqu'à quatre indices matriciels et quatre poids correspondants. Je les additionne dans le vertex shader et les applique à chaque sommet.
C'est ce que je fais dans le vertex shader dans la version iOS de mon jeu (ne vous occupez pas des normales):
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;
}
Et ça marche plutôt bien. Cependant, avec le même code sous Android, dans certains appareils (notamment le Nexus 7 2013), vous ne pouvez pas accéder aux uniform
s avec des indices non constants. En d'autres termes, vous ne pouvez pas faire ceci:
bones[int(in_bone_index.w)]
car bones[some_non_constant]
est toujours évalué comme bones[0]
, ce qui n'est pas amusant du tout. Le pire, c'est que le compilateur de shaders compile cela avec bonheur.
Ce mec semblait avoir exactement le même problème. Il l'a résolu en accédant aux uniformes comme vecteurs au lieu de matrices. J'ai fait de même, et en fait ça a marché!
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;
}
Mais je pense que cela a fonctionné comme un hasard. uniform
s ne sont pas destinés à être consultés au hasard, donc je crains que cette "technique" ne fonctionne pas sur tous les appareils.
Ce gars passe ses matrices sous forme de textures, ce qui est une idée plutôt cool. J'ai créé une texture 4x32 OES_texture_float, où chaque texel est une ligne de matrice et chaque ligne de texture est une matrice entière. J'y accède comme ceci:
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;
}
En fait, cela fonctionnait plutôt bien ... Jusqu'à ce que je l'essaie sur mon Galaxy Note 2. Cette fois, le compilateur s'est plaint que je ne pouvais pas l'utiliser texture2D
sur le vertex shader!
Je vérifie donc si le GPU prend en charge les accès aux textures sur le vertex shader et s'il prend en charge OES_texture_float. Si c'est le cas, j'utilise l'approche de texture. Si ce n'est pas le cas, j'utilise l'approche vectorielle.
Cependant, l'approche de texture n'est pas disponible sur toutes les plateformes, et l'approche vectorielle fonctionne un peu par hasard. Je voudrais savoir s'il existe un moyen de transmettre mes matrices de skinning au vertex shader, qui fonctionne de manière fiable sur tous les appareils.
Je peux avoir des exigences de système d'exploitation minimales raisonnables, comme Android 4.1+, mais j'aimerais avoir une solution qui fonctionne sur tous les appareils qui répondent à ces exigences.