Como aplico animação esquelética de um arquivo .x (Direct X)?

8

Usando o formato .x para exportar um modelo do Blender, posso carregar uma malha, armadura e animação. Não tenho problemas para gerar a malha e visualizar modelos no jogo. Além disso, tenho animações e a armadura carregadas corretamente nas estruturas de dados apropriadas.

Meu problema é aplicar corretamente a animação aos modelos. Eu tenho a estrutura para aplicar os modelos e o código para selecionar animações e percorrer os quadros.

Pelo que entendi, o AnimationKeys dentro do AnimationSetfornece as transformações para transformar a pose de ligação na pose no quadro animado. Como pequeno exemplo:

Animation {
  {Armature_001_Bone}
  AnimationKey { 
    2; //Position
    121; //number of frames
    0;3;     0.000000, 0.000000, 0.000000;;,
    1;3;     0.000000, 0.000000, 0.005524;;,
    2;3;     0.000000, 0.000000, 0.022217;;,
    ...
  }
  AnimationKey { 
    0; //Quaternion Rotation 
    121;
    0;4;   -0.707107, 0.707107, 0.000000, 0.000000;;,
    1;4;   -0.697332, 0.697332, 0.015710, 0.015710;;,
    2;4;   -0.684805, 0.684805, 0.035442, 0.035442;;,
    ...
  }
  AnimationKey { 
    1; //Scale
    121;
    0;3;     1.000000, 1.000000, 1.000000;;,
    1;3;     1.000000, 1.000000, 1.000000;;,
    2;3;     1.000000, 1.000000, 1.000000;;,
    ...
  }
}

Portanto, para aplicar o quadro 2, eu pegaria a posição, a rotação e a escala do quadro 2, criaria uma matriz de transformação (chamada Transform_A) a partir delas e aplicaria a essa matriz os vértices controlados por Armature_001_Boneseus pesos. Então, eu enfiava TransformAno shader e transformava o vértice. Algo como:

vertexPos = vertexPos * bones[ int(bfs_BoneIndices.x) ] * bfs_BoneWeights.x;

Onde bfs_BoneIndicese bfs_BoneWeightssão valores específicos para o vértice atual.

Ao carregar nos vértices da malha, eu os transformo pelo rootTransforme pelo meshTransform. Isso garante que eles sejam orientados e redimensionados corretamente para visualizar a pose de ligação.

O problema é que, quando eu crio essa matriz de transformação (usando a posição, rotação e escala da animação), ela não transforma adequadamente o vértice. Provavelmente há mais do que apenas usar os dados da animação. Eu também tentei aplicar as hierarquias de transformação óssea, ainda sem dados. Basicamente, acabo com alguns modelos distorcidos. Deve-se notar também que estou trabalhando em openGL, portanto, qualquer transposição de matriz que precise ser aplicada deve ser considerada.

Quais dados eu preciso e como combiná-los para aplicar animações .x aos modelos?

Fiz alguns modelos da aparência, caso isso seja útil.

Primeiro, eu queria apenas testar a tradução, esta é uma cabeça oscilante, como é no Blender:

http://i.stack.imgur.com/NAc4B.gif

E como fica no jogo (não importa as cores):

http://i.stack.imgur.com/nj2du.gif

Então, apenas para rotação, a animação foi a cabeça girando 360 graus em torno do eixo vertical. Isto é o que parece no jogo:

http://i.stack.imgur.com/gVyUW.gif

Observe que não deve haver inclinação, apenas rotação como um carrossel.

Atualizar

Eu tenho a parte da tradução da animação funcionando. Mas parece meio hacky e não vejo como aplicá-lo à rotação.

A tradução funciona seguindo estas etapas:

  1. Assuma a posição do quadro de animação e troque os valores yez
  2. Traduzir a matriz de transformação pela posição alterada
  3. Transponha a matriz de transformação
  4. Aplique a matriz de transformação aos vértices

Então é assim que pode funcionar, mas como deve funcionar geralmente para posição, escala e rotação?

MichaelHouse
fonte
2
Você transformou o vértice com a matriz de pose de ligação inversa antes de aplicar o esfola?
R2d2rigo
Eu tenho, tem um efeito, mas não é o desejado. Parece afetar a representação visual do modelo (invertendo todas as normais, ao que parece), mas não afeta o movimento / rotação / escala.
Michaelhouse
Você poderia postar uma captura de tela ilustrando o problema?
R2d2rigo 03/09/12
@ r2d2rigo Eu atualizei com gifs animados.
MichaelHouse
11
Estou pensando principalmente aqui, mas se a ordem do vetor (xyz, zxy) diferir entre .xe opengl, você obterá alguns efeitos divertidos. O fato de a transposição ajudar significa que ela usa o vetor de coluna (ou linha) para a posição, e não o contrário. en.wikipedia.org/wiki/… tem uma descrição das rotações para 2D. Eu não sei muito bem como modificar um quaternion. Ou a diferença entre os formatos .x e openGL
Daniel Carlsson

Respostas:

3

Deve-se notar também que estou trabalhando em openGL, portanto, qualquer transposição de matriz que precise ser aplicada deve ser considerada.

Talvez essa seja a chave, você já tentou negar algumas coordenadas Z para converter de coordenadas esquerdas do DirectX para destras do OpenGL?

Você pode negar todos os Zs antes de aplicar suas transformações e renegar quando terminar, para ver se a saída melhora.

EDITAR

Trocar as coordenadas Y e Z é outra maneira de mudar de posição. Então você precisa adaptar suas transformações, consulte este documento para obter detalhes .

O fato de você ter que transpor sua matriz também significa que você vai da linha principal para a coluna principal ou vice-versa. Isso deve ser menos complicado, provavelmente você só precisará encontrar o "lugar" certo para fazê-lo em seu pipeline. Estou apenas supondo, mas fazê-lo em quaterniões pode significar simplesmente alterá-los para matrizes, transpor e voltar quaterniões, se sua biblioteca de matemática oferecer essa opção.

Laurent Couvidou
fonte
Tipo de me dizer o que eu já sei aqui ...
Michaelhouse
11
Bem, você não mencionou imparcialidade na sua pergunta e acredito que isso seja pelo menos parte do seu problema. Não posso presumir que você saiba ou não que o DirectX e o OpenGL estão seguindo convenções diferentes nesse ponto (na verdade, isso é um pouco mais complexo ). Observe que isso pode ajudar outras pessoas que estão lendo esta pergunta a vê-la mencionada, o que é o objetivo de todo o site.
Laurent Couvidou
Justo, você está certo. Eu apenas sugeri o fato de saber sobre as conversões que precisavam ocorrer quando mencionei o uso do OpenGL e as transposições que precisavam acontecer.
Michaelhouse
2

OK, dediquei mais tempo a isso e o fiz funcionar para vários ossos, e consideravelmente menos parecido com um hack (embora ainda um pouco). O sistema que eu tinha originalmente estava quase correto. No entanto, os principais problemas foram:

  1. O Blender salva Quaternions nos formatos W, X, Y, Z. Eu estava carregá-los X, Y, Z, W .
  2. Meu código para converter um Quaternion em uma matriz estava incorreto. Na verdade, ainda não sei por que isso está incorreto, mas sei que o seguinte código funciona, como parte da minha classe Quaternion:

    public Matrix4f toMatrix() {
        Matrix4f tmpMatrix = new Matrix4f();
        if (this.length() > 0)
            this.normalise();
    
        float xx = this.x * this.x;
        float xy = this.x * this.y;
        float xz = this.x * this.z;
        float wx = this.x * this.w;
    
        float yy = this.y * this.y;
        float yz = this.y * this.z;
        float wy = this.y * this.w;
    
        float zz = this.z * this.z;
        float wz = this.z * this.w;
        //note the "* -1"s
        //I needed those to make the same matrices Blender was expecting
        tmpMatrix.m00 = (1.0f - 2.0f * (yy + zz)) * -1;
        tmpMatrix.m01 = (       2.0f * (xy - wz)) * -1;
        tmpMatrix.m02 =        2.0f * (xz + wy);
        tmpMatrix.m03 = 0;
    
        tmpMatrix.m10 =        2.0f * (xy + wz);
        tmpMatrix.m11 = 1.0f - 2.0f * (xx + zz);
        tmpMatrix.m12 = (       2.0f * (yz - wx)) * -1;
        tmpMatrix.m13 = 0;
    
        tmpMatrix.m20 =        2.0f * (xz - wy);
        tmpMatrix.m21 =        2.0f * (yz + wx);
        tmpMatrix.m22 = (1.0f - 2.0f * (xx + yy)) * -1;
        tmpMatrix.m23 = 0;
        return tmpMatrix;
    }

Fora isso, é apenas:

finalMatrix = boneMatrix * skinMatrix * invertedBindMatrix;

Observe que eles boneMatrixdevem usar matrizes animadas para cada osso, em vez das matrizes de ligação. Ele também deve obter todas as matrizes pai da seguinte forma:

private Matrix4f getBoneMatrix() {
        if (parent == null) {
            return Matrix4f.mul(Matrix4f.mul(parentArmature.getBindMatrix(), parentArmature.getArmatureRootMatrix(), null), boneMatrix, null);
        } else {
            return Matrix4f.mul(parent.getBoneMatrix(), boneMatrix, null);
        }
    }

E isso funciona. Deixe um comentário se precisar de esclarecimentos.

MichaelHouse
fonte
Eu escrevi algumas informações mais gerais sobre como fazer isso funcionar no meu blog: byte56.com/2012/12/the-blender-connection.html
MichaelHouse