Bagaimana saya bisa menerapkan skinning GPU di Android dengan andal?

11

Saya mencoba membuat skinning karakter berfungsi di Android.

Idenya cukup vanila: Saya memiliki matriks skinning, dan bersama dengan setiap vertex, saya mengirim hingga empat indeks matriks dan empat bobot yang sesuai. Saya menjumlahkannya dalam vertex shader, dan menerapkannya pada setiap vertex.

Inilah yang saya lakukan di vertex shader di versi iOS game saya (jangan pedulikan normals):

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

Dan itu bekerja dengan cukup baik. Namun, dengan kode yang sama di Android, di beberapa perangkat (terutama Nexus 7 2013), Anda tidak dapat mengakses uniformdengan indeks yang tidak konstan. Dengan kata lain, Anda tidak dapat melakukan ini:

bones[int(in_bone_index.w)]

karena bones[some_non_constant]selalu dievaluasi sebagai bones[0], yang tidak lucu sama sekali. Yang terburuk adalah bahwa shader compiler dengan senang hati mengkompilasi ini.

Orang ini tampaknya memiliki masalah yang persis sama. Dia memecahkannya dengan mengakses seragam sebagai vektor, bukan matriks. Saya melakukan hal yang sama, dan ternyata itu berhasil!

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

Tapi saya pikir ini berfungsi sebagai kebetulan. uniforms tidak dimaksudkan untuk diakses secara acak, jadi saya khawatir "teknik" ini tidak akan berfungsi di setiap perangkat.

Orang ini melewati matriksnya sebagai tekstur, yang merupakan ide yang cukup keren. Saya membuat tekstur OES_texture_float 4x32, di mana setiap texel adalah baris matriks, dan setiap baris tekstur adalah seluruh matriks. Saya mengaksesnya seperti ini:

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

Sebenarnya, ini bekerja dengan sangat baik ... Sampai saya mencobanya di Galaxy Note 2. Kali ini kompiler mengeluh bahwa saya tidak dapat menggunakan texture2Dvertex shader!

Jadi yang saya lakukan adalah memeriksa apakah GPU mendukung akses tekstur pada vertex shader dan apakah mendukung OES_texture_float. Jika ya, saya menggunakan pendekatan tekstur. Jika tidak, saya menggunakan pendekatan vektor.

Namun, pendekatan tekstur tidak tersedia di semua platform, dan pendekatan vektor agak bekerja secara kebetulan. Saya ingin tahu apakah ada cara untuk mengirimkan matriks skinning saya ke vertex shader, yang dapat diandalkan di semua perangkat.

Saya dapat memiliki persyaratan OS wajar minimum, seperti Android 4.1+, tetapi saya ingin memiliki solusi yang berfungsi pada semua perangkat yang memenuhi persyaratan tersebut.

Piyama Panda
sumber
Yah, saya tidak bisa memikirkan alternatif, TBH Saya pikir taruhan terbaik Anda adalah menggunakan kedua teknik tergantung mana yang tersedia, jika keduanya tidak tersedia jangan gunakan skinning GPU hanya mundur untuk implementasi skinning CPU (mungkin dengan model yang kurang rinci ?).
concept3d
@ concept3d: Saya tidak tahu, mungkin beberapa ekstensi yang dijamin ada di android 4.1+ yang dirancang untuk menyelesaikan masalah ini, atau melakukan sesuatu yang berbeda secara konseptual dengan hasil yang sama. Saya menganggap diri saya cukup mahir dalam konsep umum dari banyak topik, tetapi pengetahuan saya tentang platform Android sangat terbatas, dan saya berpikir mungkin ada solusi teknis murni untuk masalah ini.
Piyama Panda
Jadi mungkin ini sebabnya saya tidak dapat menguliti untuk menggunakan Nexus 7.: / Terima kasih, pertanyaan Anda membuka mata saya!
async

Jawaban:

4

Ini adalah perilaku yang tidak sesuai oleh Nexus 7 (Adreno GPU). Anda mengatakan "seragam tidak dimaksudkan untuk diakses secara acak", tetapi menurut Lampiran A dari spesifikasi :

Seragam (tidak termasuk sampler)

Di vertex shader, dukungan untuk semua bentuk pengindeksan array diamanatkan. Di fragmen shader, dukungan untuk pengindeksan hanya diamanatkan untuk ekspresi indeks konstan.

Kedengarannya dari diskusi di sini bahwa bug ini hanya berlaku untuk array matriks yang seragam, sehingga penyelesaian dengan menggunakan vektor cenderung bekerja dengan andal dan portabel untuk GPU lainnya (saya tahu pengindeksan seragam acak bekerja setidaknya pada GPU Mali dan PowerVR).

GuyRT
sumber
Hmm, sepertinya itu benar. Saya pikir saya telah membaca utas itu sebelumnya, tetapi tidak ada pengakuan dari Qualcomm yang mengkonfirmasi masalah ini.
Panda Pajama