本文翻譯自:github.com/fangcun010/g

由於本人才疏學淺,翻譯難免有誤,望各位不吝惜指正。


處理頂點蒙皮的操作略微有點複雜。它幾乎使用了可以包含在glTF資源的JSON文件中的所有元素類型。本章節,我們將對上一章節給出的頂點蒙皮示例代碼進行解釋說明。

幾何數據

我們的頂點蒙皮示例使用的是帶有索引的三角形網格數據,它包含了8個三角形和10個頂點,構成了一個位於x-y平面上的矩形。這個矩形的左下角坐標為(0,0,0),右上角坐標為(1,2,0),所有使用的頂點坐標如下:

0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 0.5, 0.0,
1.0, 0.5, 0.0,
0.0, 1.0, 0.0,
1.0, 1.0, 0.0,
0.0, 1.5, 0.0,
1.0, 1.5, 0.0,
0.0, 2.0, 0.0,
1.0, 2.0, 0.0

所有使用的頂點索引如下:

0, 1, 3,
0, 3, 2,
2, 3, 5,
2, 5, 4,
4, 5, 7,
4, 7, 6,
6, 7, 9,
6, 9, 8,

原始幾何數據被存儲在第一個buffer對象中。索引數據和頂點位置數據分別被索引為0的bufferView對象和索引為1的bufferView對象指定,被索引為0和索引為1的accessor對象描述。下圖是示例的幾何數據使用線框模式渲染的結果:

圖20a:頂點蒙皮示例使用的幾何數據使用線框模式渲染的結果

幾何數據被包含在一個mesh對象中,這一mesh對象被附著在場景中的node對象上。mesh對象包含的primitive對象還包含了"JOINTS_0"和"WEIGHTS_0"兩個屬性。這兩個屬性的用途,我們會在之後說明。

骨骼結構

我們的示例使用了兩個node對象來定義關節點。skin對象通過它的joints屬性來引用這兩個node對象作為關節點。

"nodes" : [
...
{
"children" : [ 2 ],
"translation" : [ 0.0, 1.0, 0.0 ]
},
{
"rotation" : [ 0.0, 0.0, 0.0, 1.0 ]
}
],

第一個關節點node對象包含了一個translation變換屬性,屬性值定義了一個沿y軸平移1.0個單位的變換操作。第二個關節點node對象包含了一個rotation變換屬性,屬性值定義了一個旋轉0度(也就是不進行旋轉)的旋轉變換,這個旋轉角度會被animation對象修改來產生蒙皮動畫。

蒙皮

skin對象的定義是進行頂點蒙皮的核心所在。我們的示例包含了一個skin對象:

"skins" : [
{
"inverseBindMatrices" : 4,
"joints" : [ 1, 2 ]
}
],

skin對象包含了一個joints屬性,它列出了關節點node對象的索引。skin對象還包含了一個inverseBindMatrices屬性,這一屬性引用了一個accessor對象,accessor對象為每個關節點node對象提供了將其變換到對應關節的矩陣信息,也就是關節點node對象的初始全局變換矩陣的逆矩陣信息。對於這個示例,每個關節點node對象的初始全局變換矩陣的逆矩陣是一樣的,如下所示:

1.0 0.0 0.0 0.0
0.0 1.0 0.0 -1.0
0.0 0.0 1.0 0.0
0.0 0.0 0.0 1.0

上面的矩陣將mesh對象沿y軸平移-1個單位,如下圖所示:

圖20b:關節點node對象1進行逆綁定變換

進行這一逆全局變換,乍一看有點違反直覺,實際上,這樣做是為了消除初始全局變換對關節點node對象的影響,使得關節對mesh對象頂點的影響可以基於真實的全局變換。

頂點蒙皮的實現

本小節會對頂點蒙皮的具體操作進行說明。

關節矩陣

被蒙皮的網格頂點位置是在頂點著色器中進行計算。頂點著色器計算蒙皮頂點位置需要當前的骨骼狀態信息。通常骨骼狀態信息會以矩陣數組的形式通過uniform變數傳入頂點著色器。矩陣數組中的每個矩陣對應一個關節點。在頂點著色器中,這些矩陣會被按權重組合起來作為蒙皮矩陣:

...
uniform mat4 u_jointMat[2];

...
void main(void)
{
mat4 skinMat =
a_weight.x * u_jointMat[int(a_joint.x)] +
a_weight.y * u_jointMat[int(a_joint.y)] +
a_weight.z * u_jointMat[int(a_joint.z)] +
a_weight.w * u_jointMat[int(a_joint.w)];
....
}

每個關節點的關節矩陣,需要對它所影響的頂點執行下面的變換:

  • 為了準備進行關節點node對象的當前全局變換。頂點需要先進行關節點node對象的inverseBindMatrix變換。這一變換是關節點node對象初始狀態的全局變換的逆變換矩陣。
  • 頂點進行關節點node對象的當前全局變換,因為已經進行了inverseBindMatrix矩陣變換。進行關節點的當前全局變換時,頂點位於當前關節點的坐標空間下。
  • 頂點需要進行mesh對象所附著結點的全局變換的逆變換。進行這一變換是為了抵消模型視圖矩陣所包含的相同的全局變換。

對於關節點j,它的關節矩陣的計算偽代碼如下所示:

jointMatrix(j) =
globalTransformOfNodeThatTheMeshIsAttachedTo^-1 *
globalTransformOfJointNode(j) *
inverseBindMatrixForJoint(j);

注意:頂點蒙皮有時也會用到一個叫做的"Bind Shape Matrix"的矩陣。這一矩陣用來將網格頂點轉換到關節的坐標空間。對於glTF資源來說,不使用"Bind Shape Matrix"矩陣,假定這一矩陣已經預先處理了網格頂點或這一矩陣已經被組合進inverseBindMatrix變換矩陣。

下圖顯示了上一章節給出的蒙皮示例中的關節點1由動畫引起的幾何變換過程的一個中間狀態。

圖20c:關節點1的幾何變換過程

上圖最右邊,顯示了只使用關節點1的關節矩陣進行變換,幾何頂點的位置變化。圖示狀態實際上並不真實存在,幾何頂點的位置會使用多個關節點的關節矩陣加權後進行計算,具體計算演算法,在下一小節說明。

蒙皮關節和權重

上面提到,mesh.primitive對象包含了兩個新的屬性用於頂點蒙皮。它們是"JOINTS_0"和"WEIGHTS_0"屬性。它們引用的accessor對象為網格中的每個頂點提供蒙皮信息。

"JOINTS_0"屬性引用了一個描述有頂點受哪些關節影響的accessor對象。為了簡化操作,這裡將影響頂點的關節信息的索引存儲在了一個四維向量中,這也意味著我們將關節點數限制為最多4個。對於我們的示例,使用的關節信息非常簡單:

Vertex 0: 0, 1, 0, 0,
Vertex 1: 0, 1, 0, 0,
Vertex 2: 0, 1, 0, 0,
Vertex 3: 0, 1, 0, 0,
Vertex 4: 0, 1, 0, 0,
Vertex 5: 0, 1, 0, 0,
Vertex 6: 0, 1, 0, 0,
Vertex 7: 0, 1, 0, 0,
Vertex 8: 0, 1, 0, 0,
Vertex 9: 0, 1, 0, 0,

上面的數據表明,我們的頂點只受索引為0和索引為1的兩個關節點影響(我們不使用向量的後兩個分量)。如果有多個關節點,我們的accessor對象可能描述了下面這樣的數據:

3, 1, 8, 4,

這表明對應頂點同時受關節點3,1,8和4影響。

"WEIGHTS_0"屬性引用的accessor對象描述了頂點受每個關節點影響的權重。對於我們的示例,權重值為:

Vertex 0: 1.00, 0.00, 0.0, 0.0,
Vertex 1: 1.00, 0.00, 0.0, 0.0,
Vertex 2: 0.75, 0.25, 0.0, 0.0,
Vertex 3: 0.75, 0.25, 0.0, 0.0,
Vertex 4: 0.50, 0.50, 0.0, 0.0,
Vertex 5: 0.50, 0.50, 0.0, 0.0,
Vertex 6: 0.25, 0.75, 0.0, 0.0,
Vertex 7: 0.25, 0.75, 0.0, 0.0,
Vertex 8: 0.00, 1.00, 0.0, 0.0,
Vertex 9: 0.00, 1.00, 0.0, 0.0,

同樣由於只有兩個關節點,我們不使用向量的後兩個分量。

結合頂點的"JOINTS_0"和"WEIGHTS_0"屬性,我們就可以計算出頂點受每個關節的影響。比如,根據上面的數據,頂點6受到關節點0百分之25的影響,受到關節點1百分之75的影響。

我們在頂點著色器中使用線性插值來組合所有影響頂點的關節矩陣,計算出最終的蒙皮矩陣。下面的GLSL代碼,我們將"JOINTS_0"屬性傳給a_joint屬性變數,將"WEIGHTS_0"屬性傳給a_weight屬性變數。

...
attribute vec4 a_joint;
attribute vec4 a_weight;

uniform mat4 u_jointMat[2];

...
void main(void)
{
mat4 skinMat =
a_weight.x * u_jointMat[int(a_joint.x)] +
a_weight.y * u_jointMat[int(a_joint.y)] +
a_weight.z * u_jointMat[int(a_joint.z)] +
a_weight.w * u_jointMat[int(a_joint.w)];
vec4 pos = u_modelViewMatrix * skinMat * vec4(a_position,1.0);
gl_Position = u_projectionMatrix * pos;
}

頂點使用蒙皮矩陣變換後,使用模型視圖矩陣進行變換。最終的變換結果可以認為是使用多個關節矩陣進行加權變換,如下圖所示:

圖20d:計算蒙皮矩陣

下圖顯示了網格中的頂點使用蒙皮矩陣變換後的結果:

圖20e:幾何頂點蒙皮示例

fangcun:glTF格式詳解(目錄)?

zhuanlan.zhihu.com
圖標

推薦閱讀:
相關文章