跳转到内容

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

除非另有说明,本页上的所有示例源代码均授予公有领域。
华夏公益教科书