Como posso implementar de maneira confiável a aparência de GPU no Android?

10

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.

Panda Pajama
fonte
Bem, não consigo pensar em alternativas, TBH, acho que sua melhor aposta é usar as duas técnicas, dependendo de qual delas estiver disponível, se nenhuma delas estiver disponível, não use a skin de GPU apenas como alternativa à implementação de skin da CPU (provavelmente com modelos menos detalhados ?).
precisa
@ concept3d: Eu não sei, talvez alguma extensão que é garantida a existir no Android 4.1+, que foi projetada para resolver esse problema, ou fazer algo conceitualmente diferente com os mesmos resultados. Eu me considero bastante proficiente nos conceitos gerais de muitos tópicos, mas meu conhecimento da plataforma Android é muito limitado e eu achava que poderia haver uma solução puramente técnica para esse problema.
Panda Pajama
Talvez seja por isso que não consegui trabalhar com o Nexus 7.: / Obrigado, sua pergunta abriu meus olhos!
assíncrono

Respostas:

4

Esse é um comportamento não conforme do Nexus 7 (GPU Adreno). Você diz que "os uniformes não devem ser acessados ​​aleatoriamente", mas de acordo com o Apêndice A da especificação :

Uniformes (excluindo os amostradores)

No sombreador de vértice, o suporte a todas as formas de indexação de matriz é obrigatório. No sombreador de fragmento, o suporte à indexação é obrigatório apenas para expressões de índice constante.

Parece que da discussão aqui que esse bug se aplica apenas a matrizes uniformes de matriz, portanto, a solução alternativa de vetores provavelmente funcionará de maneira confiável e será portátil para outras GPUs (sei que a indexação uniforme aleatória funciona pelo menos nas GPUs Mali e PowerVR).

GuyRT
fonte
Hmm, acho que parece certo. Acho que já li esse tópico antes, mas não há nenhum reconhecimento da Qualcomm que confirme esse problema.
Panda Pajama