零基础学图形学(8) 几何知识——点和向量的变换

(1)点的变换

我们已经介绍完了如何使用矩阵来进行点的变换所需要写代码的知识。但是尽管平移被看做是最简单的应用于电商的线性操作,在之前的章节中我们没有提到。因为平移会用到矩阵的乘法知识,我们需要对点的结构做点变化,那可能会让你感到迷惑。

像我们在之前两篇文章中提到的矩阵和矩阵的乘法,只有当两个矩阵的大小满足一定的规则之后才可以进行,也就是说需要m x p和p x n的大小。让我们从一个3 x 3的相似矩阵开始,我们知道一个点和这个矩阵相乘的话它的坐标不会改变。让我们看看我们需要改变什么来处理平移。一个点的平移无非就是对这个点的每一个坐标都加上一定的数值(这些数字可以是正也可以是负)。比如如果我们想将点(1, 1, 1)移动到点(2,3,4)中,我们需要对点的x, y,z坐标分别加上1, 2, 3的数值。它是很简单的,记住从现在开始,我们将点和向量看做是一个1 × 3的矩阵。

现在我们使用矩阵考虑点的变换:

我们可以将一个旋转矩阵延伸,因此它也能处理平移。因此我们需要第四个参数来处理平移,需要下面这个公式。

记住线面我们需要想到一个矩阵,它可以进行缩放,旋转和平移操作。因此,我们需获取Tx, Ty, Tz的值去满足矩阵的点乘。现在看看第一行,去计算x',我们只需要矩阵的第一行参数。如果这一行有四个参数,那么Tx就是M30,其它两个也是如此替换。

但是这意味这我们的矩阵的大小是4 x 3而不再是3 x 3.这是真歌曲的,我们说矩阵可以有任意的大小。但是我们也知道矩阵的乘法只有在参数的大小满足的条件下进行。但是现在我们让一个1 x 3的点和一个 4 × 3的矩阵相乘,从理论上来说这是不可能的。我们应该怎么办呢?这个答案是简单的,我们需要对点增加额外的一列让它编程1 x 4的矩阵,同时设置第四个的参数为1.我们现在的点的坐标是(x, y, z, 1).在计算机图形学中,这个点被称作是homogenous point(其次坐标点).用这个点我们很容易将平移加入到矩阵中,可以看看有如下的方程:

这只是理论上的,为了进行平移,缩放和旋转在一个矩阵上,我们需要使用齐次坐标系统来处理点。但是因为第三个值始终是1, 我们从不在代码中定义它。我们仅仅知识定义x, y, z的值的坐标以及假定这里有第四个点,因此点和矩阵的代码看起来是下面的样子:

现在我们的矩阵是4 x 3的矩阵,我们也许想知道,我们怎么从4 x 3的矩阵中得到我们最终的 4 x 4的矩阵呢?4 x 4的矩阵才是在CG中使用最多的。第四列在透视投射(perspective projection)中有很大的作用。以及其它不大常用的变换中有作用,比如切变,但是通常地将它设置成为(0, 0, 0, 1)。如果这些参数和默认的参数有不同会产生什么影响呢?在我们能够回答这个问题之前,我们需要学习一些关于其次坐标点的知识。

(2)齐次坐标点的技巧

让一个点成为其次坐标点可以让一个点和一个[4 x 4]的矩阵相乘。但是,在代码中,这只能隐式地完成,因为我们已经解释过了,w一直都是1.我们的点C++类,不会定义有是个浮点数据的坐标,它只有x, y, z三个。从技术上来讲,如果我们让一个点和一个[4 x 4]的矩阵相乘,这个w坐标会和矩阵的第四列相乘。但是,就如我们之前提到的,这一列经常被设置成为(0, 0, 0, 1)。这种情况下,变换点的w的左边应该是1.(w'=x*0+y*0+z*0+w(=1)*1=1)以及这个的x', y', z'的坐标可以被直接使用。但是就像我们之前提到的,在处理透视投射的时候这个第四个坐标不总是设置为(0, 0,0, 1).在那些特殊的情况下,w'的值可能不为1,但是这个值可以对笛卡尔的点产生作用,我们需要将w' normalize为1.就是让其它的坐标值(x', y', z')除以这个w'的值,在伪代码中,我们可以表现成如下的形式:

P'.x = P.x * M00 + P.y * M10 + P.z * M20 + M30;
P'.y = P.x * M01 + P.y * M11 + P.z * M21 + M31;
P'.z = P.x * M02 + P.y * M12 + P.z * M22 + M32;
w' = P.x * M03 + P.y * M13 + P.z * M23 + M33;
if (w' != 1 && w' != 0) {
P'.x /= w', P'.y /= w', P'.z /= w';
} 
正如你所看到的,我们不需要在点的类型中声明w的坐标。我们可以直接计算w'的值,因为齐次坐标中我们已经设置了最后的坐标为1.但是,在投射矩阵中得到的w'的值可能不为1,在这种特殊的情况下,我们需要将那最后的一点重新设置为1,一旦完成这个,我们可以让我们点重新在笛卡尔坐标系统中使用。

所有你 需要记住的是,你永远也不需要关注齐次坐标,除了一个点和投射矩阵相乘之外。但是,如果你在光线追踪上工作的话可能不会碰到这个问题,因为这种特殊的类型矩阵没有在光线追踪上使用。如果你还是挣扎在理解w的坐标上,以及它有什么用,那么请阅读3D基础渲染的透视矩阵和正交矩阵。你会学习到一个3D的点如何投射到一个图像平面上,那么齐次坐标点会更容易理解一些。

当提到用C++方法实现这个函数的时候,这里有两种学派处理这个问题。一些开发者喜欢在点和矩阵的乘法中总是计算w'的值,然后如果w的值不为1的话,所有的坐标都会除以这个值。但是,这仅仅只是在投射矩阵中有用,这个在光线追踪中用得很少。99%的例子中,去计算w'的值,然后比较它是否等于1,是一种对cpu的浪费。另外一种观点是忽略掉w和w',认为点和矩阵的乘法的时候第四列的数值总是(0, 0, 0, 1).当处理特殊的投射矩阵的时候,你会想起其他的另外一个特殊的函数,它会计算w'的值,然后x', y', z'会除以这个w'的值。你可以选择一个通用的方法但是有没有更多的优化的需要两个函数而不是一个。为了简洁,我们会提供第一种方案的实现:

写在函数vec3类的里面,其中函数后的const是防止改变引用对象的。

    void multVecMatrix(const Vec3<T> &src, Vec3<T> &dst) const {
        dst.x = src.x * m[0][0] + src.y * m[1][0] + src.z * m[2][0] + m[3][0];
        dst.y = src.x * m[0][1] + src.y * m[1][1] + src.z * m[2][1] + m[3][1];
        dst.z = src.x * m[0][2] + src.y * m[1][2] + src.z * m[2][2] + m[3][2];
        T w = src.x * m[0][3] + src.y * m[1][3] + src.z * m[2][3] + m[3][3];
        if (w != 1 && w != 0) {
            dst.x = x / w;
            dst.y = y / w;
            dst.z = z / w;
        }
    }
(3)向量的变换

向量从某种程度上来说,比点的变换更简单。向量,就像我们会签所说的,代表一个方向,而点代表的是空间位置。向量不需要平移,因为他们的位置实际上是没有意义的。对于向量,我们支队他的方向感兴趣,以及它的长度,我们有时候需要用他来解决一些介个的或者阴影的问题。向量可以像点那样平移,但是我们我们移除了这部分的代码,所以向量的变换看起来像下面的样子(可以和点的变换做比较):

V'.x = V.x * M00 + V.y * M10 + V.z * M20;
V'.y = V.x * M01 + V.y * M11 + V.z * M21;
V'.z = V.x * M02 + V.y * M12 + V.z * M22; 

    void multiDirMatrix(const Vec3<T> &src, Vec3<T> &dst) const {
        dst.x = src.x * m[0][0] + src.y * m[1][0] + src.z * m[2][0];
        dst.y = src.x * m[0][1] + src.y * m[1][1] + src.z * m[2][1];
        dst.z = src.x * m[0][2] + src.y * m[1][2] + src.z * m[2][2];
    }
(4)法向量的变换

它听起来很奇怪,你也许会认为法向量会和向量一样使用相同的代码进行变换。实际上它并没有我们想象中那么简单,我们会在法向量的变换中介绍。

(5)总结

在这章中我们已经学习使用了[4 x 4]矩阵而不是[3 x 3]矩阵。参数c30, c31, c32代表着平移的数值。现在这个矩阵的大小为[4 x 4],我们需要通过增加额外的坐标的方法延伸点的大小。我们可以通过隐式第将点变成齐次坐标点做到这点,但是仍然在笛卡尔坐标系中使用它。我们需要确保第四个坐标值始终为1.大部分的时间我们用于平移变换的矩阵的第四列的数值是(0, 0, 0, 1),使用这些数,那么w'的数值也总是1. 但是在特殊的例子中(投射矩阵,剪切矩阵)那么w'的值可能不为1,这种情况下我们需要单位它,需要x', y', z'都除以w'。

矩阵不仅仅存储中变换信息。你也可以使用欧拉公司进行旋转。这个只要定义了一个旋转轴和一个旋转的角度。你也可以使用Benjamin Olinde Rodrigues.给定一个轴r和角度Q,和一个点p,我们可以通过下面的等式获得旋转的信息。


两种方法都在CG中使用解决相关问题。在CG中旋转也可以使用quaternions,当旋转的角度超过360度的时候矩阵他们本身有些限制。这可能导致万向节死锁(gimbal lock).矩阵也很难进行运动物体的模糊渲染。对于这些特殊的原因,quaternions是个更好的方式表达那些难以理解的问题。关于quanternions的问题有专门的章节讲到。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值