[O programador de animação de remédios Henrik Enquist contou como sua equipe criou uma atraente simulação de jaqueta de tweed do protagonista do jogo de suspense e suspense Alan Wake.]O personagem principal do nosso thriller de ação é Alan Wake, um escritor preso em um pesadelo, onde é forçado a lutar com forças das trevas e resolver o mistério do desaparecimento de sua esposa. Ele não é um herói de ação bem treinado, mas uma pessoa comum.
Para enfatizar o personagem, nosso diretor de arte queria vesti-lo com uma velha jaqueta de tweed com manchas nos cotovelos. O jogo acontece na comitiva do mundo real, portanto, ao contrário de um jogo de fantasia ou de um jogo de tiro espacial, os personagens são limitados nas ferramentas utilizadas. E isso significa que as roupas de nossos personagens se tornam muito mais importantes.
Para transmitir a ilusão de uma atmosfera de suspense, a jaqueta de Alan Wake deve ser o mais crível possível. A jaqueta deve vibrar com o vento e adicionar movimentos auxiliares ao personagem ao se mover pela floresta. Como programador, comecei imediatamente a pensar em usar simulação de tecidos.
A simulação de tecido foi usada em muitos jogos antes de nós, mas as técnicas usadas com frequência deram uma sensação de seda ou borracha - materiais que não são adequados para nós. Apenas muito recentemente começaram a surgir sistemas muito bons de simulação de tecidos de empresas terceirizadas, mas no momento em que precisávamos de uma solução estável, essas ferramentas ainda não existiam ou não atendiam às nossas necessidades.
Neste artigo, falarei sobre os problemas que tivemos que enfrentar e sobre as soluções para criar nossa própria simulação de tecidos.
Equipamento de jaqueta
A jaqueta foi modelada junto com o resto do personagem como uma malha de pele normal. Os ossos que controlam a malha da jaqueta são uma camada separada no topo de um esqueleto comum. As mangas da jaqueta usam o padrão usual para o ombro e o antebraço. Os ombros e os antebraços são divididos em um osso principal e um osso dobrado. A parte superior da camisa é controlada por restrições de observação e a parte inferior é controlada pela simulação Verlet.
Figura 1. Jaqueta do equipamento em cima de um esqueleto de jogo comum.Top de jaqueta
Os ossos da jaqueta têm uma hierarquia que vai de cima para baixo (os inferiores são filhos dos superiores); portanto, quando os ossos superiores se movem, os ossos inferiores os seguem. Ficamos tentados a fazer a filha dos ossos inferiores diretamente no peito, mas isso causaria uma perda de movimento, principalmente o movimento vertical, quando o personagem erguia os ombros.
Na parte superior da jaqueta, simulamos o movimento das almofadas nos ombros, movendo os ossos dos ombros com a ajuda de restrições de observação em direção aos ossos dos ombros. Graças a isso, as almofadas seguem o ombro e, quando você levanta a mão, a almofada levanta o resto dos ossos, como em uma jaqueta de verdade.
Como é a restrição de restrição de aparênciaRestrição visual aplicada ao cone vermelho Os próximos ossos da cadeia são a camada entre a parte superior da jaqueta e a parte inferior simulada. Esses ossos são direcionados diretamente pelas restrições observadas para compensar a rotação que os ombros criam. Também adicionamos restrições de posição entre os ossos esquerdo e direito para compensar o alongamento que ocorre quando as ombreiras se movem.
Figura 2. O movimento dos ossos ao levantar um personagem da mão.Isso pode ser suficiente para implementar as restrições no exportador de animações e exibir os resultados nos dados da animação, mas ainda tentamos controlar os ossos no mecanismo do jogo em tempo real.
Graças a isso, pudemos salvar alguns bytes nos dados da animação, além de transferir facilmente animações entre os caracteres, independentemente de haver jaquetas neles. Além disso, os movimentos do ombro gerados pela cinemática inversa do jogo (por exemplo, no momento da mira) ao resolver restrições em tempo real seriam aplicados corretamente.
O fundo da jaqueta
Tendo resolvido o problema com a parte superior da jaqueta, passamos a simular a parte inferior. A maioria das simulações de tecidos de jogos usa uma ligação individual entre os vértices na simulação de tecido e os vértices da malha renderizada.
Queríamos manter a precisão da malha do revestimento para que não interferisse em nenhuma restrição definida pelo programador. Por exemplo, se decidíssemos usar a mesma malha para simulação de tecido e para renderização, a silhueta dos bolsos e da frente da jaqueta seria perdida.
Mapas normais poderiam ser usados para dar volume à jaqueta, mas achamos que isso não seria suficiente. Queríamos que nossos artistas modelassem a jaqueta da maneira que desejavam e depois deixassem que usassem mapas normais para adicionar vincos ou outros detalhes, em vez de compensar a geometria perdida.
Chegamos a essa decisão: crie uma malha de tecido de baixa resolução para simular uma jaqueta e, em seguida, prenda-a nos ossos do esqueleto usado para controlar a malha de esfolamento.
Figura 3. Comparação das silhuetas de nossa jaqueta e tecido que possuem os mesmos vértices com uma simulação.Física Werle
Primeiro, examinamos a física de Verlet e depois aprendemos como criar uma correspondência para a simulação de ossos. Atualmente, a Verlé Physics é a solução padrão para simular tecidos em jogos. Se você não está familiarizado com a técnica de Verlet, então, para começar, recomendo a leitura de um destes artigos sobre Gamasutra:
Diabo de Vestido Azul Facetado: Animação em Pano em Tempo Real ou
Física Avançada de Personagens .
Figura 4. Uma grade de vértices 4x4 e restrições para um dos vértices.Quanto ao resto, repetirei brevemente o princípio do trabalho. A Figura 4 mostra uma malha de tecido e restrições de mola para um de seus vértices. Como você pode ver na figura, cada vértice da malha é conectado a todos os vértices vizinhos, bem como aos seus vizinhos.
As restrições de vizinhos imediatos são chamadas de restrições de extensão e são indicadas por azul. As restrições longas indicadas em vermelho são chamadas restrições de cisalhamento / dobra.
É importante armazenar essas restrições em dois grupos, porque mais tarde as resolveremos com parâmetros diferentes. Observe que, em nossa jaqueta, a linha superior dos pontos de tecido está ligada ao personagem por esfolamento e não será controlada pela simulação.
A presença de uma malha de malha não é um requisito do próprio algoritmo; no entanto, para simular um tecido com essa topologia, é mais fácil trabalhar com ele. A base da simulação de tecido consiste em duas partes. A primeira parte é a integração Verlet, na qual calculamos a velocidade de cada vértice e a aplicamos à posição.
Vector3 vVelocity = vertex.vCurrentPosition - vertex.vPreviousPosition; vertex.vPreviousPosition = vertex.vCurrentPosition; vertex.vCurrentPosition += vVelocity * ( 1.0f - fDampingFactor ) + vAcceleration * fDeltaTime * fDeltaTime;
Em nosso projeto, a
vAcceleration
foi definida pela soma da força gravitacional e do vento. A atenuação foi usada para ajustar a aparência da jaqueta e estabilizar a simulação. Um alto fator de amortecimento
fDampingFactor
dá à jaqueta a sensação de um tecido muito leve descendo lenta e suavemente, e um baixo fator de amortecimento torna a jaqueta mais pesada, fazendo com que oscile / oscile por mais tempo após o movimento.
A segunda parte do algoritmo é a resolução de restrições de mola (esse processo é chamado de relaxamento). Para cada restrição, atraímos ou repelimos os vértices um do outro para que eles satisfaçam seus comprimentos originais. Aqui está um trecho de código legível.
Vector3 vDelta = constraint.m_vertex1.m_vCurPos - constraint.m_vertex0.m_vCurPos; float fLength = vDelta.length(); vDelta.normalize(); Vector3 vOffset = vDelta * ( fLength - constraint.m_fRestLength ); constraint.m_vertex0.m_vCurrentPosition += vOffset / 2.0f; constraint.m_vertex1.m_vCurrentPosition -= vOffset / 2.0f;
As restrições de estiramento mantêm as partes superiores do tecido unidas, e as restrições de inclinação / dobra ajudam a manter a forma do tecido. Como você pode ver, com uma solução ideal para esse sistema, o tecido se moverá com muita força. Por isso, antes de resolver novas posições, adicionamos um coeficiente às restrições de inclinação / flexão.
vOffset *= fStiffness; constraint.m_vertex0.m_vCurrentPosition += vOffset / 2.0f; constraint.m_vertex1.m_vCurrentPosition -= vOffset / 2.0f;
Com um coeficiente de rigidez de 1,0, o tecido não é flexível e, em 0,0, o tecido se dobra sem nenhuma restrição.
Etapa de tempo fixo
Você já deve ter notado que a integração do Verlet sugere que o passo anterior era exatamente o mesmo que o atual; caso contrário, a velocidade calculada estará incorreta. Usando a integração Verlet, uma etapa de tempo variável pode ser dispensada, mas a resolução de restrições é muito sensível a alterações na etapa de tempo.
Como o solucionador resolve o problema contornando iterativamente as restrições, elas nunca podem ser resolvidas idealmente. No jogo, essa imprecisão se manifestará como um alongamento, e quanto menor o tempo, menor o alongamento que o jogador verá.
Por fim, esse será um compromisso entre a precisão e a quantidade de tempo do processador que você pode gastar em roupas. Se o intervalo de tempo não for constante, o alongamento das roupas variará e introduziremos vibrações indesejadas no sistema. Mais importante, o intervalo de tempo afetará o indicador de rigidez e outros parâmetros do tecido: quanto menor o tempo, mais rígido será o tecido, mesmo ao usar o mesmo coeficiente de rigidez.
Na prática, isso significa que, antes de começar a personalizar a aparência da roupa com a ajuda dos parâmetros do tecido, você deverá decidir sobre uma etapa de tempo fixo. Eu sei que existem jogos nos quais um intervalo de tempo variável é usado para a física, mas minha experiência pessoal me diz que a vida se torna muito mais fácil quando o intervalo de tempo para a física e a lógica do jogo é fixo.
Hood
Antes de entrarmos nos detalhes da simulação de tecido, vamos dar uma rápida olhada em como o capô é simulado. Para esfolar os topos da malha do capô, usamos um osso extra. Criamos um pêndulo do centro do osso até a posição atrás do capô. O fim do pêndulo é uma partícula controlada pela física de Verlet. Então, usando uma restrição de olhar, o osso é direcionado para o pêndulo.
Figura 5. Capuz e pêndulo.Criando matrizes ósseas
O capuz nos dá uma dica sobre o que fazer em seguida com a parte de baixo da jaqueta. Usaremos as posições dos vértices na malha simulada para calcular as transformações ósseas.
A primeira coisa que fazemos é mapear os ossos para que a dobradiça de cada osso corresponda ao topo da malha simulada. Por isso, a tarefa da parte da matriz relacionada ao deslocamento será um processo trivial.
Então precisamos calcular a matriz de rotação 3x3. Cada linha (ou coluna, dependendo da configuração da matriz) é definida pelos eixos x, ye z do osso.
Definimos o eixo x do osso como a direção do vértice base para o próximo abaixo dele. Então, o eixo y é definido pelo vetor do vértice à esquerda até o vértice à direita.
Figura 6. Ossos presos à malha do tecido.Na Figura 6, o eixo x é mostrado em vermelho e o eixo y é mostrado em verde. Então, o eixo z é calculado como o produto vetorial desses vetores. No final, também ortogonalizamos a matriz para se livrar da distorção nos dados de deslocamento.
Como você pode ver, na direção vertical, usamos cada linha da malha do tecido (exceto a última) para ajustar os ossos, mas na direção horizontal apenas cada segunda coluna é usada. Além de fornecer as vantagens artísticas descritas acima, esse método também é bastante rápido. Graças a isso, as técnicas tradicionais de skinning podem ser usadas no lado da GPU para renderizar a malha, porque, caso contrário, teríamos que atualizar o enorme buffer dinâmico de vértices.
Uma malha de malha pode ter uma resolução bastante baixa, o que reduz a carga na CPU. O único custo adicional para nossa solução é converter a simulação de baixa resolução em uma malha de alta resolução, mas em nosso esquema esses custos serão insignificantes em comparação com o restante da simulação.
Colisões
Para resolver o problema de aparar tecidos com pernas e corpo, usamos o reconhecimento de colisões entre um elipsóide e uma partícula. A Figura 7 mostra os elipsóides necessários para resolver o truncamento de uma jaqueta por um modelo de personagem.
Figura 7. O sistema elipsóide para o modelo Wake.O reconhecimento de colisões de elipsóides com partículas é muito rápido. As colisões podem ser resolvidas transformando o espaço no qual o elipsóide e a partícula existem, de modo que o elipsóide se transforme em uma esfera. Você pode executar um teste rápido de colisão da esfera e da partícula.
Na prática, isso é acompanhado pela criação de uma transformação inversa com base nos valores do comprimento, largura e altura do elipsóide com sua aplicação na posição da partícula. O único problema aqui é que a colisão normal que temos após a conversão de volta ao sistema de coordenadas original está distorcida.
Decidimos que poderíamos chegar a um acordo com uma ligeira imprecisão no cálculo da direção da colisão. Nos casos em que um elipsóide fortemente esticado pode causar reações incorretas, dividimos em mais duas homogêneas.
A distância máxima para a partícula
Outro problema que precisava ser resolvido foi a estabilidade da jaqueta. O tecido durante o movimento rápido pode causar a criação de nós ou aparecer do outro lado do volume de colisões e passar pelo corpo. Resolvemos esse problema definindo uma distância segura para cada vértice do tecido simulado.
Para cada vértice, a posição inicial de repouso por esfola é anexada ao osso mais próximo e a usamos como ponto de referência. Se a simulação exceder o valor limite, simplesmente movemos o vértice para mais perto do ponto de referência. Em nosso projeto, permitimos que os picos abaixo se movessem uma distância maior do que os picos mais próximos dos ombros.
A distância máxima que podemos permitir que os picos se movam é de cerca de 40 cm. Quando esse valor é excedido, casos raros de nós e truncamento começam a aparecer. Também tentamos usar outras técnicas, por exemplo, aviões de colisão, mas o método da distância máxima acabou sendo o melhor. Era rápido, fácil de configurar e oferecia a maior liberdade de movimento antes que erros visíveis começassem a aparecer no tecido.
Mais Tweed, Menos Borracha
Até o momento, conseguimos encontrar boas maneiras de alcançar nossos objetivos. Nosso artista modelou sua jaqueta do jeito que ele gostava; para animar a jaqueta, não era necessário um animador, porque tudo foi simulado no jogo e o processador ficou satisfeito por termos recursos suficientes para outros cálculos no jogo. Mas uma coisa nos incomodou - o tecido parecia borracha.
Alongamento de combate
Primeiro, precisamos nos livrar do alongamento. Como eu disse acima, o fenômeno do alongamento é causado por erros que aparecem devido à natureza iterativa do algoritmo. Este é um tópico de pesquisa popular e muitos métodos podem ser encontrados para resolver esse problema.
Infelizmente, todas as soluções disponíveis nos forçariam a alocar recursos de CPU muito mais escassos para cálculos de tecidos. Portanto, resolvemos o problema do alongamento adicionando o último passo à simulação do tecido, na qual as chamadas "restrições rígidas" são aplicadas.
Fizemos restrições estritas às restrições de extensão (todas elas são direcionadas verticalmente). Essas restrições foram classificadas de cima para baixo, de modo que as restrições próximas aos ombros fossem resolvidas para restrições próximas às pernas.
Como iteramos sobre as restrições acima, sabemos que o vértice superior do par já foi resolvido e não causa nenhum alongamento, portanto, precisamos mover o vértice inferior em direção ao superior. Graças a isso, podemos ter certeza de que, após uma única iteração, o comprimento de cima para baixo será exatamente o mesmo que o comprimento em repouso.
Vector3 vDelta = constraint.m_vertexTop.m_vCurPos - constraint.m_vertexDown.m_vCurPos; float fLength = vDelta.length(); vDelta.normalize(); Vector3 vOffset = vDelta * ( fLength - constraint.m_fRestLength ); constraint.m_vertexDown.m_vCurrentPosition += vOffset;
Figura 8. Restrições rígidas.Como você pode ver, não levamos em consideração o alongamento horizontal da jaqueta. É impossível aplicar restrições estritas à direção horizontal, porque neste caso o vértice será resolvido duas vezes, ou seja, perderemos os resultados do estágio de computação vertical e o comprimento do tecido não será mantido em repouso.
No entanto, percebemos que, no caso de uma jaqueta, o alongamento horizontal permanece invisível ao olho humano e, devido ao alongamento vertical, a jaqueta parece muito ruim. Essa solução acabou sendo muito boa.
Bordas da jaqueta
Em segundo lugar, queríamos que as bordas da jaqueta se movessem um pouco mais do que o resto. Por exemplo, se você usar um paletó aberto, notará que a resistência do ar afeta mais as bordas do paletó do que a parte central. Isso ocorre porque seu corpo cobre o restante da jaqueta com o vento.
As arestas podem ser facilmente encontradas pelo número de restrições associadas a elas. Qualquer vértice que tenha menos de quatro restrições de extensão é uma aresta. Portanto, podemos marcar esses vértices e simulá-los com outros parâmetros.
- Atenuação reduzida.
- O vento global tem um impacto maior.
- O movimento no espaço mundial tem um impacto maior (para mais informações sobre o movimento no espaço mundial, veja abaixo).
- A distância máxima de segurança permitida é maior.
Devido a isso, a frequência interna das bordas será diferente do restante do revestimento. Agora, a jaqueta inteira não responde a impulsos como um pêndulo grande, e apenas as bordas adicionam um belo movimento auxiliar ao movimento.
Figura 9. Os topos das bordas.Movimento no espaço mundial e no espaço local
Percebemos então que, ao mover um personagem, o movimento no espaço do mundo tem um efeito bastante grande na simulação, enquanto pequenos movimentos corporais locais ou movimentos dos ombros passam despercebidos.
Em uma simulação tradicional de tecidos, as posições dos vértices são simuladas no espaço do mundo. Alguém pode dizer que simular o tecido é correto, mas não parece natural. Portanto, simulamos uma jaqueta para os personagens no espaço local e, separadamente, adicionamos um pequeno movimento no espaço mundial. Percebemos que os resultados que precisamos são obtidos com animação local do esqueleto a 100%, com movimento de 10 a 30% no espaço mundial.
Fricção
E, finalmente, queríamos exagerar o contraste entre a jaqueta em movimentos lentos e rápidos. Queríamos que a jaqueta ficasse relativamente imóvel ao caminhar e, quando Alan pula ou esquiva, o movimento deve ser mais animado.
Pensamos que quando a jaqueta toca o corpo, ela deve se mover menos devido ao atrito entre a jaqueta e a camisa e, quando a jaqueta se eleva, deve se mover com mais força porque nada a restringe. Simulamos isso aplicando um valor de atenuação aumentado a cada vértice que toca o elipsóide. Graças a isso, as partes superiores do corpo parecerão um pouco pegajosas, criando contraste suficiente entre a jaqueta em situações normais e em movimento rápido.
Conclusão e trabalho adicional
A primeira modalidade da simulação de tecidos foi bastante simples de implementar: nós apenas pesquisamos a palavra "fabric" na literatura de desenvolvimento de jogos e aplicamos os algoritmos que encontramos. A segunda etapa, na qual tentamos obter uma sensação convincente de uma jaqueta de tweed, exigiu o estudo de artigos científicos, muitas tentativas e erros e até a remoção de parte do código.
Claro, você sempre pode melhorar alguma coisa. Por exemplo, usar simulação de baixa resolução e vinculá-lo a uma malha de alta resolução complica a solução para o problema de todos os truncamentos. Não tivemos tempo suficiente para outros pequenos detalhes: por exemplo, são cartões dobráveis nos locais das dobras da jaqueta ou a implementação da interação correta entre a jaqueta e o tornado.
Por fim, nossos esforços valeram a pena - nosso tecido é muito diferente da simulação de tecidos em outros jogos. Ela parece muito mais um tweed do que seda ou borracha. Além disso, nosso sistema acabou sendo muito flexível e nos permitiu simular outros tecidos, por exemplo, a jaqueta de Barry Wheeler e o véu da velha senhora. Parece que, ajustando os parâmetros, você pode obter simulação e outros tipos de tecido.
Figura 10. Jaqueta de tweed.