您当前的位置:五五电子网电子知识单片机-工控设备综合-其它利用c++面向对象的方法来实现光线追踪 正文
利用c++面向对象的方法来实现光线追踪

利用c++面向对象的方法来实现光线追踪

点击数:7161 次   录入时间:03-04 11:46:43   整理:http://www.55dianzi.com   综合-其它

    这篇文章将介绍光线追踪技术。在计算机图形领域中,这种技术被普遍应用于生成高质量的照片级图像。在为一个场景计算光照的时候,通过固定图形渲染管线可以计算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(int y=0; y<=ImageHeight; y++)

    {

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

    {

    double pixel_x = -20.0 +40.0/ImageWidth*x;

    double pixel_y = -15.0 +30.0/ImageHeight*y;

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

    CRay ray(CameraPosition, direction);

    // call RayTracer function

    }

    }

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

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



www.55dianzi.com

    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还应该有个很大初始值,表示在无限远处物体和射线相交,这种情况用于判断物体和射线没有交点。函数isIntersected()还返回一个枚举类型INTERSECTION_TYPE,定义如下:

    enum INTERSECTION_TYPE {INTERSECTED_IN = -1, MISS = 0, INTERSECTED = 1};

    其中INTERSECTED_IN表示射线从物体内部出发并和物体有交点,MISS射线和物体没有交点,INTERSECTED表示射线从物体外部出发并且和物体有交点。射线和不同物体交点的计算方法不同,于是该函数为虚函数,继承该函数的子类可以实现自己的isIntersected()方法。下面的代码就可以判断一条射线和场景中所有物体的是否有交点,并且返回离相机最近的一个。

    double distance = 1000000; // 初始化无限大距离

    GVector3 Intersection; // 交点

    for(int i = 0; i

    {

    CGObject *obj = objects_list[i];

[1] [2] [3]  下一页


本文关键字:暂无联系方式综合-其它单片机-工控设备 - 综合-其它