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 章中关于顶点蒙皮的部分。