首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

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

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

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;
if( obj->isIntersected(ray, distance) != MISS) // 判断是否有交点
{
Intersection = ray.getPoint(distance); //如果相交,求出交点保存到Intersection
}
}
为了计算方便,这里就以球为例,创建一个CSphere的类,该类继承于CGObject。
作为球,只需要提供球心Center和半径Radius就可以决定它的几何性质。所以CSphere类只有两个私有成员变量。在所有成员函数中,我们重点来看看isIntersected()方法。
INTERSECTION_TYPE CSphere::isIntersected(CRay _ray, double& _dist)
{
GVector3 v = _ray.getOrigin() - m_Center;
double b = -(v * _ray.getDirection());
double det = (b * b) - v*v + m_Radius;
INTERSECTION_TYPE retval = MISS;
if (det > 0){
det = sqrt(det);
double t1 = b - det;
double t2 = b + det;
if (t2 > 0){
if (t1 < 0) {
if (t2 < _dist) {
_dist = t2;
retval = INTERSECTED_IN;
}
}
else{
if (t1 < _dist){
_dist = t1;
retval = INTERSECTED;
}
}
}
}
return retval;
}
如果射线和球有交点,那么交点肯定在球面上。球面上的点P都满足下面的关系,
| P – C | = R
很明显球面上的点和球心的差向量的大小等于球的半径。然后将射线的参数方程带入上面的公式,再利用求根公式判断解的情况。具体的方法这里就不详述了,有兴趣的同学可以参考另一篇文章“利用OpenGL实现RayPicking”,这篇文章详细讲解了射线和球交点的计算过程。
现在我们实现了射线CRay,球体CSphere,还差一个重要的角色——光源。光源也是物体的一种,完全可以从我们的基类CGObject类继承。这里做一点区别,我们单独创建一个所有光源的基类CLightSource,然后从它在派生出不同的光源种类,比如平行光源DirectionalLight,点光源CPointLight和聚光源CSpotLight。本文中只详细讲解平行光源的情况,其他两种光源有兴趣的同学可以自己实现。
类CLightSource的成员变量有四个,分别表示光源的位置,光源的环境光成分,漫反射成分和镜面反射成分。同样地,所有的set和get方法都为该类的子类提供相同的功能。最后也有三个虚成员函数,EvalAmbient(),EvalDiffuse()和EvalSpecular(),它们名字分别说明它们的功能,并且都返回GVector3类型的值——颜色。由于对于不同种类的光源,计算方法可能不同,于是将它们设置为虚函数为以后的扩展做准备。笔者这里将光照计算放在了光源类里面,当然你也可以放在物体类CGObject里,也可以单独写一个方法,将光源和物体作为参数传入,计算出颜色后最为返回值返回。具体使用哪一种好还是要根据具体情况具体分析。
上面的平行光源类CDirectionalLight是CLightSource的子类,它继承了父类三个虚函数方法。下面来看看这三个函数的具体实现。
环境光的计算是最简单的,将物体材质环境反射系数和光源的环境光成分相乘即可。
ambient = Ia•Ka
计算环境光的代码如下
GVector3 CDirectionalLight::EvalAmbient(const GVector3& _material_Ka)
返回列表