[El programador de animación de remedios Henrik Enquist contó cómo su equipo creó una convincente simulación de chaqueta de tweed del protagonista del juego de suspenso y terror Alan Wake].El personaje principal de nuestro thriller de acción es Alan Wake, un escritor atrapado en una pesadilla donde se ve obligado a luchar con fuerzas oscuras y resolver el misterio de la desaparición de su esposa. No es un héroe de acción bien entrenado, sino una persona común.
Para enfatizar el personaje, nuestro director de arte quería vestirlo con una vieja chaqueta de tweed con parches en los codos. El juego tiene lugar en el entorno del mundo real, por lo tanto, a diferencia de un juego de fantasía o un tirador espacial, los personajes están limitados en las herramientas utilizadas. Y esto significa que la ropa de nuestros personajes se vuelve mucho más importante.
Para transmitir la ilusión de una atmósfera de suspenso, la chaqueta de Alan Wake debe ser lo más creíble posible. La chaqueta debe revolotear con el viento y agregar movimientos auxiliares al personaje cuando se mueva por el bosque. Como programador, inmediatamente comencé a pensar en usar la simulación de tejidos.
La simulación de tela se usó en muchos juegos anteriores a nosotros, pero las técnicas que a menudo se usan allí daban la sensación de seda o goma, materiales que no son adecuados para nosotros. Recientemente, comenzaron a aparecer muy buenos sistemas de simulación de tejidos de compañías externas, pero en el momento en que necesitábamos una solución estable, tales herramientas aún no existían o no satisfacían nuestras necesidades.
En este artículo, hablaré sobre los problemas que tuvimos que enfrentar y sobre las soluciones para crear nuestra propia simulación de tejidos.
Plataforma de la chaqueta
La chaqueta fue modelada junto con el resto del personaje como una malla de pelado regular. Los huesos que controlan la malla de la chaqueta son una capa separada en la parte superior de un esqueleto regular. Las mangas de la chaqueta usan el patrón habitual para el hombro y el antebrazo. Tanto los hombros como los antebrazos se dividen en un hueso principal y un hueso doblado. La parte superior de la chaqueta está controlada por restricciones de observación, y la parte inferior está controlada por la simulación Verlet.
Figura 1. Chaqueta de plataforma en la parte superior de un esqueleto de juego normal.Parte superior de la chaqueta
Los huesos de la chaqueta tienen una jerarquía que va de arriba a abajo (los inferiores son hijos de los superiores), de modo que cuando los huesos superiores se mueven, los huesos inferiores los siguen. Estuvimos tentados de hacer que la hija de los huesos inferiores fuera directamente hacia el cofre, pero eso causaría una pérdida de movimiento, especialmente movimiento vertical, cuando el personaje levanta los hombros.
En la parte superior de la chaqueta, simulamos el movimiento de las almohadillas en los hombros, moviendo los huesos de los hombros con la ayuda de restricciones de observación hacia los huesos de los hombros. Gracias a esto, las almohadillas siguen el hombro, y cuando levantas la mano, la almohadilla levanta el resto de los huesos, como en una chaqueta real.
Aspecto de la restricción de restricción de aspectoCostraint look-to aplicado al cono rojo Los siguientes huesos de la cadena son la capa entre la parte superior de la chaqueta y la parte inferior simulada. Estos huesos son impulsados directamente por las restricciones de observación hacia abajo para compensar la rotación que crean los hombros. También agregamos restricciones de posición entre los huesos izquierdo y derecho para compensar el estiramiento que ocurre cuando se mueven las hombreras.
Figura 2. El movimiento de los huesos al levantar un personaje de mano.Esto podría ser suficiente para implementar las restricciones en el exportador de animaciones y hornear los resultados en los datos de animación, pero aún tratamos de controlar los huesos en el motor del juego en tiempo real.
Gracias a esto, pudimos guardar algunos bytes en los datos de la animación, así como transferir fácilmente animaciones entre los personajes, independientemente de si hay chaquetas en ellos. Además, los movimientos del hombro generados por la cinemática inversa del juego (por ejemplo, al momento de apuntar) al resolver restricciones en tiempo real se aplicarían correctamente.
La parte inferior de la chaqueta
Una vez resuelto el problema con la parte superior de la chaqueta, procedimos a simular la parte inferior. La mayoría de las simulaciones de juegos de telas usan una unión uno a uno entre los vértices en la simulación de tejido y los vértices de la malla renderizada.
Queríamos mantener la precisión de la malla de la chaqueta para que no interfiriera con ninguna restricción definida por el programador. Por ejemplo, si decidiéramos usar la misma malla para la simulación de tela que para el renderizado, entonces la silueta de los bolsillos y el frente de la chaqueta se perdería.
Los mapas normales podrían usarse para dar volumen a la chaqueta, pero sentimos que eso no sería suficiente. Queríamos que nuestros artistas modelaran la chaqueta como la querían, y luego les permitimos usar mapas normales para agregar pliegues u otros detalles, en lugar de compensar la geometría perdida.
Llegamos a esta decisión: crear una malla de tela de baja resolución para simular una chaqueta y luego unirla a los huesos del esqueleto que se usa para controlar la malla de desollar.
Figura 3. Comparación de las siluetas de nuestra chaqueta y tela que tiene los mismos vértices con una simulación.Física Werle
Primero miramos la física de Verlet, y luego aprendemos cómo crear una coincidencia para la simulación de huesos. Verlé Physics es actualmente la solución estándar para simular tejidos en juegos. Si no está familiarizado con la técnica Verlet, para empezar, le recomiendo leer uno de estos artículos sobre Gamasutra:
Diablo en el vestido facetado azul: Animación de tela en tiempo real o
Física avanzada de personajes .
Figura 4. Una cuadrícula de vértices 4x4 y restricciones para uno de los vértices.Por lo demás, repetiré brevemente el principio del trabajo. La Figura 4 muestra una malla de tela y restricciones de resorte para uno de sus vértices. Como puede ver en la figura, cada vértice de malla está conectado a todos los vértices vecinos, así como a sus vecinos.
Las restricciones de los vecinos inmediatos se denominan restricciones de estiramiento y se indican en azul. Las restricciones largas indicadas en rojo se denominan restricciones de cizallamiento / plegado.
Es importante almacenar estas restricciones en dos grupos, porque luego las resolveremos con diferentes parámetros. Tenga en cuenta que en nuestra chaqueta, la fila superior de puntos de tela está unida al personaje mediante desollado y no será controlada por la simulación.
La presencia de una malla no es un requisito del algoritmo en sí mismo, sin embargo, para simular un tejido con dicha topología, es más fácil trabajar con él. La base de la simulación de tejidos consta de dos partes. La primera parte es la integración de Verlet, en la que calculamos la velocidad de cada vértice y la aplicamos a la posición.
Vector3 vVelocity = vertex.vCurrentPosition - vertex.vPreviousPosition; vertex.vPreviousPosition = vertex.vCurrentPosition; vertex.vCurrentPosition += vVelocity * ( 1.0f - fDampingFactor ) + vAcceleration * fDeltaTime * fDeltaTime;
En nuestro proyecto,
vAcceleration
fue establecido por la suma de la fuerza gravitacional y el viento. La atenuación se utilizó para ajustar la apariencia de la chaqueta y estabilizar la simulación. Un factor de amortiguación alto
fDampingFactor
le da a la chaqueta la sensación de que la tela muy ligera desciende lenta y suavemente, mientras que un factor de amortiguación bajo hace que la chaqueta sea más pesada, haciendo que se balancee / oscile por más tiempo después del movimiento.
La segunda parte del algoritmo es la resolución de restricciones de resorte (este proceso se llama relajación). Para cada restricción, atraemos o rechazamos los vértices entre sí para que satisfagan sus longitudes originales. Aquí hay un fragmento de código legible.
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;
Las restricciones de estiramiento mantienen unidas las partes superiores de la tela, y las restricciones de inclinación / flexión ayudan a mantener la forma de la tela. Como puede ver, con una solución ideal para este sistema, la tela se moverá demasiado fuerte. Es por eso que, antes de resolver nuevas posiciones, agregamos un coeficiente a las restricciones de inclinación / flexión.
vOffset *= fStiffness; constraint.m_vertex0.m_vCurrentPosition += vOffset / 2.0f; constraint.m_vertex1.m_vCurrentPosition -= vOffset / 2.0f;
Con un coeficiente de rigidez de 1.0, la tela no será flexible y, a 0.0, la tela se doblará sin restricciones.
Paso de tiempo fijo
Ya debe haber notado que la integración de Verlet sugiere que el paso de tiempo anterior era exactamente el mismo que el actual; de lo contrario, la velocidad calculada será incorrecta. Cuando se utiliza la integración Verlet, se puede prescindir de un paso de tiempo variable, pero la resolución de las restricciones es muy sensible a los cambios en el paso de tiempo.
Como el solucionador resuelve el problema al eludir de forma iterativa las restricciones, nunca se pueden resolver de manera ideal. En el juego, esta imprecisión se manifestará como un estiramiento, y cuanto más corto sea el tiempo, menos estiramiento verá el jugador.
En última instancia, esto será un compromiso entre la precisión y la cantidad de tiempo de procesador que puede dedicar a la ropa. Si el paso de tiempo no es constante, el estiramiento de la ropa variará e introduciremos vibraciones no deseadas en el sistema. Más importante aún, el paso de tiempo afectará el indicador de rigidez y otros parámetros de la tela: cuanto más corto sea el paso de tiempo, más rígida será la tela, incluso cuando se use el mismo coeficiente de rigidez.
En la práctica, esto significa que antes de comenzar a personalizar la apariencia de la ropa con la ayuda de los parámetros de la tela, usted mismo debe decidir sobre un paso de tiempo fijo. Sé que hay juegos en los que se usa un paso de tiempo variable para la física, pero mi experiencia personal me dice que la vida se vuelve mucho más fácil cuando se fija el paso de tiempo tanto para la física como para la lógica del juego.
Campana
Antes de entrar en los detalles de la simulación de tejidos, echemos un vistazo rápido a cómo se simula la campana. Para desollar la parte superior de la malla de la capucha, utilizamos un hueso extra. Creamos un péndulo desde el centro del hueso hasta la posición detrás del capó. El final del péndulo es una partícula controlada por la física de Verlet. Luego, usando una restricción de observación, el hueso se dirige hacia el péndulo.
Figura 5. Capucha y péndulo.Creando matrices óseas
La capucha nos da una pista sobre qué hacer a continuación con la parte inferior de la chaqueta. Usaremos las posiciones de vértice en la malla simulada para calcular las transformaciones óseas.
Lo primero que hacemos es mapear los huesos para que la bisagra de cada hueso coincida con la parte superior de la malla simulada. Debido a esto, la tarea de la parte de la matriz relacionada con el desplazamiento será un proceso trivial.
Luego necesitamos calcular la matriz de rotación 3x3. Cada fila (o columna, según la configuración de la matriz) está definida por los ejes x, y y z del hueso.
Definimos el eje x del hueso como la dirección desde el vértice base hasta el siguiente debajo de él. Luego, el eje y se define por el vector desde el vértice de la izquierda hasta el vértice de la derecha.
Figura 6. Huesos unidos a la malla de tela.En la Figura 6, el eje x se muestra en rojo y el eje y se muestra en verde. Luego, el eje z se calcula como el producto vectorial de estos vectores. Al final, también ortonormalizamos la matriz para eliminar la distorsión en los datos de desplazamiento.
Como puede ver, en la dirección vertical, usamos cada fila de la malla de tela (excepto la última) para ajustar los huesos, pero en la dirección horizontal solo se usa cada segunda columna. Además del hecho de que ofrece las ventajas artísticas descritas anteriormente, este método también es bastante rápido. Gracias a esto, se pueden usar técnicas de skinning tradicionales en el lado de la GPU para renderizar la malla, porque de lo contrario tendríamos que actualizar el enorme buffer dinámico de vértices.
Una malla de tela puede tener una resolución bastante baja, lo que reduce la carga en la CPU. El único costo adicional para nuestra solución es convertir la simulación de baja resolución en una malla de alta resolución, pero en nuestro esquema estos costos serán insignificantes en comparación con el resto de la simulación.
Colisiones
Para resolver el problema de recortar tejido con piernas y cuerpo, utilizamos el reconocimiento de colisiones entre un elipsoide y una partícula. La Figura 7 muestra los elipsoides necesarios para resolver el truncamiento de una chaqueta por un modelo de personaje.
Figura 7. El sistema elipsoide para el modelo Wake.El reconocimiento de colisiones de elipsoides con partículas es muy rápido. Las colisiones se pueden resolver transformando el espacio en el que existen el elipsoide y la partícula, de modo que el elipsoide se convierta en una esfera. Luego puede realizar una prueba rápida de colisión de la esfera y la partícula.
En la práctica, esto se acompaña de la creación de una transformación inversa basada en los valores de la longitud, el ancho y la altura del elipsoide con su aplicación a la posición de la partícula. El único problema aquí es que la colisión normal que tenemos después de convertir de nuevo al sistema de coordenadas original está distorsionada.
Decidimos que podríamos llegar a un acuerdo con una ligera imprecisión en el cálculo de la dirección de la colisión. En los casos en que un elipsoide fuertemente estirado podría causar reacciones incorrectas, lo dividimos en dos más homogéneas.
La distancia máxima a la partícula.
Otro problema que debía resolverse era la estabilidad de la chaqueta. El tejido durante el movimiento rápido podría causar la creación de nodos o aparecer en el otro lado del volumen de colisiones y pasar a través del cuerpo. Resolvimos este problema estableciendo una distancia segura para cada vértice del tejido simulado.
Para cada vértice, la posición de reposo inicial por desollado se une al hueso más cercano y lo usamos como punto de referencia. Si la simulación excede el valor umbral, simplemente movemos el vértice más cerca del punto de referencia. En nuestro diseño, permitimos que los picos de abajo se muevan una mayor distancia que los picos más cerca de los hombros.
La distancia máxima que podemos permitir que se muevan los picos es de aproximadamente 40 cm, cuando se excede este valor, comienzan a aparecer casos raros de nodos y truncamiento. También intentamos usar otras técnicas, por ejemplo, planos de colisión, pero el método de distancia máxima resultó ser el mejor. Fue rápido, fácil de configurar y proporcionó la mayor libertad de movimiento antes de que aparecieran errores notables en la tela.
Más tweed, menos goma
Hasta ahora, hemos podido encontrar buenas maneras de lograr nuestros objetivos. Nuestro artista modeló su chaqueta como a él le gustaba; para animar la chaqueta, no se necesitaba un animador, porque todo estaba simulado en el juego, y el procesador estaba satisfecho de que tuviéramos suficientes recursos para otros cálculos en el juego. Pero una cosa nos molestó: la tela parecía goma.
Estiramiento de lucha
Primero, necesitamos deshacernos del estiramiento. Como dije anteriormente, el fenómeno del estiramiento es causado por errores que aparecen debido a la naturaleza iterativa del algoritmo. Este es un tema de investigación popular y se pueden encontrar muchos métodos para resolver este problema.
Desafortunadamente, todas las soluciones disponibles nos obligarían a asignar recursos de CPU mucho más escasos para los cálculos de tejido. Por lo tanto, resolvimos el problema del estiramiento agregando el último paso a la simulación de tejido, en la cual se aplican las llamadas "restricciones duras".
Hicimos restricciones estrictas sobre las restricciones de estiramiento (todas están dirigidas verticalmente). Estas restricciones se ordenaron de arriba a abajo para que las restricciones cerca de los hombros se resolvieran a las restricciones cerca de las piernas.
Como iteramos sobre las restricciones desde arriba, sabemos que el vértice superior del par ya se ha resuelto y no causa ningún estiramiento, por lo que solo debemos mover el vértice inferior hacia el superior. Gracias a esto, podemos estar seguros de que después de una sola iteración, la longitud de arriba a abajo será exactamente la misma que la longitud en reposo.
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. Restricciones estrictas.Como puede ver, no tenemos en cuenta el estiramiento horizontal de la chaqueta. Es imposible aplicar restricciones estrictas a la dirección horizontal, porque en este caso el vértice se resolverá dos veces, es decir, perderemos los resultados de la etapa de cálculo vertical y la longitud de la tela no se mantendrá en reposo.
Sin embargo, notamos que en el caso de una chaqueta, el estiramiento horizontal en realidad permanece invisible para el ojo humano, y debido al estiramiento vertical, la chaqueta se ve muy mal. Esta solución resultó ser bastante buena.
Bordes de la chaqueta
En segundo lugar, queríamos que los bordes de la chaqueta se movieran un poco más que el resto. Por ejemplo, si corre en una chaqueta abierta, notará que la resistencia al aire afecta más los bordes de la chaqueta que la parte central. Esto se debe a que tu cuerpo cubre el resto de la chaqueta del viento.
Los bordes se pueden encontrar fácilmente por la cantidad de restricciones adjuntas a ellos. Cualquier vértice que tenga menos de cuatro restricciones de estiramiento es un borde. Por lo tanto, podemos marcar estos vértices y simularlos con otros parámetros.
- Atenuación reducida.
- El viento global tiene un mayor impacto.
- El movimiento en el espacio mundial tiene un mayor impacto (para más información sobre el movimiento en el espacio mundial, ver más abajo).
- La distancia de seguridad máxima permitida es mayor.
Debido a esto, la frecuencia interna de los bordes será diferente del resto de la chaqueta. Ahora toda la chaqueta no responde a impulsos como un péndulo grande, y solo los bordes agregan un hermoso movimiento auxiliar al movimiento.
Figura 9. La parte superior de los bordes.Movimiento en el espacio mundial y en el espacio local.
Luego notamos que al mover un personaje, el movimiento en el espacio mundial tiene un efecto bastante grande en la simulación, mientras que los pequeños movimientos locales del cuerpo o los movimientos del hombro pasan desapercibidos.
En una simulación de tejido tradicional, las posiciones de los vértices se simulan en el espacio mundial. Alguien puede decir que simular el tejido es correcto, pero se siente poco natural. Por lo tanto, simulamos una chaqueta sobre personajes en el espacio local y por separado agregamos un pequeño movimiento en el espacio mundial. Notamos que los resultados que necesitamos se obtienen con una animación 100% local del esqueleto con un movimiento del 10-30% en el espacio mundial.
Fricción
Y finalmente, queríamos exagerar el contraste entre la chaqueta en movimiento lento y rápido. Queríamos que la chaqueta estuviera relativamente inmóvil al caminar, y cuando Alan salta o esquiva, el movimiento debería ser más animado.
Pensamos que cuando la chaqueta toca el cuerpo, debe moverse menos debido a la fricción entre la chaqueta y la camisa, y cuando la chaqueta se levanta, debe moverse más fuerte porque nada lo restringe. Simulamos esto aplicando un mayor valor de atenuación a cada vértice que toca el elipsoide. Gracias a esto, las partes superiores que tocan el cuerpo aparecerán un poco pegajosas, creando suficiente contraste entre la chaqueta en situaciones normales y en movimientos rápidos.
Conclusión y trabajo adicional.
La primera forma de realización de la simulación de tejidos fue bastante simple de implementar: solo buscamos la palabra "tejido" en la literatura de desarrollo de juegos y aplicamos los algoritmos que encontramos. La segunda etapa, en la que tratamos de lograr una sensación convincente de una chaqueta de tweed, requirió el estudio de artículos científicos, muchas pruebas y errores, e incluso la eliminación de parte del código.
Por supuesto, siempre puedes mejorar algo. Por ejemplo, usar simulación de baja resolución y vincularlo a una malla de alta resolución complica la solución al problema de todas las truncaciones. No tuvimos tiempo suficiente para otros pequeños detalles: por ejemplo, estas son tarjetas plegables en los lugares de los pliegues de la chaqueta o la implementación de la interacción correcta entre la chaqueta y el tornado.
En última instancia, nuestros esfuerzos dieron sus frutos: nuestro tejido es muy diferente de la simulación de tejidos en otros juegos. Se parece mucho más a un tweed que a la seda o el caucho. Además, nuestro sistema resultó ser muy flexible y nos permitió simular otras telas, por ejemplo, la chaqueta de Barry Wheeler y el velo de la anciana. Parece que al ajustar los parámetros puede lograr la simulación y otros tipos de tejido.
Figura 10. Chaqueta de tweed.