Estoy tratando de que el skinning de personajes funcione en Android.
La idea es bastante vainilla: tengo mis matrices de pelado y, junto con cada vértice, envío hasta cuatro índices de matriz y cuatro pesos correspondientes. Los sumo en el sombreador de vértices y los aplico a cada vértice.
Esto es lo que estoy haciendo en el sombreador de vértices en la versión de iOS de mi juego (no importa las normales):
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;
}
Y funciona bastante bien. Sin embargo, con el mismo código en Android, en algunos dispositivos (especialmente el Nexus 7 2013), no puede acceder a uniform
s con índices no constantes. En otros términos, no puedes hacer esto:
bones[int(in_bone_index.w)]
porque bones[some_non_constant]
siempre se evalúa como bones[0]
, lo que no es divertido en absoluto. Lo peor es que el compilador de sombreadores compila felizmente esto.
Este tipo parecía tener exactamente el mismo problema. Lo resolvió accediendo a los uniformes como vectores en lugar de matrices. ¡Hice lo mismo, y de hecho funcionó!
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;
}
Pero creo que esto funcionó como una casualidad. uniform
No se debe acceder al azar, por lo que me temo que esta "técnica" no funcionará en todos los dispositivos.
Este tipo está pasando sus matrices como texturas, lo cual es una idea genial. Hice una textura 4x32 OES_texture_float, donde cada texel es una fila de matriz, y cada fila de textura es una matriz completa. Accedo así:
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;
}
De hecho, esto funcionó bastante bien ... Hasta que lo probé en mi Galaxy Note 2. ¡Esta vez el compilador se quejó de que no puedo usar texture2D
el sombreador de vértices!
Entonces, lo que estoy haciendo es verificar si la GPU admite accesos de textura en el sombreador de vértices y si admite OES_texture_float. Si es así, estoy usando el enfoque de textura. Si no es así, estoy usando el enfoque vectorial.
Sin embargo, el enfoque de textura no está disponible en todas las plataformas, y el enfoque vectorial está funcionando por casualidad. Me gustaría saber si hay una manera de pasar mis matrices de pelado al sombreador de vértices, que funciona de manera confiable en todos los dispositivos.
Puedo tener requisitos mínimos de SO razonables, como Android 4.1+, pero me gustaría tener una solución que funcione en todos los dispositivos que cumplan con esos requisitos.