光线追踪是图形学领域里最为著名的一种技术,其中首要的一步是视点(相机,眼睛)穿过像素中心,发射一条射线,也就是主光线(次级光线指的是从物体表面反射或折射等发出的光线)。这一步看起来比较简单,但仍然涉及到一些细节和概念需要厘清。
我系统的学习这些东西是从renderman规范开始的,看的第一本书是advanced renderman,这本比较难,我更推荐看An Introduction to Ray Tracing (1989)。
开始正题。首先,要搞清楚fov角,advanced renderman上会讲到焦距,讲的非常细致深入,如果要深挖可以看书,我就不谈了(毕竟我也不记得了)。相机所看到的三维场景是一个视锥体,也叫平截头体,如下图:
顶点处会有两个角度,一个大,一个小,小的那个就是fov角,大的那个具体多大是由我们看到的屏幕的宽高比决定的。比如,屏幕宽和高分别是width=640,height=480,小的fov角为fov_little,大的那个角度是not_fov_but_big,有如下关系:tan(fov_little/2)/tan(not_fov_but_big/2) = height/width。fov角是做什么用的呢?它决定了我们能看到的视锥体的角度,或者更直白的说,决定了我们能看到的世界的大小,当然,这样说也不准确,我们仍然讲前面这个例子,屏幕的宽和高分别是640和480,眼睛朝着正前方看去,fov角决定的就是眼睛的上下所能看到的世界的范围,而左右所能看到的范围则由640决定,如果宽变成了1000,自然能看到的就会更多,如果不是这样,1000和640看到的世界是一样的,那么必然会导致图像的拉伸,通常这不是我们想要的结果。而另一方面,如果宽变小,自然看到的世界就会变小;但如果宽小于480,那么fov角又变成了决定眼睛左右所能看的世界的范围了。
然后,我们穿过像素发射光线,像素所在的空间是光栅化空间,比如,宽是从0到639,高是从0到479,但我们现在需要在三维世界(相机坐标系)里发射光线,所以我们需要把像素(px,py)变换到三维世界中去(wx,wy,wz)。如果我们已经变换过去从而得到了(wx,wy,wz),那么连接相机所在的点(0,0,0)和(wx,wy,wz),就是我们想要的射线。怎么做这个变换呢?直观的看,就是把这块屏幕(640,480)放到三维世界中,使得z轴穿过屏幕中心(忘了说一点,本文基于renderman讨论,所以相机朝向z轴正方向),同时屏幕的x轴(0~639)和三维世界的x轴平行,屏幕的y轴(0~479)和三维世界的y轴平行。由此,我们引入另一个坐标系,屏幕坐标系,其实就是把光栅化空间归一化,把0~479变到-1~1,相应的0~639变到-1.333~1.333。(这只是一种约定,或者较为方便的做法,数值上并不是非要如此)然后,我们把归一化的屏幕放到planez=cot(fov/2)的位置,此时,屏幕刚好和平截头体内切(不知道准确的叫法,反正就是刚好在平截头体内部,再大一点就会出来),如图:
然后说一下变换,对于任意像素(px,py),先做一个平移,使得z轴穿过屏幕中心,平移之后得到,px1 = px - width/2,py1 = py - height/2,(此处只讨论宽和高都能被2整除的情形)。然后,需要注意,我们是从像素点的中心发射光线,而px1和py1是像素左上角的坐标,所以需要把它们加0.5,得到px2 = px1 + 0.5,py2 = py1 + 0.5,然后,做归一化,px2和py2同除以height/2,px3 = px2 / (2*height),py3 = py2 / (2*height),这就是我们最终想要的屏幕空间的坐标。注意,我们把屏幕放在了planez=cot(fov/2)的位置,所以,我们已经得到了射线与屏幕空间相交的三维坐标(px3,py3,planez)。我们已知射线的起点是(0,0,0),且射线经过点(px3,py3,planez),根据相似三角形的性质,可以得到射线在z=z0时,经过的点是(px3/planez*z0,py3/planez*z0,z0)。连接射线起点(0,0,0)和射线上任意点都可以得到我们所需要的向量,所以,我们可以选择z=planez的点,也可以选z=z0(比如是1)的点。得到的向量归一化之后是一样的。
如果我不能把事情讲清楚,我是会有很强的挫败感的,所以我努力准确的去表达,同时希望对方能精确的提问,比如说清楚自己哪里懂了,哪里不懂,不太确定的地方就说一下自己的理解,看对不对,觉得很困惑的地方也可以说一下自己认为的应该是什么样子,问为什么不是这样。懂得倾听是非常好的美德,但交流问题的时候,还是应该适当的发表自己的观点,对错并不重要,重要的是你说了自己的想法,别人就知道你哪里没搞清楚,哪里搞清楚了,更有针对性的去解释,否则,回答者会有一种在森林里迷路的感觉,一会问一下A问题,一会问一下B问题,完全不知道你搞清楚了哪些没搞清楚哪些,会很挫败很苦恼。还是那个道理,我们做任何事情,都是需要激励的,无论来自外界的,还是自我的,解释问题也是一样,回答方希望提问方给出明确的反馈,这个问题的哪些子问题搞明白了,但另一些子问题没搞明白,这样,一方面,回答方受到了激励,就不会觉得辛苦,另一方面,再回答的时候也会更有针对性。