¿Cómo puedo implementar de manera confiable el revestimiento de GPU en Android?

11

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 uniforms 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. uniformNo 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 texture2Del 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.

Pijama Panda
fuente
Bueno, no puedo pensar en alternativas, TBH, creo que su mejor opción es usar ambas técnicas dependiendo de cuál esté disponible, si ninguna está disponible, no use el revestimiento de GPU, solo recurra a la implementación de revestimiento de CPU (probablemente con modelos menos detallados ?)
concept3d
@ concept3d: No lo sé, tal vez alguna extensión garantizada para Android 4.1+ que esté diseñada para resolver este problema, o hacer algo conceptualmente diferente con los mismos resultados. Me considero bastante competente en los conceptos generales de muchos temas, pero mi conocimiento de la plataforma Android es muy limitado, y estaba pensando que podría haber una solución puramente técnica para este problema.
Panda Pyjama
Entonces, tal vez es por eso que no pude desollar para trabajar en mi Nexus 7.: / ¡Gracias, su pregunta me abrió los ojos!
async

Respuestas:

4

Este es un comportamiento no conforme por parte del Nexus 7 (Adreno GPU). Usted dice "no se debe acceder a los uniformes al azar", pero de acuerdo con el Apéndice A de la especificación :

Uniformes (excluyendo muestreadores)

En el sombreador de vértices, es obligatorio el soporte para todas las formas de indexación de matrices. En el sombreador de fragmentos, el soporte para la indexación solo es obligatorio para las expresiones de índice constante.

De la discusión aquí se desprende que este error se aplica solo a matrices de matrices uniformes, por lo que es probable que la solución usando vectores funcione de manera confiable y sea portátil para otras GPU (sé que la indexación uniforme aleatoria funciona al menos en las GPU Mali y PowerVR).

GuyRT
fuente
Hmm, supongo que eso parece correcto. Creo que había leído ese hilo antes, pero no hay reconocimiento de Qualcomm que confirme este problema.
Panda Pyjama