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 uniform
dengan 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. uniform
s 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 texture2D
vertex 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.