世界坐标系到观察坐标系的变换步骤_《坐标系相关》3.0投影变换-透视投影矩阵到Clip空间...

投影变换是整个渲染管线里,设计得最复杂的,也最巧妙的一次变换。

其实基本理解了这些变换,对整个渲染管线就有了大概的认识了。

毕竟是理科生,所以这一篇不直接给最终矩阵,我们一步步来讨论,

为什么投影变换矩阵要这么设计?

为什么裁剪在透视除法之前?

为什么观察坐标系是右手坐标系?

如何实现的近大远小?

如何实现的erlay-z?

深入探索透视投影变换_大数据_Popy007(Twinsen)的专栏-CSDN博客​blog.csdn.net
bbb0e0b890ae2d4596170d7b22a9f214.png

上面这篇博客是我认为写的最好的关于透视投影变换的文章。本文很多内容也直接抄过来了。既然有这篇文章,为什么我还要写一篇,主要是觉得部分推导顺序,以及关于 裁剪在透视除法之前等问题的原因不准确,所以放在后面讨论。

1.投影变换的目的

坐标转换到观察空间后,由于直接使用摄像机的平截头体进行裁剪比较复杂(平截头体的边界方程求交困难),所以需要将其转化到裁剪空间(Clip空间 )。

从观察空间到裁剪空间的变换叫做投影变换

裁剪空间变换的思路是,对平截头体进行缩放,使近裁剪面和远裁剪面变成正方形,使坐标的w分量表示裁剪范围,此时,只需要简单的比较x,y,z和w分量的大小即可裁剪图元。

虽然叫做投影变换,但是投影变换并没有进行真正的投影。

这一篇主要讲透视投影

2.透视投影变换分2步:

1. 从Frustum内一点投影到近剪裁平面

2. 由近剪裁平面缩放成规则观察体(Canonical View Volume),CVV空间,得到clip坐标

(此时没有除以W变成3D坐标,是齐次坐标)

相机空间中的顶点,如果在视锥体中,则变换后就在CVV中。如果在视锥体外,变换后就在CVV外。CVV本身的规则性对于多边形的裁剪很有利。

3.透视投影推导:

投影坐标系有两种矩阵:透视矩阵 和 正交矩阵

我们选择OpenGL的透视投影变换进行分析:

3747c55ddecf4d14b85da5cdb77070d7.png

第一步:投影到近剪裁平面

我们先从一个方向考察投影关系

fea229c0e26cf2603b08ed4eed8de75b.png
右手坐标系中顶点在相机空间中的情形。

设P(x,z)是经过相机变换之后的点,

视锥体由eye--眼睛位置,

np--近裁剪平面,

fp--远裁剪平面组成。

N是眼睛到近裁剪平面的距离,

F是眼睛到远裁剪平面的距离。

选择近裁剪平面作为投影平面。设P"(x",z")是投影之后的点,则有z’ = -N。通过相似三角形性质:

X/X' = Z/Z' = Z/ -N

所以

X' = -N * X/Z

同理,有

Y' = -N * Y/Z

这样,我们便得到了P投影后的点P"

p' = ( -N *x/z -N *y/z -N)

从上面可以看出,当Frustum内的点投影到近剪裁平面的时候,投影的结果z"始终等于-N,在投影面上。实际上,z"对于投影后的P"已经没有意义了。

3.1 充分利用Z’值,一步步对Z’改造

0)后面在进入片元操作之前还有erlay-Z测试,有必要把投影之前的z保存下来,方便后面使用。

由第一幅图可知,所有位于线段p'p上的点,最终都会投影到p'点,那么如果这条线段上有多个点,如何确定最终保留哪一个呢?当然是离观察这最近的这个了,也就是深度值(z值)最小的。所以z'坐标需要保存p点的z值。

那么 p' = ( -N *x/z -N *y/z Z)

又因为在光栅化之前,我们需要对z坐标进行插值

1)后面投影之后的光栅化阶段,要通过x'和y'对z进行线性插值,以求出三角形内部片元的z,进行z缓冲深度测试。

从X' = -N * X/Z 可以看出 ,投影后的x'和y',与z不是线性关系,与1/z才是线性关系。所以用1/z的线性组合值和x'、y'一起插值才是正确的。

2)同时为了保证近处精度更高,我们使用Z坐标的的倒数

z' = 1/z;

3)P"的3个代数分量统一地除以分母-z,易于使用齐次坐标变为普通坐标来完成。

所以我们写作: z' = -1/z;

所以:我们暂时得到的新的点P" 表示为:

P' = ( -N *x/z -N *y/z -1/z)

第2步:缩放到CVV空间

CVV是一个x,y,z的范围都为[-1,1]的规则体,便于进行多边形裁剪。

我们先不管x和y,先映射Z到[-1,1]之间。

要进行映射,常用的主要是wrap和线性映射

我们直接使用线性公式:

我们对 -1/z 适当的选择系数a和b,也就是

a + b*( -1/z )

这个式子在z = -N的时候值为-1,而在z = -F的时候值为1,从而在z方向上构建CVV。

所以最终记录的Pz值:

z' = -(a + b/z)

所以:我们得到的新的点 暂时表示为:

2d41013a47058011635422922eec38fb.png

3)我们为了在GPU中运算更快,同时能合并之前的矩阵变换,所以我们最终要使用矩阵形式来做透视变换。 所以这一步还是要转为齐次坐标

所以:

p' = ( -N *x/z -N *y/z -(a + b/z) 1)

对于齐次坐标,我们可以对 所有项乘以一个相同的值,不会改变他的位置。

p' = p'* (-Z) = ( -N *x -N *y (az + b) -z)

所以我们可以凑出下面的矩阵乘法:

e82919c1afd84ea18e40c1bd3c68d8e3.png

这一步在透视投影过程中称为透视除法(Perspective Division),这是透视投影变换的第2步,经过这一步,就丢弃了原始的z值(得到了CVV中对应的z值),顶点才算完成了投影。而在这两步之间的就是CVV裁剪过程,所以裁剪空间使用的是齐次坐标。

为什么不把透视除法整合到矩阵中?我们在后面详细讨论原因。

接下来我们就求出a和b:因为CVV是 -1到1范围,所以:

073d5c8cd47c82fed3c50ef4b75ee09b.png

所以:

8d82c189fa0630be76cc3971a92f9219.png

我们得到了透视投影矩阵的第一个版本:

d1c1441ae67d427809e4cc4904550b81.png

现在映射X和Y到[-1,1]之间

上面版本的透视投影矩阵可以从z方向上构建CVV,但是x和y方向仍然没有限制在[-1,1]中。

为了能在x和y方向把顶点从Frustum情形变成CVV情形,我们开始对x和y进行线性插值映射到[-1, 1]:(在 x,y项没有乘以-Z之前的范围)

对于 :

p' = ( -N *x/z -N *y/z -(a + b/z) 1)

Nx / z的有效范围是[left, right]

Ny / z 为[bottom, top]

注:投影平面的左边界值(记为left)和右边界值(记为right)

0642030068d7560ec1f20bd6ada54f8f.png

把x,y代入第一个版本,我们得到了最终的投影点:

21687c52549316c8b69cad84decb14a5.png

注意到我们之前对 p’乘以了 -z, 但上面的 X,Y 还没有乘以 -z

所以对 X,Y项乘以 -Z

4b98e42b649a6068e17fa8ea9ca66375.png

则我们最终的透视变换矩阵:

849f829878c076426fed6a0703add7a4.png

注意到M的最后一行不是(0 0 0 1)而是(0 0 -1 0),因此可以看出透视变换不是一种仿射变换,它是非线性的。

将right、left、top以及bottom等参数去掉

fov即视野,是视锥体在xz平面或者yz平面的开角角度,具体哪个平面都可以。OpenGL和D3D都使用yz平面。

aspect即投影平面的宽高比。

near是近裁剪平面的距离

far是远裁剪平面的距离。

d4f1239ce6edcdb9f12027659ae968a2.png

我们回头讨论下几个问题

为什么OpenGL和D3D计算FOV都使用yz平面?

上图中左边是在xz平面计算视锥体,右边是在yz平面计算视锥体。可以看到左边的第3步top = right / aspect使用了除法(图形程序员讨厌的东西),而右边第3步right = top x aspect使用了乘法,这就是为什么图形APIs采用yz平面的原因

为什么要先做裁剪,再做透视除法?

我们看到博客里说:“我们可以看出为什么要先做裁剪,再做透视除法,因为透视除法会丢失w’信息,也就是原始的z值,导致Z方向无法做裁剪。”

这个理解是不准确的。

我总结了三个原因:

1.第一个原因,避免裁剪出来的新三角形有畸变

对于齐次坐标, w'同时影响 x’,y‘,z’ 值。 因为 CVVx = x'/w CVVy = y'/w CVVz = z'/w

不是单纯导致Z方向无法做裁剪,因为X,Y平面也可以因为clip 切割三角形,而这个三角形,是在Z方向需要插值的。

硬件进行clip操作要在透视除法之前做,此时的视锥体xyz范围是[-w,w](d3d的 z裁剪范围是在(0,w)范围裁剪),是因为clip之后可能会产生很多新的三角形,所以也会得到新的顶点(新的顶点的 x‘ y' z' w' 都是相交计算得到的,w’要通过x' = Nx ; y' =Ny; z' = az+b ; 来插值得到,这样后面除以w’得到cvv坐标,才能还原成投影到近平面的近大远小效果,因为w‘= -z 是深度信息),之后再进行透视除法,转化到[-1,1]范围

如果除以w后,丢失w信息,再裁剪,那么只能计算新三角形的新顶点的x‘ y' z' 值,w'没有计算。

对于X,Y平面超出被切割的三角形是没关系的,但对于Z方向上被切割的三角形,因为w’信息丢失,

那么clip的边缘相交切割的得到的新三角形的顶点 插值,是基于-Nx/z ; -Ny/z; -(az+b)/z ; 来插值得到w 。 我们可以看到这个插值,不再基于z线性,而是基于 1/z 线性,那么得到的三角形就是有畸变的。

2. 进行透视除法之前会进行裁剪,会把z=0的部分剔除掉,从而保证透视除法的时候不会存在z=0的顶点。

3. 优化性能

裁剪能剔除部分顶点(即使生成了新顶点,很多时候剔除得更多),减少部分顶点进行 除以w的除法操作,提高性能。

而之后的深度测试,我们为了减少近处的z-fight效应,本来就想近处精度更高,所以要基于 1/z线性,

经过透视投影后,顶点的原始z信息丢了,但得到了[-1,1]中的z,顶点之间的z顺序不会有变,不会影响通过NDC的z光栅化得到的fragment的z进行depth test。

另外以下是忘记看的哪里说的了,不知道正确与否:

在真正的渲染管线里(比如DX),为了优化性能,

并不是裁剪后进行透视除法,得到clip坐标。

而是,直接把透视除法整合到投影矩阵里,直接得到Clip坐标, 然后 乘以1/W来得到CVV坐标来剔除

为什么本地坐标系,世界坐标系、投影坐标系都是左手坐标系,观察坐标系是右手坐标系?


实际上,在Opengl中,本地坐标系和世界坐标系都是右手坐标系,也就是说,直到投影空间中才变换为左手。而unity中的本地坐标系和世界坐标系是左手系统,所以显得观察坐标系比较的特别。

所以Opengl里 不需要转换Z轴, Unity(DX)里需要转换一次Z轴为负方向

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值