前言
- 写一点困惑
- 我看了关于光线追踪的教程视频,对其原理有了认识
- 看到最后,老师布置作业不让用OpenGL,因此只能用C++手动敲代码
- 我完全不知道该怎么入手,一脸懵逼
原理
- 从视点发出一条射线,然后透过屏幕的某个像素点,然后与场景中物体相交,取相交的第一点,然后利用Phong模型,计算其颜色值,再从该点引发反射和折射光线(根据物体的材质不同,金属没有折射,直接被吸收了。透明材质的折射光线会与后面的物体相交,并且反映后面物体的颜色值),计算这两个光线与其它物体的交点,并计算交点的颜色值,与初始颜色值叠加,一直迭代下去,直到光线衰减为0。
- 利用这个算法,可以实现阴影,反射,折射等效果
步骤
光线投射
- 明暗效果仅仅由第一次相交的物体表面法线方向,材质,视点和光照方向,以及光照强度等因素共同考虑
- 光线投射并不考虑第二层以及更深层次的光线,因此不具有阴影,反射,折射等效果
添加阴影
- 计算阴影区域时,只关心是否与物体相交,而不关注哪个是最近交点
折射效果与反射效果
- 计算光线在交点处被物体反射和折射所产生的新的光线的方向,新的方向由入射光方向,物体表面法向,以及介质共同决定
- 对新产生的光线分别继续进行跟踪
- 反射方向 R=I-2(I.N)N
- 折射方向的公式很复杂。
递归结束
- 有些物体并不透明
- 递归深度
- 在光线弹射一定次数后停止
- 光线的贡献
- 在光线的贡献衰减到足够小时停止
关键
- 因此光线追踪算法的关键就是如何计算射线和各个图形的交点
平面类
- 平面类进行计算,光线本身有一个方程,平面本身也有一个方程,直接联立方程组,计算出交叉点即可。
- 计算出交叉点只是表明该位于平面上,但不一定在该平面范围内。
- 所以我们需要判断该点是否位于该平面范围内。因此也引发了很多有趣的算法。并且这涉及到计算几何
光线的表示
- 这里将光线作为从眼睛射出的射线
- P(t) = R0+t*Rd
- R0是光线的源点,Rd代表光线的朝向,Rd是单位向量
- 参数t代表光线到达的位置
- 在光线的正方向上,参数t都是正数,t>0
平面的表示
- 由法线和该平面上的任意一点可以确定一个平面
- 显示表示P0 = (X0,y0, z0),n=(A,B,C)
- 隐式表示H(P)=Ax+By+Cz+D=0(n.P+D=0)n与P是点乘
- 当n是单位法向量时,距离就是H(P)(见距离公式)
获得解
- 直接联立两个方程获得解
- t=-(D+n.R0)/(n.Rd)
- 需要验算t>0
三角形平面
- 在实时图形学中,基于三角形的几何表示十分常见,可以使用三个顶点坐标来表示
- 重心坐标(也是需要数学证明)
- 三角形P0P1P2内的一点P可以表示成P=aP0+bP1+cP2
- (a,b,c)被称为重心坐标,满足大于等于0小于等于1,a+b+c=1
- 有许多其它的应用,纹理映射,法向插值,颜色插值等
- 性质,将点P连接P0,P1,P2,然后计算分割出来的三个三角形的面积占总面积的比例,就是abc的值。相反,我们只要给出abc,相加等于1,那么就一定能找到一个点在该三角形内。
- 然后联立光线方程与重心坐标方程获得一个方程组
- 计算就可以知道
- 需要验算t>0并且b+c<=1
多边形平面
- 虽然三角形比较常用,但是多边形也需要用到,因此需要一个普遍性的求交算法
- 判断是否在多边形内部,将交点以及多边形的所有顶点投影到XY,YZ,ZX平面中一个,在二维平面上进行处理
- 问题转变为判断一个点是否在多边形的内部(计算几何学科的问题)使用交点检测算法
交点检测算法
- 是所有不进行预处理的方法中最快的
- 基于Jordan曲线定理(如果一点在多边形内部,当且仅当由该点发出的任何一条射线与多边形的边界有奇数个交点。如果是外界就会有偶数个算法)
- 几种特殊情况
- 我们会遍历将该射线分别和三角形的几条边求交点
- 如果该射线穿过多边形的顶点,那么是按照一个交点还是两个
- 但是如果该射线与某条边平行,并且穿过该边,那么就有无数个交点
- 技巧
- 以检测点为原点,发出的射线为正坐标轴,作为检测交点的射线
- 如果某条边的y坐标符号相同,则与x轴无交点
- 否则,计算这条边与x轴的交点
- 位于x轴正半轴上,交点个数的变量+1
- 否则,与x轴正半轴没有交点(这样做还是没有解决上面两个特殊情况)
弧长法
- 按照顺时针给多边形的顶点标记ABCDE
- 然后依次连接PA,PB,PC,PD,PE
- 再去计算这几个边之间的夹角之和,最后等于2π,在内部。等于0在外部。
- 因为要求角度所以很麻烦,但是很稳定。一点误差也没有关系(上一个算法不稳定,因为有特殊情况。如果错了就完蛋)
- 升级版本
- 我们不直接计算角度,而是以该点为坐标原点,然后计算各象限内点的符号
- 事先规定,如果顶点Pr的某个坐标为0,则其符号为正。都为0就是被测点,预先排除。
- 然后根据象限变化
- 1-1 同一个象限 0
- 1-2 π/2
- 1-3 ±π(可能为正或者负号)
- 1-4 -π/2
- 当边的终点在与起点相对象限时,弧长变化增加或者减少π
- 这里通过二维形式的叉乘来计算,(x1,y1)和(x2,y2)分别是起点与终点
- f=x1y2-x2y1
- f=0,则穿过坐标原点,f>0,弧长代数和增加π,f<0,弧长代数和减少π
球体
- 和平面类似,直接联立方程组,然后求出相交点。但是这样很麻烦并且有一些缺点
- 我们不需要相交的第二个点
- 可以加速
- 用几何方法
- 首先连接光源点和球心并计算两者之间的距离
- 然后和r的平方作对比,如果大于该r的平方,则说明光源点在球体外否则在球体内。当相等的时候,则位于球上。(要考虑退化。什么是退化?)
- 然后计算球心与光源点向量到光线方向的投影tp(即光源点到球心沿光源方向的距离)
- 如果光源在球外,并且投影距离小于0,则光线不与球面相交
- 然后计算球心到光线所在直线的距离d,如果d大于R,则 光线与球面也不相交
- 然后计算球心到光线方向的投影点与交点的距离t,如果光源点在球内,那么交点s=tp+t,如果光源点在球外,s=tp-t
长方体
- 我们可以用长方体将三维模型包裹,然后计算光线是否与该长方体相交,如果相交就再计算与该三维模型的交点。不相交就直接忽略
- 因此长方体经常用来进行光线追踪加速
- Slab算法与Woo算法
- Slab算法
- Slab是两个互相平行的一组平面
- 计算该光线与长方体的三组Slab的交点,p1max,p1min,p2max,p2min ,p3max,p3min
- 然后取max里面的最小点Pmax与min里面的最大点Pmin。Pmin要小于Pmax,然后这就是与长方体相交的两个点了。如果Pmin不小于Pmax,那么该光线不与长方体相交
- 至于为什么,教程里面用一个二维图像验证了。但是具体的证明估计要用到数学。不想深究
- Woo算法
- 该算法是建立在这样一个基础上,沿坐标轴进行放置
- 我们取比较近的三个平面,(去掉背相面)
- 然后获得光线与该三个平面的交点,
- 将这三个t值中的最大一个对应的点作为可能的交点
- 检查这个可能的交点是否位于长方体的表面上,如果不在,则光线与长方体不相交
- 两者的计算复杂度类似
添加纹理
- 纹理自身分为二维纹理与三维纹理
- 进行光照模型计算的时候,我们需要使用与该模型上的某点的颜色
- 但是如果贴上纹理,就要取该点所对应的纹理的颜色值
- 因此我们需要进行纹理映射
- 以矩形表面为例
- 为矩形的四个顶点指定二维纹理坐标
- 计算矩形内部与光线的交点的二维纹理坐标(双线性插值)
- 使用该纹理坐标在纹理图上进行查找,根据查找结果赋予交点相应的颜色值
一些思考
- Epsilon问题,计算精度问题
- 当光线与平面,球面相切,或者与多边形相交于某个顶点时,需要仔细考虑
- 加速算法
- 使用包围盒加速Bounding
- 使用层次结构加速 Hierarchical Structure 加速