4

TinyRenderer学习笔记04:透视投影

 2 years ago
source link: https://direct5dom.github.io/2022/09/03/TinyRenderer%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B004/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在之前的学习中,我们绘制的人头模型都是以正交投影的方式绘制的。但是现实空间中是有透视关系存在的,这就是我们今天所努力的目标。

二维线性变换

平面上的线性变换可以用对应的矩阵来表示。

如果我们取一个点(x,y),那么它的变换可以写成如下形式:

最简单的(非退化)变换是恒等式,这时点没有任何移动:

矩阵的对角线系数提供了沿坐标轴的缩放,让我们来说明一下,如果我们进行以下转换:

这时,白色多边形将被转换为黄色多边形:

红色和绿色线段分别为x和y的单位长度向量

矩阵很方便。我们可以这样表达整数个对象的变换:

这个2x5矩阵实际上就是上面提到的多边形顶点。我们把所有顶点放在一个数组中,乘以变换矩阵,就得到了变换后的物体。

虽然矩阵很方便,但是它也存在着一定问题。在一个场景中,我们需要通过连续的许多变换来变换我们的对象:

vec2 foo(vec2 p)$return vec2(ax+by, cx+dy);
vec2 bar(vec2 p)$return vec2(ex+fy, gx+hy);
// [..]
for$(each p in object)${
p = foo(bar(p));
}

这段代码对我们对象的每个顶点都进行了两次线性变换,而我们经常以百万为单位计算这些顶点。

在一行中进行几十次变换的情况并不罕见,这将导致数千万次的操作,计算开销真的很大。在矩阵中,我们可以预先将所有的变换矩阵相乘,并对我们的对象进行一次变换。

让我们继续来看看其他系数对对象的影响:

得到的结果:

这是一个沿x轴的剪切变换。所以我们也能类比出来另一个反对角线元素是沿y轴的剪切变换。

因此在一个平面上有两种基本的线性变换:

  • 缩放
  • 剪切

但是直觉告诉我们,还应该有一个“旋转变换”。

其实,任何旋转(围绕原点)都可以表示为三个剪切的复合动作,

这里,白色物体先被剪切为红色物体,然后被剪切为绿色物体,最后被剪切为蓝色物体。

不过这太复杂了,其实可以直接写出旋转矩阵:

我们可以将矩阵按任意顺序相乘。但是请注意,矩阵的相乘不可交换

从图形上也很好理解:剪切一个物体然后再旋转它,与旋转它然后再剪切它是不一样的。

二维仿射变换

我们说平面上任何线性变换都是缩放和剪切的组合。这意味着我们可以靠这两个变换完成我们任何想要的效果——除了平移。

平移并不是线性的,让我们尝试在执行线性变换后添加平移:

现在我们得到了一个“完美”矩阵,它可以完成:缩放、剪切、旋转和平移。

但是我们可能会遇到多次变换的结果,他们组合起来就像这样:

即使只是一次组合,事情就已经足够糟糕了,更别提我们还需要更多次组合变换。

试着想象一下,给我们的变换增加一列一行,使其成为3x3矩阵,并在我们要变换的向量上附加一个永远等于1的坐标。

如果我们把这个矩阵和增量为1的向量相乘,就会得到另一个最后一个分量为1的向量,但其他两个分量的形状正好是我们想要的形状。

这个方法真的很简单,平行平移在二维空间中不是线性的,所以我们把我们的二维空间嵌入到三维空间(增加1的分量)。这意味着我们的二维空间是三维空间中的平面z=1。然后我们进行了一个线性的三维变换,并将结果投射到我们的二维平面上。平行平移没有变成线性平移,但是这真的很简单。

至于我们如何将三维投射到二维平面:

Z=0的极限状态

当z=0时,矩阵:
[xy0]
变成了一个无限长的向量,而不是点。

当我们想要组合变换的时候,例如我们需要围绕一个点(x0,y0)旋转一个二维物体,我们只需要:

在三维空间中,这种序列会更长一些,但是道理是一样的:我们只需要用很少的基础变换,就可以表示任意的组合动作。

3x3矩阵的底行

我们试着改变3x3矩阵的底行,看看会有什么变化:

这是原始图像:

这是变换后的效果:

在这里我们实际上改变了透视关系,

我们以二维的角度去看(就像ybuffer那样),将摄像机放在(5,0),将二维对象投影到x=0上。

我们使用中心投影,摄像机指向原点,为了找到投影,我们可以绘制一些追踪直线:

然后在不改变这些黄色线段的基础上,用变换后的对象替换掉原始对象:

靠近摄像机的垂直线被拉伸,远离摄像机的垂直线被缩小。如果我们正确的选择系数,那我们就会得到一个透视(中心)投影的图像。

这个魔法矩阵以很简单的方式完成了复杂的工作:

全3D下的情况

我们刚才都在二维空间下进行操作,现在我们需要将其类比到三维空间。

类比二维仿射变换,我们需要将一个点(x,y,z)增维到(x,y,z,1),然后在四维空间中进行变换并投射回三维:

投影到三维后:

让我们暂时搁置这个结果,回到中心投影的定义上来。

一个很简单的理解方式:给定一个点P=(x,y,z),我们想要把它投影到平面z=0上,相机在z轴上的点(0,0,c)上:

三角形ABC和ODC是相似的。这意味着我们可以写出以下内容:
|AB||AC|=|OD||OC|→xC−z=x′c
也就是:

同理可证:

这和我们刚才说的搁置的结果很相似,但是我们通过单个矩阵乘法就得到了结果。我们得到了系数的规律:
r=−1C

全三维的情况下:

我们以矩阵中的方式变换了我们的对象,获得了透视的效果:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK