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 uniform
s 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. uniform
Non 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 texture2D
lo 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.