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