跳轉到內容

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

來自華夏公益教科書,自由的教科書,自由的世界
一個虛構的翹曲場對空間進行變形。這是一種不能用線性變換建模的非線性變形的例子。

本教程介紹頂點混合作為非線性變形的例子。主要的應用是渲染蒙皮網格。

雖然本教程不是基於任何其他特定教程,但對“頂點變換”一節的瞭解非常有用。

兩種模型變換之間的混合

[編輯 | 編輯原始碼]

大多數網格的變形不能用“頂點變換”一節中討論的 4×4 矩陣的仿射變換來建模。虛構翹曲場對空間的變形就是一個例子。在計算機圖形學中,一個更重要的例子是關節彎曲時網格的變形,例如肘部或膝蓋。

本教程介紹頂點混合來實現這些變形中的部分。基本思想是在頂點著色器中應用多個模型變換(在本教程中,我們只使用兩個模型變換),然後混合變換後的頂點,即用必須為每個頂點指定的權重計算它們的加權平均值。例如,骨骼關節附近皮膚的變形主要受關節處兩個(剛性)骨骼的位置和方向的影響。因此,兩個骨骼的位置和方向定義了兩個仿射變換。皮膚上的不同點受兩個骨骼的影響不同:關節處的點可能受兩個骨骼的影響相同,而遠離關節的一個骨骼周圍的點更受該骨骼的影響而不是另一個。這兩種骨骼影響力的不同強度可以透過在兩個變換的加權平均值中使用不同的權重來實現。

在本教程中,我們使用兩個統一變換float4x4 _Trafo0float4x4 _Trafo1,它們由使用者指定。為此,一個小型的 JavaScript(應該附加到要變形的網格,例如預設球體)允許我們指定另外兩個遊戲物件,並將它們模型變換複製到著色器的制服中

@script ExecuteInEditMode()

public var bone0 : GameObject;
public var bone1 : GameObject;

function Update () 
{
   if (null != bone0)
   {
      GetComponent(Renderer).sharedMaterial.SetMatrix("_Trafo0", 
         bone0.GetComponent(Renderer).localToWorldMatrix);
   }
   if (null != bone1)
   {
      GetComponent(Renderer).sharedMaterial.SetMatrix("_Trafo1", 
         bone1.GetComponent(Renderer).localToWorldMatrix);
   }
   if (null != bone0 && null != bone1)
   {
      transform.position = 0.5 * (bone0.transform.position 
         + bone1.transform.position);
      transform.rotation = bone0.transform.rotation;
   }
}

在 C# 中,指令碼(名為“MyClass”)看起來像這樣

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class MyClass : MonoBehaviour
{
   public GameObject bone0;
   public GameObject bone1;
   
   void Update () 
   {
      if (null != bone0)
      {
         GetComponent<Renderer>().sharedMaterial.SetMatrix("_Trafo0", 
            bone0.GetComponent<Renderer>().localToWorldMatrix);
      }
      if (null != bone1)
      {
         GetComponent<Renderer>().sharedMaterial.SetMatrix("_Trafo1", 
            bone1.GetComponent<Renderer>().localToWorldMatrix);
      } 
      if (null != bone0 && null != bone1)
      {
         transform.position = 0.5f * (bone0.transform.position 
            + bone1.transform.position);
         transform.rotation = bone0.transform.rotation;
      }
   }
}

另外兩個遊戲物件可以是任何東西——我喜歡用內建的半透明著色器中的一個立方體,這樣它們的位置和方向是可見的,但不會遮擋變形的網格。

在本教程中,用於與變換_Trafo0混合的權重設定為input.vertex.z + 0.5

            float weight0 = input.vertex.z + 0.5;</code>

另一個權重是1.0 - weight0。因此,具有正input.vertex.z座標的部分更多地受到_Trafo0的影響,而另一部分更多地受到_Trafo1的影響。一般來說,權重是應用程式相關的,使用者應該能夠為每個頂點指定權重。

兩個變換的應用和加權平均值可以用這種方式寫出來

            float4 blendedVertex = 
               weight0 * mul(_Trafo0, input.vertex) 
               + (1.0 - weight0) * mul(_Trafo1, input.vertex);

然後,混合的頂點必須乘以檢視矩陣和投影矩陣。這兩個矩陣的乘積可用作UNITY_MATRIX_VP

            output.pos = mul(UNITY_MATRIX_VP, blendedVertex);

為了說明不同的權重,我們用紅色的分量視覺化weight0,用綠色的分量視覺化1.0 - weight0(在片段著色器中設定)

            output.col = float4(weight0, 1.0 - weight0, 0.0, 1.0);

對於實際應用,我們還可以透過兩個相應的轉置逆模型變換來變換法向量,並在片段著色器中進行逐畫素光照。

完整的著色器程式碼

[編輯 | 編輯原始碼]

總而言之,著色器程式碼看起來像這樣

Shader "Cg shader for vertex blending" {
   SubShader {
      Pass {   
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
 
         // Uniforms set by a script
         uniform float4x4 _Trafo0; // model transformation of bone0
         uniform float4x4 _Trafo1; // model transformation of bone1
 
         struct vertexInput {
            float4 vertex : POSITION;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 col : COLOR;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float weight0 = input.vertex.z + 0.5; 
               // depends on the mesh
            float4 blendedVertex = 
               weight0 * mul(_Trafo0, input.vertex) 
               + (1.0 - weight0) * mul(_Trafo1, input.vertex);
 
            output.pos = mul(UNITY_MATRIX_VP, blendedVertex);
 
            output.col = float4(weight0, 1.0 - weight0, 0.0, 1.0); 
               // visualize weight0 as red and weight1 as green
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            return input.col;
         }
 
         ENDCG
      }
   }
}

當然,這只是一個概念的說明,但它已經可以用於一些有趣的非線性變形,例如圍繞軸的扭曲。

對於骨骼動畫中的蒙皮網格,需要更多的骨骼(即模型變換),每個頂點都必須指定哪個骨骼(例如使用索引)以哪個權重參與加權平均。但是,Unity 在軟體中計算頂點的混合;因此,這個主題對 Unity 程式設計師來說不那麼重要。

恭喜你,你已經完成了另一個教程。我們已經看到了

  • 如何混合由兩個模型矩陣變換的頂點。
  • 這種技術如何用於非線性變換和蒙皮網格。

進一步閱讀

[編輯 | 編輯原始碼]

如果你還想了解更多

  • 關於模型變換、檢視變換和投影,你應該閱讀“頂點變換”一節中的描述。
  • 關於頂點蒙皮,你可以閱讀 Aaftab Munshi、Dan Ginsburg 和 Dave Shreiner 於 2009 年由 Addison-Wesley 出版 的“OpenGL ES 2.0 程式設計指南”第 8 章中關於頂點蒙皮的部分。

< Cg 程式設計/Unity

除非另有說明,本頁上的所有示例原始碼均授予公有領域。
華夏公益教科書