本教程介绍顶点混合作为非线性变形的一个例子。主要的应用实际就是蒙皮网格的绘制。
虽然本教程不是基于其它任何指定的教程上的,对章节“顶点变换”的理解还是非常有用的。
在两个模型变换之间混合
大多数网格的变形不能用章节“顶点变换”中讨论的4×4矩阵的仿射变换来建模。虚构的变形场对空间的变形只是其中的一个例子。计算机图形中最重要的例子就是当连接点弯曲时网格的变形,比如肘或膝盖。
本教程介绍了实现其中一些变形的顶点混合。基本的想法是在顶点着色器中应用多重模型变换(在本教程中我们只使用两个模型变换)以及混合这些变换过的顶点,即用必须为每个顶点指定的权重计算它们的加权平均值。举例来说,在骨骼连接点附近处皮肤的变形主要是受两根骨骼交接处的位置和方向的影响。于是,这两根骨骼的位置和方向定义了两个仿射变换。皮肤上不同的点会受到这两根骨骼不同的影响:连接点处的点可能受到两根骨骼同样的影响,而在一根骨骼周围远离连接处的点会更多地受到这根骨骼的影响。通过使用这两个变换的加权平均值中的不同权重可以实现两根骨骼影响的不同强度。
为了本教程的目的,我们使用两个uniform变换float4x4 _Trafo0
和float4x4 _Trafo1
,它们由用户指定。最后一个小型Java脚本(它应该被挂载到应该被变形有网格上去,比如默认的球体)允许我们指定其它两个游戏对象并且把它们的模型变换复制到着色器的uniform中。
@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;
另一个权重就是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
}
}
}
当然,这个只是这个概念的一个说明,但是它已经可以被用在一些有趣的非线性变形中了,比如围绕着z轴的扭动。
对于骨骼动画中的蒙皮网格,更多的骨骼(即模型变换)是必须的,每个顶点必须指定哪一根骨骼(例如使用索引)对加权平均值的哪个权重有贡献。但是,Unity会在软件中计算顶点的混合;这样,这个话题就跟Unity程序员无关了。
总结
恭喜,你又完成了一章的学习。我们看到了:
- 如何混合被两个模型矩阵变换的顶点。
- 该技术是如何被应用到非线性变换和蒙皮网格上的。