matrix - matrices - transformaciones geometricas en opengl
Aplicando pesos a matrices y vértices(rotación ósea) (3)
El problema
La causa de lo que ve es ilustrada por el dibujo en la answer Levans . Sin embargo, para comprender lo que está sucediendo, considere lo que sucede cuando ejecuta el código:
Si el primer punto vert1
tiene coordenadas (p, 0)
las coordenadas de vert2
serán (p cos(α), p sin(α))
donde α
es el ángulo entre los dos huesos (esto siempre es posible dada una transformación de coordenadas apropiada) ) Sumando estos juntos utilizando los pesos adecuados w
y 1-w
obtenemos las siguientes coordenadas:
x = w p + (1-w) p cos(α)
y = (1-w) p sin(α)
La longitud de este vector es:
length^2 = x^2 + y^2
= (w p + (1-w) p cos(α))^2 + (1-w)^2 p^2 sin(α)^2
= p^2 [w^2 + 2 w (1-w) cos(α) + (1-w)^2 cos(α)^2 + (1-w)^2 sin(α)^2]
= p^2 [w^2 + (1-w)^2 + 2 w (1-w) cos(α)]
Como ejemplo, cuando w = 1/2
esto se simplifica a:
length^2 = p^2 (1/2 + 1/2 cos(α)) = p^2 cos(α/2)^2
Y length = p |cos(α/2)|
mientras que la longitud de los vectores originales es p
(ver graph ). La longitud del nuevo vector se reduce, este es el efecto de contracción que percibió. La razón de esto es que en realidad estamos interpolando los dos vértices a lo largo de una línea recta. Si queremos mantener la misma longitud p
, de hecho necesitamos interpolar a lo largo de un círculo alrededor del centro de la rotación. Un posible enfoque es renormalizar el vector resultante, preservando el ancho de la articulación.
Esto significa que debemos dividir las coordenadas del vértice resultantes por |cos(α/2)|
(o el resultado más general para pesos arbitrarios). Esto tiene como efecto secundario, por supuesto, una división por cero siempre que el ángulo sea exactamente 180 ° (por la misma razón, el ancho en la articulación es cero con su técnica).
No soy un experto en animación esquelética, pero me parece que la solución original tal como la describiste es una aproximación para trabajar con ángulos de hueso pequeños (donde el efecto de contracción es mínimo).
Aproximaciones alternativas
Un enfoque diferente es interpolar sus rotaciones en lugar de sus vértices. Véase, por ejemplo, la página slerp wiki y este documento .
SLERP
La técnica de slerp es similar a la técnica que describí anteriormente en el sentido de que también conserva el ancho de la articulación, sin embargo, se interpone directamente a lo largo de un recorrido circular alrededor de la articulación. La fórmula general es:
gl_Position = [sin((1-w)α)*vert1 + sin(wα)*vert2]/sin(α)
Dados los puntos de arriba vert1 = (p, 0)
y vert2 = (p cos(α), p sin(α))
aplicando la fórmula SLERP se obtiene result = (x, y)
con:
x = p [sin((1-w)α) + sin(wα) cos(α)]/sin(α)
y = p sin(wα) sin(α)/sin(α) = p sin(wα)
Cálculo del coseno cos θ
del ángulo entre vert1
y rendimientos result
:
cos(θ) = vert1*result/(|vert1| |result|) = vert1*result/p^2
= p^2 [sin(wα) + sin((1-w)α) cos(α)]/sin(α)/p^2
= [sin(α) cos((1-w)α) - cos(α) sin((1-w)α) + sin((1-w)α) cos(α)]/sin(α)
= cos((1-w)α)
El ángulo entre vert2
y result
es:
cos(φ) = vert2*result/p^2
= [sin(wα) cos(α) + sin((1-w)α) cos(α)^2 + sin((1-w)α) sin(α)^2]/sin(α)
= [sin(wα) cos(α) + sin((1-w)α) cos(α)]/sin(α)
= [sin(wα) cos(α) + sin(α) cos(wα) - cos(α) sin(wα)]/sin(α)
= cos(wα)
Esto significa que θ/φ = (1-w)/w
que expresa el hecho de que SLERP se interpola con la velocidad radial constante. Cuando trabajamos con matrices de rotación 3D podemos expresar la rotación que transforma vert2
en vert2
como M = inverse(A)*B = transpose(A)*B
para que podamos expresar el ángulo de rotación α
como:
cos(α) = (tr(M) - 1)/2 = (tr(transpose(A)*B) - 1)/2
= (A[0][0]*B[0][0] + A[0][1]*B[1][0] + A[0][2]*B[2][0] +
A[1][0]*B[0][1] + A[1][1]*B[1][1] + A[1][2]*B[2][1] +
A[2][0]*B[0][2] + A[2][1]*B[1][2] + A[2][2]*B[2][2] - 1)/2
Quaternion LERP
Cuando se trabaja con cuaterniones, una buena aproximación al SLERP es interpolar linealmente los cuaterniones, después de los cuales se renormaliza el resultado. Esto da una curva de interpolación idéntica a la de SLERP, sin embargo, la interpolación no ocurre a velocidad radial constante.
Si realmente desea evitar estos problemas por completo, siempre puede dividir sus mallas en la articulación y rotarlas por separado.
Estoy rotando los huesos de un esqueleto dentro de una malla para una figura de baja poli 3D. En el sombreador de vértices se aplica así.
glsl:
vec4 vert1 = (bone_matrix[index1]*vertex_in)*weight;
vec4 vert2 = (bone_matrix[index2]*vertex_in)*(1-weight);
gl_Position = vert1+vert2;
bone_matrix[index1]
es la matriz de un hueso y bone_matrix[index2]
es la matriz del otro. weight
designa la pertenencia de vertex_in
a estos huesos. El problema es que cuanto más cerca esté el peso de 0,5, más se encogerá el diámetro del codo cuando se aplique una rotación. Lo he probado con una forma de cilindro de 10 vértice (con un gradiente de pesos). El resultado parecía doblar una manguera de jardín.
Obtuve mi método de ponderación a partir de estas fuentes. De hecho, es la única forma en que pude encontrar:
http://www.opengl.org/wiki/Skeletal_Animation
http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html
http://blenderecia.orgfree.com/blender/skinning_proposal.pdf
La izquierda es la forma en que comienza la forma, el medio es cómo la ecuación anterior lo gira, y la derecha es mi objetivo. Los puntos medios tienen un peso de 0.5
. Solo empeora cuanto más doblado está, a 180 grados tiene cero diámetro.
- Intenté ensamblar la matriz en el sombreador, para poder aplicar los pesos a la rotación en lugar de los vértices resultantes. Se ve perfecto como el de la imagen de la derecha, pero requiere ensamblar la matriz para cada vértice (costoso)
- He investigado los cuaterniones, pero GLS no los admite de forma nativa (corríjanme si me equivoco) y son confusos. ¿Es eso lo que tengo que hacer?
- He considerado tener tres huesos por articulación y agregar una "rótula" entre cada hueso. Esto no eliminaría el problema sino que lo mitigaría.
- Estoy considerando proyectar el vértice su distancia original desde el eje una vez que se rotan. Esto fallaría a 180 grados pero sería (relativamente) barato.
Entonces, teniendo en cuenta las opciones u otras opciones que quizás no haya considerado, ¿cómo han evitado otros este efecto de pellizco?
EDITAR: He conseguido que SLERP funcione usando cuaterniones, pero opté por no usarlo ya que GLSL no lo admite de forma nativa. No pude hacer funcionar el SLERP geométrico como lo describe Tom. Obtuve NLERP trabajando durante los primeros 90 grados, así que agregué un "hueso" extra entre cada articulación. Entonces para doblar el antebrazo 40 grados doblo el codo y el antebrazo en 20 grados cada uno. Esto elimina el efecto de pellizco a expensas de duplicar la cantidad de huesos que no es una solución ideal.
Dependiendo de su aplicación real, es posible que le guste esta variante: puede agregar una banda adicional entre las partes de esta manera:
Los pesos se muestran en verde / verde azulado. Sin embargo, esto requiere un pequeño truco de huesos, así que cuando se doble hacia la derecha, use los huesos del lado derecho y establezca el centro de rotación hacia la derecha, y hacia la izquierda, los huesos del lado izquierdo y el centro de rotación hacia la izquierda.
Descargo de responsabilidad: no soy un tipo 3D, así que solo te sugiero un enfoque matemático que pueda ayudarte.
Antes que nada, permítanme poner este pequeño esquema, de esta manera nos aseguraremos de que todos hablemos de lo mismo:
Las figuras azules y verdes son los huesos originales, rotados completamente con bone_matrix[index1]
o bone_matrix[index2]
. El punto rojo es el centro de rotación, la figura naranja es lo que quieres y la negra es lo que tienes.
Entonces, te imaginas construyendo como un promedio ponderado de los azules y verdes, en este dibujo vemos (gracias a las líneas grises), por qué se encoge así.
Necesita compensar de alguna manera esta contracción, le sugiero que reduzca los puntos de su centro de rotación, necesitamos una escala de valor 2 en la unión entre los huesos y de valor 1 en las extremidades.
Deje scale_matrix
ser una matriz pre calculada: una escala de amplitud 2 centrada en su centro de rotación (punto rojo).
Terminas con este sombreador:
vec4 vert1 = (bone_matrix[index1]*vertex_in)*weight;
vec4 vert2 = (bone_matrix[index2]*vertex_in)*(1-weight);
vec4 inter = vert1+vert2;
vec4 scaled1 = inter*(1-2*min(weight, 1-weight));
vec4 scaled2 = (scale_matrix*inter)*(2*min(weight, 1-weight));
gl_Position = scaled1+scaled2;
Me temo que no puedo probarlo ahora mismo (no sé mucho sobre GLSL), pero creo que podrás adaptarlo a tu caso si algo no encaja.