我正在旋转网格内骨架的骨骼以获得低多边形 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
是这些骨头的成员(member)。问题是权重越接近 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
左边是形状开始的方式,中间是上述方程如何旋转它,右边是我的目标。中点加权
0.5
.它越弯曲只会变得更糟,在 180 度时它的直径为零。所以考虑我可能没有考虑过的选项或其他选项,其他人如何避免这种挤压效应?
编辑:我已经让 SLERP 使用四元数工作,但我选择不使用它,因为 GLSL 本身不支持它。我无法让几何 SLERP 像汤姆所描述的那样工作。我让 NLERP 在前 90 度工作,所以我在每个关节之间添加了一个额外的“骨骼”。所以要弯曲前臂 40 度,我将肘部和前臂各弯曲 20 度。这消除了挤压效应,代价是骨骼数量加倍,这不是理想的解决方案。
最佳答案
问题
Levans 中的图说明了您所看到的原因answer .但是,要了解发生了什么,请考虑执行代码时发生的情况:
如果第一点vert1
有坐标(p, 0)
vert2
的坐标将是 (p cos(α), p sin(α))
哪里α
是两个骨骼之间的角度(如果进行适当的坐标变换,这总是可能的)。使用适当的权重将这些加在一起 w
和 1-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 page和 this paper .
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 θ
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)α)
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 旋转矩阵时,我们可以表达旋转变换 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
四元数LERP
使用四元数时,SLERP 的一个很好的近似方法是直接对四元数进行线性插值,然后对结果进行重新归一化。这给出了与 SLERP 中相同的插值曲线,但是插值不会在恒定径向速度下发生。
如果你真的想完全避免这些问题,你总是可以在关节处拆分网格并分别旋转它们。
关于matrix - 将权重应用于矩阵和顶点(骨骼旋转),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25481176/