Opengl_10_透视投影-创新互联
1,
We have finally reached the item that represents 3D graphics best - the projection from the 3D world on a 2D plane while maintaining the appearance of depth. A good example is a picture of a road or railway-tracks that seem to converge down to a single point far away in the horizon.
通过在保留物体深度立体感的前提下将3d世界的物体投影到2d平面上最优化显示3D图形。
2,
对图形的透视变换需要提供四个参数:
1.屏幕宽高比:矩形屏幕的宽高比例是投影的目标;
2.垂直视野:相机窗口看向3d世界的垂直方向上的角度;
3.Z轴近平面的位置:近平面用于将离相机太近的物体裁剪掉;
4.Z轴远平面的位置:远平面用于将离相机太远的物体裁剪掉;
屏幕宽高比是一个必要的参数,因为我们要在一个宽高相等的单位化的盒子内展示所有的坐标系,而通常屏幕的宽度是大于屏幕的高度的,所以需要在水平方向上的轴线上布置更加密集的坐标点,竖直方向上相对稀疏。这样经过变换,我们就可以在保证看到更宽阔屏幕图像的需求下,根据X轴在单位盒子空间内的比例,在X方向上添加更多的X坐标。
垂直视野(The vertical field of view)让我们可以(放大和缩小zoom in and out)3D世界。
左边的图片中相机的视角较大而在屏幕上物体看上去应该会较小;
右边的视角较小而物体看上去应该会更大。
α > β, L2 > L1
这是由于相机的位置导致的效果,有点违背直觉,(同样的物体咋就看上去一个大一个小了)。
左边的相机靠近投影屏幕使视角变大而右边的相机远离屏幕视角变小。
然而,在程序中这个现象不会有实际的效果,因为投影的坐标系和屏幕是映射匹配的,相机的位置不会产生任何影响。
3,
投影屏幕是一个和XY平面平行的平面。
整个平面不会都可见,因为它太大了。
我们只能从一个和屏幕有相同的宽高比的矩形区域(投影窗projection window)来看物体,屏幕宽高比为:
ar = screen width / screen height
如果投影窗高度为2,则宽度为2ar。
所有超出这个矩形(宽[-ar, ar],高[-1, 1])的物体都会被裁剪掉
4,
上面求得3D空间的P点在投影窗上坐标P'
5,
由于我们的屏幕尺寸宽为2*ar,高为2,所以3d世界的点只要X坐标在-ar到ar之间,Y坐标在-1到1之间那么就会在屏幕上有投影点。
这样在Y分量上我们实际上是单位化的了,但X分量上没有单位化。
我们可以让Xp除以屏幕宽高比来将其单位化,单位化后原本X坐标为+ar的就变成+1了。
比如如果投影后X坐标是+0.5,屏幕宽高比是1.333,单位化后的新X坐标变为0.375。总之,除以屏幕宽高比进行单位化可以起到在X轴上浓缩更多的坐标点的效果。
6,
在求解最终结果之前让我们先尝试看一下这个投影变换矩阵大致应该是什么样子的。也就是说我们要使用矩阵来表示上面的等式。
那么问题来了,在两个等式中,我们分别都需要让X或Y除以Z,
而Z同时也是表示位置的向量的一个分量,Z的值从一个顶点到下个顶点会改变,也就是不是常量,
所以不可能把它放在一个矩阵中来对所有顶点进行变换。
为了好理解我们可以先看变换矩阵顶上第一行的四个分量(a,b,c,d)。我们需要找到一组值使下面的等式成立:
这是第一行的这组值和顶点位置向量的点积,
最后要作为变换后的顶点位置向量中X分量的值。我们可以使'b'和'd'都为0,但是无论'a'和'c'怎么取值也得不到等号右边的结果。
OpenGL中的解决办法是将这个变换分解成两步:先乘以一个投影变换矩阵,然后再单独除以Z分量的值。
应用中会提供那个投影变换矩阵,shader中要进行顶点和投影变换矩阵相乘的这个步骤,除以Z分量的单独步骤在GPU中是固定的,而且是在光栅器中进行(在顶点着色器和片段着色器之间的某个地方)。那么GPU怎么知道顶点着色器输出的哪些顶点需要除以它们的Z值呢?简单的是,这个会由内置的gl_Position变量来负责实现,不需要我们操心。现在我们要做的是找到上面只关于X和Y两个分量的投影变换矩阵。乘以这个投影变换矩阵之后GPU之后会自动帮我们进行Z值得相除变换使我们可以得到我们想要的最终结果。
但是这里还有一个复杂的点是:如果我们将顶点位置和变换矩阵相乘,然后除以Z值我们事实上会丢失Z值,因为每个顶点中Z的值都变成1了。最初的Z值是必须要保存下来的,因为之后要用来进行深度检测(depth test)。这里的技巧是将原始的Z值保存在结果向量的W分量中,然后只将XYZ除以W分量而不是Z。W保存Z的原始值用于最后的深度检测。将gl_Position除以W分量的自动步骤称为'透视分割'。现在我们就可以创建一个实现上面两个等式的中间变换矩阵,以及在W分量中保存Z的值:
7,
我们想实现既将Z值单位化,还要使裁剪器更容易的对图形进行深度裁剪,而不需要知道Near Z和Far Z的值。然而上面的矩阵将Z都变成0了。
向量变换之后系统会自动进行透视分离,我们需要选择变换矩阵第三行的一组值,使视窗内(比如:NearZ <= Z <= FarZ)Z分量上的分离操作樱映射到[-1,1]范围内。
这个映射操作由两部分组成:首先我们将 [NearZ, FarZ]这个范围缩放到任意一个宽度为2的范围内,然后进行平移使其起点从-1开始,也就是[-1,1]的范围了。对Z进行先缩放然后平移的操作可以用下面的一个通用函数表示:F(z) = A*z + B;
但是之后的透视分离会将等号右侧的函数变成:A + B/z;
现在要找出将Z的范围映射到[-1,1]范围的A和B的值。特别的我们知道当Z等于NearZ时映射结果为-1,而当Z等于FarZ时映射结果为1
∵ A + B/NearZ = -1
∴ A = -1 -B/NearZ
∵A + B/FarZ = 1
∴ A = 1 -B/ FarZ
∴ -1 -B/NearZ = 1 -B/ FarZ
∴ B*(1/FarZ - 1/NearZ) = 2
∴ B*(NearZ - FarZ) = 2* NearZ* FarZ
∴ B =(2* NearZ* FarZ)/(NearZ - FarZ)
∴ A = -1 -B/NearZ
= -1 - (2* FarZ) /(NearZ - FarZ)
= (-NearZ + FarZ – 2* FarZ)/(NearZ - FarZ)
= -(NearZ + FarZ) /(NearZ - FarZ)
现在我们需要设置变换矩阵的第三行(a,b,c,d)的值满足下面的等式:
aX + bY + cZ + dW = AZ + B
首先我么立即会将'a'和'b'的值设置为0,因为我们不想X和Y在变换过程中对Z产生影响。然后我们可以让A作为'c'的值而B作为'd'的值(W已知为1)。因此我们最终的变换矩阵为:
将顶点位置向量和投影变换矩阵相乘之后,坐标系将会变换到裁剪空间中,并且在透视分离之后坐标系会变换到NDC 空间(Normalized Device Coordinates:单位化设备坐标系)中。
之前在没有投影变换的情况下,我们只能从顶点着色器VS中简单地输出XYZ各分量都在[-1,1]范围内的顶点,保证他们在屏幕当中,并且通过让W的值为1我们可以防止透视分离产生的影响,然后将坐标变换到屏幕空间就结束了。当使用了投影变换矩阵之后,透视分离就成了3d投射到2d平面的一个集成的部分了。
8,
PersProjTrans = InitPersProjTransform();
m_Wtransformation = TranslationTrans * RotateTrans * ScaleTrans;
m_WPtransformation = PersProjTrans * m_Wtransformation;
gl_Position = gworld * vec4(Position, 1.0);
分享文章:Opengl_10_透视投影-创新互联
分享链接:http://hbruida.cn/article/coogpp.html