变换
我们现在已经知道如何创建对象,为它们着色和/或使用纹理赋予它们详细的外观,但它们仍然不是那么有趣,因为它们都是静态对象。我们可以尝试通过更改它们的顶点并在每一帧重新配置它们的缓冲区来使它们移动,但这很麻烦,并且会消耗相当多的处理能力。有更好的方法来变换对象,那就是使用(多个)矩阵对象。这并不意味着我们要谈论功夫和一个大型的数字人工世界。
矩阵是非常强大的数学构造,一开始看起来很吓人,但一旦你习惯了它们,它们就会被证明是非常有用的。在讨论矩阵时,我们将不得不稍微深入一些数学知识,对于更倾向于数学的读者,我将提供额外的资源以供进一步阅读。
然而,为了充分理解变换,我们首先必须在讨论矩阵之前更深入地研究向量。本章的重点是为您提供基本的数学背景,这些知识将在后面用到。如果这些主题很难,请尽力理解它们,并在需要时回到本页复习这些概念。
向量
在最基本的定义中,向量是方向,仅此而已。向量有一个方向和一个大小(也称为其强度或长度)。你可以把向量想象成藏宝图上的方向:"向左走10步,现在向北走3步,然后向右走5步";这里"左"是方向,"10步"是向量的大小。因此,藏宝图的方向包含3个向量。向量可以有任意维度,但我们通常使用2到4维。如果向量有2维,它表示平面上的一个方向(想想2D图表),如果它有3维,它可以表示3D世界中的任何方向。
下面你会看到3个向量,每个向量在2D图表中用(x,y)表示为箭头。因为在2D中显示向量比在3D中更直观,所以你可以把2D向量想象成3D向量,z坐标为0。由于向量表示方向,所以向量的原点不会改变其值。在下面的图表中,我们可以看到向量 v 和 w 即使它们的原点不同也是相等的:
当数学家描述向量时,他们通常更喜欢用一个小横杠来表示向量。然而,我们在Markdown中不能这样做,所以我们将用粗体来表示向量名称,比如 v。此外,当在公式中显示向量时,它们通常如下所示:
因为向量被指定为方向,所以有时很难将它们可视化为位置。我们基本上是将方向的原点设置为 (0,0,0) ,然后指向一个特定的方向,该方向指定了点的位置,使其成为位置向量(我们也可以指定不同的原点,然后说:"这个向量从这个原点指向空间中的那个点")。位置向量 (3,5) 将指向以 (0,0) 为原点的图表上的 (3,5) 点。因此,使用向量我们可以描述2D和3D空间中的方向和位置。
就像普通数字一样,我们也可以定义几种向量操作(其中一些你已经见过)。
标量向量操作
标量是一个单一的数字(或者如果你想留在向量空间中,它是一个只有一个分量的向量)。当向量与标量相加/相减/相乘或相除时,我们只需将向量的每个元素与标量相加/相减/相乘或相除。加法的情况如下所示:
其中 + 可以是 + ,- , • 或 ÷ ,其中 • 是乘法运算符。请记住,对于 - 和 ÷ 运算符,反向顺序是未定义的。
向量取反
对向量取反会得到一个方向相反的向量。指向东北的向量在取反后会指向西南。要对向量取反,我们在每个分量上添加一个负号(你也可以将其表示为标量-向量乘法,标量值为-1):
加法和减法
两个向量的加法定义为分量加法,即一个向量的每个分量与另一个向量的相同分量相加,如下所示:
在向量 v=(4,2) 和 k=(1,2) 上,它看起来像这样:
就像普通加法和减法一样,向量减法与加法相同,只是第二个向量被取反:
从彼此中减去两个向量会得到一个向量,该向量是两个向量指向的位置之间的差异。这在某些情况下非常有用,例如当我们需要检索一个向量,该向量是两个点之间的差异时。
长度
要检索向量的长度/大小,我们使用你可能从数学课上记得的毕达哥拉斯定理。当你将向量的单个 x 和 y 分量可视化为三角形的两条边时,向量会形成一个三角形:
其中 ||v|| 表示向量 v 的长度。这很容易扩展到3D,只需在方程中添加 z^2。
在这种情况下,向量 (4, 2) 的长度等于:
即 4.47。
还有一种特殊类型的向量,我们称之为单位向量。单位向量有一个额外的属性,即其长度正好为1。我们可以通过将向量的每个分量除以其长度来计算任何向量的单位向量 n:
我们称这为向量的归一化。单位向量用一个小帽子表示,通常更容易处理,特别是当我们只关心它们的方向时(方向不会因向量的长度变化而改变)。
向量-向量乘法
两个向量相乘有点奇怪。普通乘法在向量上并没有真正的定义,因为它没有视觉意义,但我们有两种特定的情况可以选择:一种是点积,用 v • k 表示,另一种是叉积,用 v × k 表示。
点积
两个向量的点积等于它们长度的乘积乘以它们之间角度的余弦。如果这听起来很困惑,请看它的公式:
其中角度在它们之间表示为 theta (θ)。为什么这很有趣?好吧,想象一下,如果 v 和 k 是单位向量,那么它们的长度将等于1。这将有效地将公式简化为:
现在点积 仅 定义了两个向量之间的角度。你可能还记得,余弦或 cos 函数在角度为90度时变为 0 ,在角度为0时变为 1 。这使我们可以轻松地使用点积来测试两个向量是否正交(即它们是否成直角)或平行。如果你想了解更多关于 sin 或 cosine 函数的信息,我建议你观看以下 Khan Academy 视频 关于基本三角学的内容。
你也可以计算非单位向量之间的角度,但那样的话,你必须将两个向量的长度从结果中除GLSL 还具有
mat2
和mat3
类型,允许进行类似于向量的混合操作。所有上述数学运算(如标量-矩阵乘法、矩阵-向量乘法和矩阵-矩阵乘法)都允许在矩阵类型上进行。无论何时使用特殊的矩阵操作,我们都会确保解释正在发生的事情。
我们添加了 uniform
并在将位置向量传递给 gl_Position
之前将其与变换矩阵相乘。我们的容器现在应该是其大小的一半,并且在 Z 轴上旋转了 90 度(向左倾斜)。不过,我们仍然需要将变换矩阵传递给着色器:
cs
GL.UseProgram(program);
int location = GL.GetUniformLocation(Handle, name);
GL.UniformMatrix4(location, true, ref matrix);
GL.UniformMatrix4
是一个相当标准的函数,类似于我们迄今为止看到的其他 Uniform
函数。参数如下:
- 着色器上
uniform
的位置。 - 一个布尔值,确定是否应该转置矩阵。由于 OpenTK 使用行主序,而 GLSL 通常使用列主序,因此您几乎总是希望在这里使用
true
。 - 我们想要传递的矩阵的引用。
我们创建了一个变换矩阵,在顶点着色器中声明了一个 uniform
,并将矩阵发送到着色器中,在那里我们变换我们的顶点坐标。结果应该看起来像这样:
完美!我们的容器确实向左倾斜,并且大小是原来的一半,因此变换是成功的。
如果您没有得到正确的结果,或者您在其他地方遇到了问题,请查看源代码和更新后的着色器类。
在下一个教程中,我们将讨论如何使用矩阵来为我们的顶点定义不同的坐标空间。这将是我们迈向实时 3D 图形的第一步!