GLSL 程式設計/Unity/非線性變形

本教程介紹了頂點混合作為非線性形變的一個例子。主要的應用實際上是蒙皮網格的渲染。
雖然本教程不基於任何其他特定的教程,但對“頂點變換”部分的良好理解非常有用。
大多數網格的形變不能透過“頂點變換”部分中討論的 4×4 矩陣的仿射變換來建模。緊身胸衣對人體的形變就是一個例子。在計算機圖形學中,更重要的一個例子是網格在關節彎曲時的形變,例如肘部或膝蓋。
本教程介紹了頂點混合來實現這些形變中的某些形變。基本思想是在頂點著色器中應用多個模型變換(在本教程中我們只使用兩個模型變換),然後混合變換後的頂點,即計算它們的加權平均值,權重必須為每個頂點指定。例如,骨骼關節附近皮膚的形變主要受兩個在關節處相遇的(剛性)骨骼的位置和方向的影響。因此,兩個骨骼的位置和方向定義了兩個仿射變換。皮膚上的不同點受到兩個骨骼的影響不同:關節處的點可能受到兩個骨骼的同等影響,而遠離關節的一個骨骼附近的點受到該骨骼的影響比另一個骨骼的影響更強。這兩個骨骼影響的不同強度可以透過在兩個變換的加權平均值中使用不同的權重來實現。
為了便於理解本教程,我們使用兩個統一變換mat4 _Trafo0 和 mat4 _Trafo1,它們由使用者指定。為此,一小段 JavaScript(應該附加到要變形的網格)允許我們指定另外兩個遊戲物件並將它們的模型變換複製到著色器的統一變數。
@script ExecuteInEditMode()
public var bone0 : GameObject;
public var bone1 : GameObject;
function Update ()
{
if (null != bone0)
{
renderer.sharedMaterial.SetMatrix("_Trafo0",
bone0.renderer.localToWorldMatrix);
}
if (null != bone1)
{
renderer.sharedMaterial.SetMatrix("_Trafo1",
bone1.renderer.localToWorldMatrix);
}
if (null != bone0 && null != bone1)
{
transform.position = 0.5 * (bone0.transform.position
+ bone1.transform.position);
transform.rotation = bone0.transform.rotation;
}
}
另外兩個遊戲物件可以是任何東西——我喜歡使用內建半透明著色器之一的立方體,這樣它們的位置和方向是可見的,但不會遮擋變形的網格。
在本教程中,與變換 _Trafo0 混合的權重設定為 gl_Vertex.z + 0.5
float weight0 = gl_Vertex.z + 0.5;
另一個權重為 1.0 - weight0。因此,具有正 gl_Vertex.z 座標的部分受 _Trafo0 的影響更大,另一部分受 _Trafo1 的影響更大。一般來說,權重取決於應用,使用者應該被允許為每個頂點指定權重。
兩個變換的應用和加權平均可以這樣寫
vec4 blendedVertex = weight0 * (_Trafo0 * gl_Vertex) + (1.0 - weight0) * (_Trafo1 * gl_Vertex);
然後,混合頂點必須乘以檢視矩陣和投影矩陣。檢視變換不可直接獲得,但可以透過將模型檢視矩陣(它是檢視矩陣和模型矩陣的乘積)乘以逆模型矩陣(它是可用的 _World2Object 乘以 unity_Scale.w,除了右下角元素,它是 1)來計算。
mat4 modelMatrixInverse = _World2Object * unity_Scale.w;
modelMatrixInverse[3][3] = 1.0;
mat4 viewMatrix = gl_ModelViewMatrix * modelMatrixInverse;
gl_Position = gl_ProjectionMatrix * viewMatrix * blendedVertex;
為了說明不同的權重,我們透過顏色的紅色分量視覺化 weight0,並透過顏色的綠色分量視覺化 1.0 - weight0(在片段著色器中設定)
color = vec4(weight0, 1.0 - weight0, 0.0, 1.0);
對於實際應用,我們還可以透過兩個相應的轉置逆模型變換變換法向量,並在片段著色器中執行逐畫素光照。
總的來說,著色器程式碼如下
Shader "GLSL shader for vertex blending" {
SubShader {
Pass {
GLSLPROGRAM
// Uniforms set by a script
uniform mat4 _Trafo0; // model transformation of bone0
uniform mat4 _Trafo1; // model transformation of bone1
// The following built-in uniforms
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform mat4 _Object2World; // model matrix
uniform vec4 unity_Scale; // w = 1/scale
uniform mat4 _World2Object; // inverse model matrix
// all but the bottom-right element should be
// multiplied with unity_Scale.w
// Varyings
varying vec4 color;
#ifdef VERTEX
void main()
{
float weight0 = gl_Vertex.z + 0.5; // depends on the mesh
vec4 blendedVertex = weight0 * (_Trafo0 * gl_Vertex)
+ (1.0 - weight0) * (_Trafo1 * gl_Vertex);
mat4 modelMatrixInverse = _World2Object * unity_Scale.w;
modelMatrixInverse[3][3] = 1.0;
mat4 viewMatrix = gl_ModelViewMatrix * modelMatrixInverse;
gl_Position =
gl_ProjectionMatrix * viewMatrix * blendedVertex;
color = vec4(weight0, 1.0 - weight0, 0.0, 1.0);
// visualize weight0 as red and weight1 as green
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor = color;
}
#endif
ENDGLSL
}
}
}
當然,這只是一個概念的說明,但它已經可以用於一些有趣的非線性形變,例如圍繞 軸的扭曲。
對於骨骼動畫中的蒙皮網格,需要更多的骨骼(即模型變換),並且每個頂點必須指定哪個骨骼(例如使用索引)以哪個權重貢獻到加權平均值。但是,Unity 在軟體中計算頂點的混合;因此,這個主題對於 Unity 程式設計師來說不太相關。
恭喜,你已經完成了另一個教程。我們已經看到了
- 如何混合由兩個模型矩陣變換的頂點。
- 如何將這種技術用於非線性變換和蒙皮網格。
如果你還想了解更多
- 關於模型變換、檢視變換和投影,你應該閱讀“頂點變換”部分中的描述。
- 關於頂點蒙皮,你可以閱讀 Aaftab Munshi、Dan Ginsburg 和 Dave Shreiner 於 2009 年由 Addison-Wesley 出版的名為“OpenGL ES 2.0 程式設計指南”的第 8 章中關於頂點蒙皮的部分。