将权重应用于矩阵和顶点(骨骼旋转)

gun*_*ker 24 glsl matrix skeletal-animation

我正在网格中旋转骨架的骨骼,以获得低聚3D图形.在顶点着色器上它的应用就像这样.
GLSL:

    vec4 vert1 = (bone_matrix[index1]*vertex_in)*weight;
    vec4 vert2 = (bone_matrix[index2]*vertex_in)*(1-weight);
    gl_Position =  vert1+vert2;
Run Code Online (Sandbox Code Playgroud)

bone_matrix[index1]是一个骨骼的矩阵,是另一个骨骼bone_matrix[index2]的矩阵. weight指定vertex_in这些骨头的成员资格.问题是重量越接近.5,当施加旋转时,肘部的直径越大.我用大约10,000个顶点圆柱形状(带有梯度的重量)对它进行了测试.结果看起来像弯曲花园软管.

我从这些来源获得了加权方法.它实际上是我能找到的唯一方式:
http://www.opengl.org/wiki/Skeletal_Animation
http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html
http://blenderecia.orgfree.com /blender/skinning_proposal.pdf

initial_ugly_good

左边是形状如何开始,中间是上面方程如何旋转它,右边是我的目标.中间点是加权的0.5.弯曲度越大越好,180度直径为零.

  • 我已经尝试在着色器上组装矩阵,以便我可以将权重应用于旋转而不是结果顶点.它看起来很像右边的图片,但它需要为每个顶点组装矩阵(昂贵)
  • 我已经研究了四元数,但是glsl本身并没有支持它们(如果我错了,请纠正我)并且它们令人困惑.这是我需要做的吗?
  • 我认为每个关节有三块骨头,并在每根骨头之间添加一个"膝盖骨".这不会消除问题,但会减轻它.
  • 我正在考虑在旋转后将顶点与轴的原始距离投影.这将在180度时失败但是(相对)便宜.

因此,考虑到我可能没有考虑的选项或其他选项,其他人如何避免这种捏合效应?

编辑: 我已经使用四元数工作SLERP,但我选择不使用它,因为GLSL本身不支持它.我不能像汤姆所描述的那样让几何SLERP工作.我让NLERP在前90度工作,所以我在每个关节之间增加了一个"骨头".因此,为了将前臂弯曲40度,我将肘部和前臂各弯曲20度.这消除了挤压效应,代价是加倍骨量,这不是理想的解决方案.

Lev*_*ans 9

免责声明:我不是很多3D人,所以我建议你一个可以帮助你的数学方法.

首先,让我把这个小模式,这样我们就可以确定我们都在谈论同样的事情:

在此输入图像描述

蓝色和绿色的数字是原始的骨骼,完全旋转与任何bone_matrix[index1]bone_matrix[index2].红点是旋转的中心,橙色的数字是你想要的,而黑色的是你拥有的.

所以,你认为构建为蓝色和绿色的加权平均值,在这张图上我们看到(由于灰线),为什么它会像那样缩小.

你需要以某种方式弥补这种缩小,我建议缩小你的旋转中心的点数,我们需要在骨骼之间的交界处缩放值2,并且在四肢的值为1.

scale_matrix是预先计算的矩阵:在你的旋转(红点)的中心为中心的振幅2的缩放.

你最终得到这个着色器:

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;
Run Code Online (Sandbox Code Playgroud)

我担心我现在无法测试它(我不太了解GLSL),但我认为如果不合适的话,你将能够适应你的情况.


Tom*_*uwé 7

问题

Levans 回答的图画说明了你所看见的原因.但是,要了解正在发生的事情,请考虑执行代码时发生的情况:

如果第一点vert1具有坐标(p, 0)的坐标vert2将是(p cos(?), p sin(?))其中?是两个骨之间的角度(这总是可能的给定一个适当的坐标变换).使用适当的权重将这些加在一起w,1-w我们得到以下坐标:

x = w p + (1-w) p cos(?)
y = (1-w) p sin(?)
Run Code Online (Sandbox Code Playgroud)

这个向量的长度是:

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(?)]
Run Code Online (Sandbox Code Playgroud)

例如,当w = 1/2这简化为:

length^2 = p^2 (1/2 + 1/2 cos(?)) = p^2 cos(?/2)^2
Run Code Online (Sandbox Code Playgroud)

length = p |cos(?/2)|而原始矢量的长度为p(参见).新矢量的长度变小,这是您所感知的缩小效果.这样做的原因是我们实际上是沿着一条直线插入两个顶点.如果我们想要保持相同的长度,p我们实际上需要围绕旋转中心的圆圈进行插值.一种可能的方法是重新归一化所得到的矢量,保留关节处的宽度.

这意味着我们需要将得到的顶点坐标除以|cos(?/2)|(或任意权重的更一般结果).当然,当角度恰好为180°时,这有一个副作用,除以零(出于同样的原因,关节的宽度与你的技术相比为零).

我不是骨骼动画专家,但在我看来,你所描述的原始解决方案,是一个近似工作的小骨角(缩小效果最小).

替代方法

另一种方法是插入旋转而不是顶点.请参阅slerp wiki页面本文.

球面线性插值

slerp技术类似于我上面描述的技术,因为它也保留了关节处的宽度,但是它直接沿着关节周围的圆形路径进行插值.通用公式是:

gl_Position = [sin((1-w)?)*vert1 + sin(w?)*vert2]/sin(?)
Run Code Online (Sandbox Code Playgroud)

鉴于上述要点vert1 = (p, 0)vert2 = (p cos(?), p sin(?))应用SLERP公式得出result = (x, y):

x = p [sin((1-w)?) + sin(w?) cos(?)]/sin(?)
y = p sin(w?) sin(?)/sin(?) = p sin(w?)
Run Code Online (Sandbox Code Playgroud)

计算cos ?角度的余弦vert1result得出:

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)?)
Run Code Online (Sandbox Code Playgroud)

vert2和之间的角度result是:

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?)
Run Code Online (Sandbox Code Playgroud)

这意味着?/? = (1-w)/w它表达了SLERP以恒定的径向速度进行插值的事实.使用3D旋转矩阵时,我们可以将旋转变换vert1为表达vert2,M = inverse(A)*B = transpose(A)*B以便我们可以将旋转角度表示?为:

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
Run Code Online (Sandbox Code Playgroud)

四元数LERP

使用四元数时,SLERP的一个很好的近似是直接线性插值四元数,然后重新归一化结果.这给出了与SLERP中的插值曲线相同的插值曲线,但插值不会以恒定的径向速度发生.

如果你真的想完全避免这些问题,你总是可以在关节处分割网格并分别旋转它们.


Kro*_*ica 5

根据您的实际应用,您可能喜欢这种变体:您可以在部件之间添加额外的带,如下所示:

在此输入图像描述

重量以绿色/蓝绿色显示.这需要一点点的骨头但是挂羊头卖狗肉,所以当你弯曲到右侧,你可以使用右侧的骨头,并设置旋转中心的权利,而当向左 - 左侧骨头和旋转中心的左侧.