Come posso implementare in modo affidabile lo skinning della GPU in Android?


11

Sto cercando di far funzionare la skin del personaggio su Android.

L'idea è piuttosto vaniglia: ho le mie matrici skinning e, insieme a ciascun vertice, invio fino a quattro indici di matrice e quattro pesi corrispondenti. Li riassumo nello shader di vertice e li applico a ciascun vertice.

Questo è quello che sto facendo nel vertex shader nella versione iOS del mio gioco (non importa le normali):

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;
}

E funziona abbastanza bene. Tuttavia, con lo stesso codice in Android, in alcuni dispositivi (in particolare il Nexus 7 2013), non è possibile accedere a uniforms con indici non costanti. In altri termini, non puoi farlo:

bones[int(in_bone_index.w)]

perché bones[some_non_constant]viene sempre valutato come bones[0], il che non è affatto divertente. La cosa peggiore è che il compilatore di shader compila felicemente questo.

Questo ragazzo sembrava avere esattamente lo stesso problema. Lo risolse accedendo alle uniformi come vettori anziché come matrici. Ho fatto lo stesso, e in effetti ha funzionato!

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;
}

Ma penso che abbia funzionato come un caso. uniformNon si intende accedere in modo casuale, quindi temo che questa "tecnica" non funzionerà su tutti i dispositivi.

Questo ragazzo sta passando le sue matrici come trame, che è un'idea piuttosto interessante. Ho creato una texture 4x32 OES_texture_float, in cui ogni texel è una riga matrice e ogni riga della trama è un'intera matrice. Vi accedo in questo modo:

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;
}

In effetti, ha funzionato abbastanza bene ... Fino a quando non l'ho provato sul mio Galaxy Note 2. Questa volta il compilatore si è lamentato del fatto che non posso usare texture2Dlo shader di vertice!

Quindi quello che sto facendo è verificare se la GPU supporta gli accessi alle trame sullo shader di vertici e se supporta OES_texture_float. In tal caso, sto usando l'approccio texture. In caso contrario, sto usando l'approccio vettoriale.

Tuttavia, l'approccio texture non è disponibile su tutte le piattaforme e l'approccio vettoriale funziona in qualche modo per caso. Vorrei sapere se esiste un modo per passare le mie matrici skinning allo shader di vertice, che funziona in modo affidabile su tutti i dispositivi.

Posso avere requisiti minimi di sistema operativo ragionevoli, come Android 4.1+, ma vorrei avere una soluzione che funzioni su tutti i dispositivi che soddisfano tali requisiti.


Bene, non riesco a pensare ad alternative, TBH Penso che la tua scommessa migliore sia usare entrambe le tecniche a seconda di quale è disponibile, se nessuno dei due è disponibile non usare lo skin GPU solo il fallback all'implementazione dello skin skin (probabilmente con modelli meno dettagliati ?).
concept3d

@ concept3d: Non lo so, forse esiste un'estensione garantita su Android 4.1+ progettata per risolvere questo problema o fare qualcosa di concettualmente diverso con gli stessi risultati. Mi considero abbastanza abile nei concetti generali di molti argomenti, ma la mia conoscenza della piattaforma Android è molto limitata e stavo pensando che potrebbe esserci una soluzione puramente tecnica a questo problema.
Panda Pajama,

Quindi forse questo è il motivo per cui non sono riuscito a far funzionare la skin sul mio Nexus 7.: / Grazie, la tua domanda mi ha aperto gli occhi!
asincrono il

Risposte:


4

Questo è un comportamento non conforme da parte del Nexus 7 (Adreno GPU). Dici "le uniformi non sono pensate per l'accesso casuale", ma secondo l'Appendice A delle specifiche :

Uniformi (esclusi i campionatori)

Nello shader di vertici è obbligatorio il supporto per tutte le forme di indicizzazione di array. Nello shader di frammenti, il supporto per l'indicizzazione è obbligatorio solo per le espressioni con indice costante.

Dalla discussione qui risulta che questo bug si applica solo ad array di matrici uniformi, quindi è probabile che il work-around utilizzando i vettori funzioni in modo affidabile e sia portatile per altre GPU (so che l'indicizzazione uniforme casuale funziona almeno sulle GPU Mali e PowerVR).


Hmm, immagino che sembra giusto. Penso di aver letto quel thread in precedenza, ma Qualcomm non ha confermato che questo problema sia stato confermato.
Panda Pajama,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.