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

时间:2014-08-25 07:41:46

标签: matrix glsl 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;

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度。这样可以消除挤压效应,但不会使骨量增加一倍,这不是理想的解决方案。

3 个答案:

答案 0 :(得分:9)

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

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

enter image description here

蓝色和绿色数字是原始骨骼,使用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;

我担心我现在无法对其进行测试(我对GLSL了解不多),但我认为如果能够将其调整到您的案例中,有些东西不合适。

答案 1 :(得分:7)

问题

Levans answer中的图纸说明了您所看到的内容的原因。但是,要了解正在发生的事情,请考虑执行代码时发生的情况:

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

x = w p + (1-w) p cos(α)
y = (1-w) p sin(α)

此向量的长度为:

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(α)]

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

length^2 = p^2 (1/2 + 1/2 cos(α)) = p^2 cos(α/2)^2

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

这意味着我们需要将得到的顶点坐标除以|cos(α/2)|(或任意权重的更一般结果)。当然,当角度恰好为180°时,除此之外还有一个零除(因为同样的原因,关节的宽度在你的技术中为零)。

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

替代方法

另一种方法是插入旋转而不是顶点。例如,请参阅slerp wiki pagethis paper

<强>球面线性插值

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

gl_Position = [sin((1-w)α)*vert1 + sin(wα)*vert2]/sin(α)

鉴于上述点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α)

计算cos θvert1之间角度的余弦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)α)

vert2result之间的角度为:

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α)

这意味着θ/φ = (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

Quaternion LERP

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

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

答案 2 :(得分:5)

根据您的实际应用,您可能会喜欢以下变体:您可以在部件之间添加其他频段,如下所示:

enter image description here

重量以绿色/青色显示。然而,这需要一些小骨头技巧,所以当你向右弯曲时,你使用右侧骨骼并将旋转中心设置在右侧,并且当左侧 - 左侧骨骼和旋转中心位于左侧时。

相关问题