Jak mogę niezawodnie wdrożyć skórowanie GPU w Androidzie?

11

Staram się, aby skórowanie postaci działało na Androidzie.

Pomysł jest dość waniliowy: mam swoje matryce do skórowania i wraz z każdym wierzchołkiem wysyłam do czterech wskaźników macierzy i czterech odpowiednich wag. Sumuję je w module cieniującym wierzchołki i stosuję je do każdego wierzchołka.

Oto, co robię w module cieniującym wierzchołki w mojej wersji na iOS (nie mówiąc o normalnych):

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

I działa całkiem dobrze. Jednak z tym samym kodem w Androidzie, na niektórych urządzeniach (zwłaszcza Nexus 7 2013), nie możesz uzyskać dostępu do uniforms przy niestałych indeksach. Innymi słowy, nie możesz tego zrobić:

bones[int(in_bone_index.w)]

ponieważ bones[some_non_constant]zawsze jest oceniany jako bones[0], co wcale nie jest zabawne. Najgorsze jest to, że kompilator modułu cieniującego chętnie to kompiluje.

Ten facet miał dokładnie ten sam problem. Rozwiązał go, uzyskując dostęp do mundurów jako wektorów zamiast matryc. Zrobiłem to samo i faktycznie zadziałało!

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

Ale myślę, że to działało przypadkowo. uniformnie są przeznaczone do przypadkowego dostępu, więc obawiam się, że ta „technika” nie zadziała w każdym urządzeniu.

Ten facet przekazuje swoje matryce jako tekstury, co jest całkiem fajnym pomysłem. Zrobiłem teksturę 4x32 OES_texture_float, w której każdy texel jest rzędem matrycy, a każdy wiersz tekstury jest całą matrycą. Uzyskuję do tego dostęp w ten sposób:

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

W rzeczywistości działało to całkiem nieźle ... Dopóki nie wypróbowałem tego na mojej Galaxy Note 2. Tym razem kompilator narzekał, że nie mogę używać texture2Dgo w module cieniującym wierzchołków!

Więc sprawdzam, czy GPU obsługuje dostęp do tekstur w module cieniującym wierzchołki i czy obsługuje OES_texture_float. Jeśli tak, używam podejścia do tekstury. Jeśli nie, używam podejścia wektorowego.

Jednak podejście do tekstury nie jest dostępne na wszystkich platformach, a podejście wektorowe działa raczej przypadkowo. Chciałbym wiedzieć, czy istnieje sposób na przekazanie moich macierzy skórowania do modułu cieniującego wierzchołki, który niezawodnie działa na wszystkich urządzeniach.

Mogę mieć minimalne uzasadnione wymagania dotyczące systemu operacyjnego, takie jak Android 4.1+, ale chciałbym mieć rozwiązanie, które działa na wszystkich urządzeniach spełniających te wymagania.

Panda Piżama
źródło
Cóż, nie mogę wymyślić alternatyw, TBH Myślę, że najlepszym rozwiązaniem jest użycie obu technik, w zależności od tego, która z nich jest dostępna, jeśli żadna z nich nie jest dostępna, nie używaj skórowania GPU tylko cofnij się do implementacji skórowania procesora (prawdopodobnie przy mniej szczegółowych modelach ?).
concept3d
@ concept3d: Nie wiem, może jakieś rozszerzenie, które z pewnością istnieje na Androidzie 4.1+, które ma na celu rozwiązanie tego problemu lub zrobienie czegoś koncepcyjnie innego z tymi samymi wynikami. Uważam się za dość biegły w ogólnych koncepcjach wielu tematów, ale moja wiedza na temat platformy Android jest bardzo ograniczona i myślałem, że może być czysto techniczne obejście tego problemu.
Panda Pajama
Więc może dlatego nie mogłem zmusić skórki do pracy nad moim Nexusem 7.: / Dzięki, twoje pytanie otworzyło mi oczy!
asynchronuje

Odpowiedzi:

4

To jest niezgodne zachowanie przez Nexus 7 (GPU Adreno). Mówisz, że „mundury nie są przeznaczone do losowego dostępu”, ale zgodnie z Załącznikiem A specyfikacji :

Mundury (z wyłączeniem próbników)

W module cieniującym wierzchołki wymagana jest obsługa wszystkich form indeksowania tablic. W module cieniującym fragmenty obsługa indeksowania jest wymagana tylko dla wyrażeń o stałym indeksie.

Z dyskusji tutaj wynika , że ten błąd dotyczy tylko tablic macierzy jednolitych, więc obejście za pomocą wektorów prawdopodobnie będzie działało niezawodnie i będzie przenośne dla innych układów GPU (wiem, że losowe indeksowanie jednolitych działa przynajmniej na procesorach graficznych Mali i PowerVR).

GuyRT
źródło
Hmm, wydaje mi się, że to prawda. Myślę, że wcześniej przeczytałem ten wątek, ale Qualcomm nie potwierdza tego problemu.
Panda Pajama,