Estou tentando fazer com que a aparência de personagem funcione no Android.
A ideia é bastante baunilha: tenho minhas matrizes de skinning e, junto com cada vértice, envio até quatro índices matriciais e quatro pesos correspondentes. Soma-os no sombreador de vértices e os aplico a cada vértice.
Isto é o que estou fazendo no vertex shader na versão iOS do meu jogo (não se importe com os normais):
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 funciona muito bem. No entanto, com o mesmo código no Android, em alguns dispositivos (principalmente o Nexus 7 2013), você não pode acessar uniforms com índices não constantes. Em outros termos, você não pode fazer isso:
bones[int(in_bone_index.w)]
porque bones[some_non_constant]é sempre avaliado como bones[0], o que não é divertido. O pior é que o compilador de shader compila isso felizmente.
Esse cara parecia ter exatamente o mesmo problema. Ele resolveu acessando os uniformes como vetores em vez de matrizes. Eu fiz o mesmo e, de fato, funcionou!
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;
}
Mas acho que isso funcionou como um acaso. uniforms não devem ser acessados aleatoriamente, então temo que essa "técnica" não funcione em todos os dispositivos.
Esse cara está passando suas matrizes como texturas, o que é uma ideia bem legal. Eu fiz uma textura 4x32 OES_texture_float, onde cada texel é uma linha da matriz e cada linha da textura é uma matriz inteira. Eu o acesso assim:
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;
}
Na verdade, isso funcionou muito bem ... Até que eu tentei no meu Galaxy Note 2. Desta vez, o compilador reclamou que não podia usar texture2Do shader de vértice!
Então, o que estou fazendo é verificar se a GPU suporta acessos de textura no sombreador de vértice e se suporta OES_texture_float. Se isso acontecer, estou usando a abordagem de textura. Caso contrário, estou usando a abordagem vetorial.
No entanto, a abordagem de textura não está disponível em todas as plataformas, e a abordagem vetorial está funcionando por acaso. Gostaria de saber se existe uma maneira de passar minhas matrizes de capa para o shader de vértice, que funciona de maneira confiável em todos os dispositivos.
Posso ter requisitos mínimos razoáveis do sistema operacional, como o Android 4.1 ou superior, mas gostaria de ter uma solução que funcione em todos os dispositivos que atendam a esses requisitos.