24

低ポリ 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]は 1 つのボーンのbone_matrix[index2]マトリックスであり、もう 1 つのボーンのマトリックスです。 これらのボーンへの のメンバーシップを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 はそれらをネイティブにサポートしておらず (間違っている場合は訂正してください)、混乱しています。それは私がする必要があることですか?
  • 関節ごとに 3 つのボーンを考え、すべてのボーンの間に「膝頭」を追加しました。これで問題が解決するわけではありませんが、軽減されます。
  • 頂点を回転させた後、軸から元の距離に頂点を投影することを検討しています。これは 180 度で失敗しますが、(比較的) 安価です。

では、オプション、または私が考慮しなかった可能性のある他のオプションを考慮すると、他の人はこのピンチ効果をどのように回避していますか?

編集: 四元数を使用して SLERP を動作させましたが、GLSL がネイティブでサポートしていないため、使用しないことにしました。トムが説明したように、幾何学的な SLERP を機能させることができませんでした。最初の 90 度で NLERP が機能するようになったので、各ジョイント間に余分な「ボーン」を追加しました。前腕を 40 度曲げるには、肘と前腕をそれぞれ 20 度ずつ曲げます。これにより、骨の量が 2 倍になるという犠牲を払って挟み込み効果がなくなりますが、これは理想的な解決策ではありません。

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なりますは 2 つのボーンの間の角度です (これは、適切な座標変換が与えられれば常に可能です)。適切な重みを使用してこれらを合計すると、次の座標が得られます。(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(グラフを参照) です。新しいベクトルの長さが短くなります。これは、あなたが認識した縮小効果です。これは、実際には 2 つの頂点を直線に沿って補間しているためです。同じ長さを維持したい場合は、p実際には回転の中心を中心に円に沿って補間する必要があります。可能なアプローチの 1 つは、結果のベクトルを再正規化して、ジョイントの幅を維持することです。

これは、結果の頂点座標を|cos(α/2)|(または任意の重みのより一般的な結果) で割る必要があることを意味します。もちろん、これには副作用として、角度が正確に 180° の場合は常にゼロで除算されます (同じ理由で、ジョイントの幅はテクニックではゼロです)。

私は骨格アニメーションの専門家ではありませんが、あなたが説明した元の解決策は、小さな骨の角度 (収縮効果が最小限) で動作する近似値であるように思えます。

代替アプローチ

別のアプローチは、頂点の代わりに回転を補間することです。たとえば、slerp wiki ページこの論文を参照してください。

スラープ

スラープ テクニックは、ジョイントの幅も保持するという意味で、上で説明したテクニックと似ていますが、ジョイントの周りの円形パスに沿って直接補間します。一般式は次のとおりです。

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

上記の点を考慮し、SLERP 式を適用するvert1 = (p, 0)と、次のようになります。vert2 = (p cos(α), p sin(α))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 のものと同じ補間曲線が得られますが、一定の半径方向速度では補間が行われません。

これらの問題を完全に回避したい場合は、いつでもジョイントでメッシュを分割し、これらを別々に回転させることができます。

于 2014-09-01T18:07:29.093 に答える
5

実際のアプリケーションによっては、このバリアントが気に入る場合があります。次のようにパーツ間に追加のバンドを追加できます。

ここに画像の説明を入力

重量は緑/ティールで表示されます。ただし、これにはちょっとした骨の工夫が必要なので、右に曲げるときは右側のボーンを使用して回転中心を右に設定し、左に曲げるときは左側のボーンと回転中心を左に設定します。

于 2014-09-01T04:35:30.813 に答える