`
izuoyan
  • 浏览: 8915872 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

从零实现3D图像引擎:(13)把宽高比、透视投影矩阵、屏幕变换矩阵说透

阅读更多

1. 问题的引出

这个问题的引出又是因为《3D游戏编程大师技巧》这书里面有的问题没讲明白,有的东西又不对。

首先宽高比这个名词的出现是因为我们的PC屏幕不是正方形的,屏幕宽度 : 屏幕高度 就是宽高比。但是我们上次搭建的相机系统的视平面是正方形的,那么当很多物体投影到视平面上后,必然最后完成的是一幅正方形的画,而屏幕是长方形的,这时只有两种办法:

1) 把照片压扁,这样画上的所有物体都被压扁了。

2) 把照片上下多余的两条分别裁下来,只保留屏幕大小的画,这样物体不会走样变形,但是这幅画有一部分看不到了。

哪种是正确的?

以人眼为例,我们的眼睛不可能因为眼睛的外框不是正方形就把东西压扁吧,所以我们要做的是不要多余的上下两条边,而不是把物体压扁。

2. 两种方式

我们可以想到有两种方法来做这件事:

1) 如Hello3DWorld这样,在已经变换到了屏幕系的图像上动手脚,把图像的多余上下边舍弃。

2) 我们将yz平面的FOV不设置为与xz平面的FOV相同,而是使视平面的宽高比和屏幕的宽高比相同。这样,上下裁剪面的方程就会有所变化,直接会将原本会出现在多余的两边的东西裁掉,也就是在3D空间裁剪做完之后,再投影到视平面上的结果已经不是正方形了,而是和屏幕的比例相同,也就是在实质上对纵向的视野进行了操作。这样的好处是在物体剔除时可以剔除更多的物体,也不需要再进行2D图像裁剪了。所以我们应该使用这种方法。

如果修改了上下裁剪面方程,从透视投影到屏幕变换的新的过程将会是这样:

调整上下裁剪面后

得到的已经是最后的屏幕坐标了。

3. 修改生成上下裁剪面的代码

还记得如何生成上下裁剪面吗?上一篇文章讲的很细了,这里不再重复了。现在所作出的改变就是取那特殊的两点的坐标发生了变化。比如上裁剪面,以前是(-1,1) (1,1),现在变成了(-1, 1/ar) (1, 1/ar)。

把新的坐标代进求叉乘,求新的上裁剪面法向量,得:<0, d, -1/ar>

下裁剪面法向量:<0, -d, -1/ar>

这是新的创建相机的函数代码:

4. 透视投影矩阵的推导

透视投影的原理我们已经非常清楚了,上篇文章介绍了:

x' = x * d / z

y' = y * d / z

推导矩阵的原理苍井空老师也介绍过了。但这次比较特殊,因为我们无法通过变换矩阵让坐标值除以z,只有借助4D齐次坐标了。

我们要这么做:

1) 把x和y放大d

2) 把齐次坐标w的值设置为z,这样因为w != 1,所以整理齐次坐标时,x就变成了x*d/w = x*d/z,y同理。

所以矩阵应该如下:

[ d 0 0 0 ]

[ 0 d 0 0 ]

[ 0 0 1 1 ]

[ 0 0 0 0 ]

如果你也发现这个结果和《3D大师》里面说的完全不一样的话,说明你有认真去推导了,因为在那本书里讲的投影矩阵推导根本狗屁不通。

然后在执行完矩阵变换后,需要把所有的顶点坐标的x,y除以w,这可以非常重要的一步哦。

下面是使用的方式,这个物体投影变换函数可以选择是手动来算还是用矩阵来算:

5. 屏幕变换矩阵的推导

上面那个大图很好的说明了屏幕变换应该怎么做:

1) 放大screen_width / 2。

2) X平移screen_width / 2。

3) Y平移screen_height / 2。

这个矩阵我们完全可以直接写出来了,不就是个缩放和平移的综合嘛:

cam->ScreenWidth / 2, 0, 0, 0,
0,cam->ScreenWidth / 2, 0, 0,
0, 0, 1, 0,
cam->ScreenWidth / 2, cam->ScreenHeight / 2, 0, 1

这个矩阵很直观吧,该缩放的缩放,该平移的平移。

使用的代码如下:

6. 总结

屏幕的宽和高不一致影响的事情有两件,一:相机的宽视野和纵视野不一致。二:变换到屏幕坐标系时需要平移的位移不相同。

7. 代码下载

因为有了灵活的相机系统,有了方便的透视和视口变换矩阵,这次稍微改了一改DEMO,可以使用方向键来调整UVN相机的世界坐标。上下调整Z坐标,左右调整X坐标,注意不要让相机离物体太近,否则会超出相机造成出错,因为我们还没有写裁剪的代码不是吗。

截图:

DEMO截图

完整项目源代码下载:>>点击进入下载页<<

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics