Comment puis-je implémenter de manière fiable le skinning GPU dans Android?

11

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 uniforms 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. uniforms 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 texture2Dsur 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.

Pyjama Panda
la source
Eh bien, je ne peux pas penser à des alternatives, TBH Je pense que votre meilleur pari est d'utiliser les deux techniques en fonction de celle qui est disponible, si aucune n'est disponible, n'utilisez pas le skinning GPU, juste de retour à la mise en œuvre du skinning CPU (probablement avec des modèles moins détaillés) ?).
concept3d
@ concept3d: Je ne sais pas, peut-être une extension garantie sur Android 4.1+ qui est conçue pour résoudre ce problème, ou faire quelque chose de conceptuellement différent avec les mêmes résultats. Je me considère assez compétent dans les concepts généraux de nombreux sujets, mais ma connaissance de la plate-forme Android est très limitée et je pensais qu'il pouvait y avoir une solution purement technique à ce problème.
Panda Pyjama
C'est peut-être pour cela que je n'ai pas réussi à faire travailler ma peau sur mon Nexus 7.: / Merci, votre question m'a ouvert les yeux!
async

Réponses:

4

Il s'agit d'un comportement non conforme du Nexus 7 (GPU Adreno). Vous dites que "les uniformes ne sont pas censés être accessibles au hasard", mais selon l'annexe A de la spécification :

Uniformes (à l'exclusion des échantillonneurs)

Dans le vertex shader, la prise en charge de toutes les formes d'indexation de tableau est obligatoire. Dans le fragment shader, la prise en charge de l'indexation n'est obligatoire que pour les expressions à index constant.

Il ressort de la discussion ici que ce bogue ne s'applique qu'aux tableaux matriciels uniformes, de sorte que le contournement à l'aide de vecteurs est susceptible de fonctionner de manière fiable et d'être portable sur d'autres GPU (je sais que l'indexation uniforme aléatoire fonctionne au moins sur les GPU Mali et PowerVR).

GuyRT
la source
Hmm, je suppose que cela semble juste. Je pense que j'avais lu ce fil avant, mais il n'y a aucun accusé de réception de Qualcomm confirmant ce problème.
Panda Pyjama