24

我正在为低多边形 3D 图形旋转网格内的骨架骨骼。在顶点着色器上它是这样应用的。
gsl:

    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这些骨骼的成员资格。问题是重量越接近 0.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 像 Tom 所描述的那样工作。我让 NLERP 在前 90 度工作,所以我在每个关节之间添加了一个额外的“骨骼”。因此,为了将前臂弯曲 40 度,我将肘部和前臂分别弯曲 20 度。这以骨骼数量加倍为代价消除了挤压效应,这不是一个理想的解决方案。

4

3 回答 3

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;

恐怕我现在无法测试它(我对 GLSL 了解不多),但我认为如果有什么不合适的地方,你可以根据自己的情况进行调整。

于 2014-08-30T10:47:41.147 回答
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(见图。新向量的长度变小了,这就是你感知到的收缩效果。这样做的原因是我们实际上是在沿直线对两个顶点进行插值。如果我们想要保持相同的长度p,我们实际上需要沿着围绕旋转中心的圆进行插值。一种可能的方法是重新归一化结果向量,保留关节处的宽度。

这意味着我们需要将结果顶点坐标除以|cos(α/2)|(或更一般的任意权重结果)。当然,这有一个副作用,当角度正好为 180° 时,除以零(出于同样的原因,您的技术中关节处的宽度为零)。

我不是骨骼动画专家,但在我看来,您描述的原始解决方案是使用小骨骼角度(收缩效果最小)的近似值。

替代方法

另一种方法是插入旋转而不是顶点。例如,参见slerp wiki 页面本文

SLERP

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 θ之间夹角的余弦: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)α)

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

这意味着θ/φ = (1-w)/w它表达了 SLERP 以恒定径向速度进行插值的事实。当使用 3D 旋转矩阵时,我们可以将旋转转换vert1vert2as 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

四元数 LERP

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

如果您真的想完全避免这些问题,您可以随时在关节处拆分网格并分别旋转它们。

于 2014-09-01T18:07:29.093 回答
5

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

在此处输入图像描述

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

于 2014-09-01T04:35:30.813 回答