光线追踪技术的理论和实践(面向对象)

                             光线追踪技术的理论和实践(面向对象)

                                 Theory & Practice of Raytracing(Object Oriented)

这篇文章将介绍光线追踪技术。在计算机图形领域中,这种技术被普遍应用于生成高质量的照片级图像。在为一个场景计算光照的时候,通过固定图形渲染管线可以计算phong光照模型,由于该模型的特征,使得渲染的物体看起来有塑料的质感。如果要渲染一个有金属质感且能反射周围环境的物体,phong模型就无能为力了。和固定渲染管线相比,可编程图形渲染管线的力能要强的多,虽然可以实现很多逼真的光照效果,比如利用环境贴图来现实物体对环境的反射效果。但是这种环境反射只能反射出已经保存在Cube Map中的图像。在真实世界中,如果一个能反射周围环境的物体周围还有很多其他物体,它们就会相互反射。一般的环境贴图技术达不到这样的效果,于是在渲染照片级画面的时候,就要用到光线追踪的技术。文本还将利用c++面向对象的方法来实现光线追踪。

原理

在介绍原理之前,先考虑一个问题:我们是怎样看到真实世界中的物体的?我们能看到物体,是因为该物体上有反射光线到达我们的眼睛。没有任何光线传入眼睛,我们就看不到任何东西。我们还经常看到一个物体表面能反射另一个物体。这也是因为被反射物体表面的反射光线到达该物体表面后,该物体继续将光线反射到我们的眼睛里,于是我们看到了该物体表面反射其他物体的效果。现在,我们将从物体表面出发最后到达眼睛的光线的方向反向。先来看看下面的Fig1,在Fig1中是一个虚拟的场景,场景中有2个球和1个圆锥,白色的点代表光源,中间四边形就是虚拟屏幕,屏幕上一个一个的小方格就代表像素,相机的位置代表观察者眼睛的位置。

 

(a)

 

(b)

Fig1光线追踪场景

 

光线追踪的原理就是从相机的位置发出一条条通过每一个像素的射线,如果该射线和场景中的物体相交,那么就可以计算出该交点的颜色,这个颜色就是对应的像素的颜色。当然,计算像素颜色的时候首先要计算出交点处所有与光照计算相关量,比如法线,入射光线和反射光线等等。

 

(a)

 

(b)

Fig2光线和空间物体相交

 

在Fig2中可以看到,从相机出发的射线依次穿过每一个像素,图中显示出其中的三条。这些射线都与物体有交点,不同物体的交点计算方法也不一样。射线与平面的交点计算方法和射线与球的交点计算方法是截然不同的。为了计算方便,这里就只以球为例。如果一个物体可以反射周围的环境,那么当一条射线与该物体相交后,射线还会在该点产生反射和折射等。例如在Fig2中,当射线和蓝色球相交后,光线会反射,反射的光线又可能和橙色圆锥和绿色球相交,所以我们能在蓝色球的表面看到橙色的圆锥和绿色球。整个光线追踪的原理就是这么简单,但是实际操作起来又有很多要注意的地方。

 

实践

 

 

用面向对象的方法来实现光线追踪比使用面向结构要来的容易一些。因为在光线追踪的整个过程中,比较容易抽象出对象的共同特征,比如我们可以抽象出射线,物体,光源,材质等等。当然,最最基本的一个类就是向量类,在计算光照的时候向量很重要。在这里我们假设已经实现了一个三维向量类GVector3,该类提供所有有关向量的操作。

 

除了向量,我们最先能想到一个关于射线的类,叫CRay。

 

对于一条射线最基本的就是它的出发点和方向,所以在CRay的类图中,能看到两个私有成员变量m_Origin和m_Direction,它们都是GVector3类型。由于类的设计原则要满足数据的封装性,既然射线的出发点和方向都是私有的,那么就要提供公共的成员方法来访问它们,于是我们还需要set和get方法。最后,getPoint(double)方法是通过向射线的参数方程传入参数t而获得在射线上的点。实现了射线CRay类后,那么在使用光线追踪计算每个像素颜色的时候,对于每一个像素都要创建一个CRay的实例。

 

for(inty=0;y<=ImageHeight;y++)

{

     for(intx=0; x<=ImageWidth;x++)

     {

          doublepixel_x = -20.0 +40.0/ImageWidth*x;

          doublepixel_y = -15.0 +30.0/ImageHeight*y;

 

          GVector3direction =GVector3(pixel_x, pixel_y,0)-CameraPosition;

          CRayray(CameraPosition,direction);

 

          // call RayTracer function

     }

}

 

从上面的代码可以看到,两个for循环用于扫描每一个像素,然后在循环里计算出每个像素的位置。如果我们假设Fig1中,四边形屏幕处于xy平面,长和宽分别是40和30,且左上顶点坐标和右下顶点坐标分别为(-20,15,0)和(20,-15,0)。为了将该屏幕映射到实际分辨率为800*600的窗口上,就要求出虚拟屏幕上每个像素的坐标pixel_x和pixel_y。然后对每一个像素都用一条射线穿过它,射线的方向自然就是像素的位置和相机位置的差向量的方向。要注意一点,实际窗口的分辨率比例要和虚拟屏幕长宽比例保持一致,这样渲染出来的画面看起来长宽比例才正确。

 

现在我们来考虑在场景中的物体。一个物体可能有很多可以描述它的特征,比如形状,大小,颜色,材质等等。使用面向对象的方法,就需要将这些物体的共同特征抽象出来。下面是一个抽象出来的物体类GCObject。

 

CGObject类成员变量有五个,分别表示物体表面环境光反射系数(m_Ka),漫反射系数(m_Kd),镜面反射系数(m_Ks),镜面反射强度(m_Shininess)和环境反射强度(m_Reflectivity)。前四个变量是计算光照所需要的最基本量,而环境反射强度表示该物体能反射环境的能力。这些成员变量都的类型都是protected,因为我们要把CGObject最为物体的基类,这些protected成员变量可以被该类的子类所继承。该类的所有get方法和set方法都能被子类继承,而且所有继承了该类的子类的方法都相同。该类还有两个虚成员函数,分别是getNormal()和isIntersected()。getNormal()函数的作用是获取物体表面一点的法线,它接受一个GVector3类型的参数_Point,并返回物体表面点_Point处的法线。当然不同物体表面获得法线的方法是不一样的。比如,对于平面来说,平面上所有点的法线都是一样的。而对于球来说,球面上每一个的法线是球面上的该交点p和球心的c的差向量。

 

NSphere= p - c

 

所以将getNormal()设置为虚成员函数就可以实现类的多态性,凡是继承了该方法的子类,都可以实现自己的getNormal()方法。同样的道理,函数isInserted也是虚成员函数,该方法接受参数射线CRay

和距离Distance,CRay是输入参数,用于判断射线和该物体的交点,Distance是输出参数,如果物体和射线相交,则返回相机到该交点的距离。Distance还应该有个很大初始值,表示在无限远处物体和射线相交,这种情况用于判断物体

  • 4
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 44
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张赐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值