计算机图形学:点、向量、法线

简介

本文为Scratchapixel文章的学习性翻译。
不保证翻译的准确性,如遇到理解问题,请参考原文。
官方地址|本章地址

索引

Lesson:Mathematics and Physics for Computer Graphics
课程:计算机图形学中的数学和物理
Chapters:Geometry
章节:几何学

几何学目录

  • Points, Vectors and Normals 点、向量、法线
  • Coordinate Systems 坐标系
  • Math Operations on Points and Vectors 点和向量的数学运算
  • Matrices 矩阵
  • How Does Matrix Work: Part 1 矩阵如何工作 第一部分
  • How Does Matrix Work: Part 2 矩阵如何工作 第二部分
  • Transforming Points and Vectors 转换点和向量
  • Row Major vs Column Major Vector 行主序 vs 列主序
  • Matrix Operations 矩阵运算
  • Spherical Coordinates and Trigonometric Functions 球坐标和三角函数
  • Creating an Orientation Matrix or Local Coordinate System 创建一个方向矩阵或局部坐标系
  • Transforming Normals 法线变换
  • Source Code 源代码

正文

关键字:向量,点,矩阵,法线,变换,笛卡尔坐标系,笛卡尔坐标,球坐标,坐标系。

“几何学是数学的一个分支,主要研究形状、大小、图形的相对位置和空间性质等问题。”

一句忠告

这门课程对大多数读者来说可能是冗长且乏味的。如果你是计算机图形学这个领域的初学者,请花一些时间仔细阅读它。完全的理解CG pipeline中的这一部分是至关重要的,并且可以在之后的学习中为你省下很多时间。

几何介绍

计算机图形中的点(Points)、向量(vectors)、矩阵(matrices)和法线(normals)就好比文学中的字母;因此大多数的CG书籍中都以线性代数和几何学作为开始的章节。然而,对于大多数想要学习图像编程的人来说,在学习如何制作图像(making images)之前就呈现大量的数学知识是十分令人不悦的。如果因为你不喜欢数学或者说根本就不知道矩阵是什么从而觉得你不适合CG编程,请不要现在就放弃。

我们将会从“3D渲染基础”的几堂课开始,这些课程因为某些原因不会要求任何线性代数的先备知识。尽管这是一种相当非常规的CG编程技术教授方式,但我们相信从一些实用且有趣的部分开始更加令人兴奋:例如,光线追踪器的入门,这一部分只要求非常少的数学知识和编程知识。编写一个渲染器是学习数学的一种更加令人兴奋且更有价值的方式,因为你可以逐步看到某些东西是如何被用来产生一个具体的结果的(即你的最终图像)。话虽如此,但点、向量和矩阵在制作CG图像的过程中起着重要的作用;所以我们将在几乎每一课中大量地使用它们。

在这节课中,你将会学会这些结构体是什么,它们怎么工作和可以操控它们的各种方法。这节课同样也会解释CG研究人员们数年来在解决他们的问题和编写他们的代码时所使用的线性代数中的不同约定(conventions)。你需要了解这些(conventions),因为它们通常在书中没有提到(在网上也很少提到)。这些约定(conventions)是非常重要的,在你可以阅读或使用别的开发者的代码或方法是时候,你必须首先检查他们使用了什么约定(conventions)。

在我们开始之前做一个简短的说明。如果你是一个数学纯粹主义者(mathematical purist),当你看到这里的解释在技术上与线性代数无关时可能会觉得奇怪。我们像要保持这门课程的宽泛性,同时包含一些在CG中常用的简单的数学方法,可能这些方法只与向量和矩阵有松散的联系。比如说,一个点,从数学上来讲,与线性代数(数学的一个分支,只涉及向量)无关。我们选择提到点是因为它们在CG中非常常见(并且可以使用线性代数的相同数学技术来操纵它们)。如果你还没有理解点和向量的区别,不用担心,我们在这一章中将会经常提到它们。

什么是线性代数?向量的介绍

所以线性代数到底是什么?我们这节课要学什么?像我们上一节提到的那样,线性代数是一门研究向量的数学分支。现在你可能会问,“嘛是向量?它在CG中有嘛用?”我们不会讲太多细节,但向量可以表示为一组数字。这组数字可以假设为任意你想要的长度,当然有时候也可以称之为做数学上元组(tuple)。如果我们想要一个特定长度的向量,我们可以称之为N元组(n-tuple),其中N表示向量的元素数量。下面这个例子就是一个含有六个元素的向量。

$V=\big(a,b,c,d,e,f\big)$

其中a,b,c,d,e,f都是一个实数

将这些数字组合在一起的想法是,它们在一个问题的语境中共同的代表了另一个值或概念。比如,在计算机图形学中,向量在空间中既可以被用来表示坐标,也可被用来表示方向。我们还可以通过一系列的操作以一种非常强大和紧凑(compact)的方法转换(或修改)这些向量。向量内容的转换过程是通过所谓的 线性转换(linear transformation 来实现的。我们将会在下一节中花更多的时间来谈论转换(transformations);现在,最重要的是知道它们非常有用就好了。

点和向量

点(Point)向量(Vector) 这两个术语在许多科学领域中都有不同的用法。在这一节中,我们将会讲解这两个术语于我们的教程和计算机图形学的关系。

图1:点描述位置。向量看作方向。

如图,在三维空间内,一个就是一个位置。另一方面,在三维空间内,一个向量通常代表一个方向(和一些相应的数值或大小)。向量可以看作是一个指向不同方向的箭头。三维的向量当然是相似的,因为它们都用上面提到的元组符号表示。

$V=\big(x,y,z\big)$

其中(x, y, z)也是实数。
记住,当与数学家或物理学家交谈时,他们对向量或点的理解可能会更加笼统;向量和点不一定仅限于我们在CG中使用。对于他们来说,一个向量可以是任意大小的,甚至是无限大小的(也就是说它可以包含任意数量的数字)。

在本节的最后我们简要的提一下齐次点(homogeneous points)。有时候为了数学上的方便,有必要添加第四个元素。下面给出一个具有齐次坐标点的例子:

$P_H=(x,y,z,w)$

齐次点用于点与矩阵相乘。现在不用太在意它。我们现在只是提一下,因为它们有时会出现在一些资料中,可能会让读者感到困惑。我们将在本课后面详细解释。

转换的简单简介

你可能想知道线性变换(linear transformation)到底是如何影响点和向量的。其实非常简单。我们在CG中对点进行的最常见的操作之一就是简单地在空间中移动它们。这种变换(transformation)可以更具体的称之为平移(translation),它在渲染过程中扮演者重要的角色。

这个平移操作仅仅是 原点(可以看作是一个输入坐标点) 的一个线性变换。对向量而言,平移 是没有意义的。这是因为向量从哪里开始是不重要的;不管位置如何所有的“箭头”都是一样的长度,指向一样的方向,也就是说是等价的。相对的,我们通常对向量使用另一个线性变换:旋转(rotation)。还有很多其他的操作,但是现在我们只考虑点的平移和向量的旋转。

$P\rightarrow Translate \rightarrow P_T$
$V\rightarrow Rotate \rightarrow V_T$

下标字母T代表 变换(transformed)

你可能已经注意到,到目前为止,我们还没有讨论向量的长度或大小的意义。实际上,箭头的长度在CG中非常重要。当向量的长度为确定的值1时,我们可以说这个向量是normalised(单词译作:正规化的,标准化的 下翻译 归一化)(你会经常听到和读到这个术语)。向量归一化的过程包括改变向量使其长度变为1,但是它的方向保持不变。大多时候我们需要对向量进行归一化处理。然而,在某些情况下,最好不要对其进行归一化,因为向量的长度可能有意义。

举个例子,想象一下你从$A$点连线到$B$点,画的这条线就是一个向量,它表示点$B$相对于点$A$的位置。换一种方式说,如果你站在了$A$点它则表示给出了$B$点的方向。这个向量的长度表示从$A$到$B$的距离。某些算法有时需要这个距离。

归一化向量往往是Bugs的来源,每当声明一个向量的时候(或使用的时候),我们建议你经常下意识的问一下自己是否这个向量 需要/不需要 被归一化。

法线

法线是计算机图形学(和几何学)中使用的技术术语,用于描述几何对象在该表面上一点的表面方向。从技术上讲,$P$点所在表面上的表面法线,可以看作是垂直于$P$点的相切平面的向量。法线在 Shading(阴影着色) 中扮演着重要的角色,它们被用来计算物体的亮度。(详情请见后面课程《光与影》).

法线可以被认为成向量,但是有一点需要注意:它们的变换方式与向量不同。这是我们花时间来区分它们的主要原因之一。你可以在“法线变换”一章中找到关于这个主题的更多信息。目前,重要的是理解它们是什么。

从理论到C++

在我们的c++代码中,我们不会区分点、向量和法线;我们用一个Vec3类来表示这三者。Vec3(一个类模板,这样我们可以根据需要创建float型、int型或double型) 一些开发人员喜欢将它们区分开来,这显然限制了犯错的可能性。然而,根据经验,我们发现只处理一个惟一的类更加高效(更少的代码)。当然,还必须根据我们处理的Vec3是表示点,向量还是法线,来谨慎的调用一些特定的函数。你可能还记得,这将在我们使用转换时特别重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T> 
class Vec3
{
public:
// 3 most basic ways of initializing a vector
// 3种最基础的初始化向量的方式
Vec3() : x(T(0)), y(T(0)), z(T(0)) {}
Vec3(const T &xx) : x(xx), y(xx), z(xx) {}
Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
T x, y, z;
};

typedef Vec3<float> Vec3f;

Vec3<float> a;
Vec3f b;

总结

从第一章开始,你应该记住,数学上一个向量可以是任意维数。然而在CG中,我们使用了一个更具体的定义:向量是三维空间中的方向(因此用三个数字表示)另外,我们以点来表示位置(同样在三维空间中也用三个数字表示)齐次点用四个数字表示,但它是一个特殊的例子,我们以后会学习。

点和向量可以用线性变换进行变换。

您将看到经常使用术语线性变换。 如果一条线变换后仍为一条线,那么我们说的是线性变换(乘以矩阵就是线性变换)。视频:什么是线性变换?

这种转换的典型例子是点的 平移 和向量的 旋转 。向量的长度可以设置为1,这也就是我们说的 归一化(normalised) 。向量的长度(在归一化之前)表示两点之间的距离,有时在某些算法中是必需的。因此,开发人员必须谨慎选择何时归一化一个向量,并且知道为什么要归一化。

接下来是什么?

一件重要的事情我们还没有解释,那就是点和向量中定义的3个的数字分别代表什么。这些数字表示点(在2D或3D空间中)相对于参考点(reference)(有时也称为原点)的坐标。这个reference,我们在技术上称之为坐标系统,是我们下一章的主题。