Board logo

标题: 光线追踪技术的理论和实践(面向对象)(二) [打印本页]

作者: forsuccess    时间: 2013-8-10 14:16     标题: 光线追踪技术的理论和实践(面向对象)(二)

{
return GVector3(m_Ka[0]*_material_Ka[0],
m_Ka[1]*_material_Ka[1],
m_Ka[2]*_material_Ka[2]);
}
漫反射的计算稍微比环境光复杂,漫反射的计算公式为
diffuse = Id•Kd• (N•L)
其中,Id是光源的漫反射成分,Kd是物体的漫反射系数,N是法线,L是入射光向量。
GVector3 CDirectionalLight::EvalDiffuse(const GVector3& _N, const GVector3& _L, constGVector3& _material_Kd)
{
GVector3 IdKd = GVector3( m_Kd[0]*_material_Kd[0],
m_Kd[1]*_material_Kd[1],
m_Kd[2]*_material_Kd[2]);
double NdotL = MAX(_N*_L, 0.0);
return IdKd*NdotL;
}
镜面反射的计算又比环境光要复杂,镜面反射的计算公式为
specular = Is•Ks• (V·R)n
其中
R = 2(L•N) •N-L
Is是光源镜面反射成分,Ks是物体的镜面反射系数,V是相机方向向量,R是反射向量, n 就反射强度Shininess。为了提高计算效率,也可以利用HalfVector H来计算镜面反射。
specular = Is•Ks• (N•H)n
其中
H=(L+V)/2
计算H要比计算反射向量R要快得多。
GVector3 CDirectionalLight::EvalSpecluar(const GVector3& _N, const GVector3& _L, constGVector3& _V,
const GVector3& _material_Ks,const double& _shininess)
{
GVector3 IsKs = GVector3( m_Ks[0]*_material_Ks[0],
m_Ks[1]*_material_Ks[1],
m_Ks[2]*_material_Ks[2]);
GVector3 H = (_L+_V).Normalize();
double NdotL = MAX(_N*_L, 0.0);
double NdotH = pow(MAX(_N*H, 0.0), _shininess);
if(NdotL<=0.0)
NdotH = 0.0;
return IsKs*NdotH;
}
分别计算出射线和物体交点处的环境光,漫反射和镜面反射后,那么该射线对应像素的颜色c为
C = ambient + diffuse + specular
于是,我们可以在代码中添加一个方法叫Tracer(),该方法就是遍历场景中的每个物体,判断射线和物体的交点,然后计算交点的颜色。
GVector3 Tracer(CRay R)
{
GVector3 color;
for(/*遍历每一个物体*/)
{
if(/*如果有交点*/)
{
GVector3 p = R.getPoint(dist);
GVector3 N = m_pObj[k]->getNormal(p);
N.Normalize();
for(/*遍历每一个光源*/)
{
GVector3 ambient = m_pLight[m]->EvalAmbient(m_pObj[k]->getKa());
GVector3 L = m_pLight[m]->getPosition()-p;
L.Normalize();
GVector3 diffuse = m_pLight[m]->EvalDiffuse(N, L, m_pObj[k]->getKd());
GVector3 V = m_CameraPosition - p;
V.Normalize();
GVector3 specular = m_pLight[m]->EvalSpecluar(N, L, V, m_pObj[k]->getKs(), m_pObj[k]->getShininess());
color = ambient + diffuse + specular;
}
}
}
}
如果要渲染可以反射周围环境的物体,就需要稍微修改上面的Tracer()方法,因为反射是一个递归的过程,一但一条射线被物体反射,那么同样的Tracer()方法就要被执行一次来计算被反射光线和其他物体是否还有交点。于是,在Tracer()方法中再传入一个代表递归迭代深度的参数depth,它表示射线与物体相交后反射的次数,如果为1,说明射线与物体相交后不反射,为2表示射线反射一次,以此类推。
Tracer(CRay R, int depth)
{
GVector3 color;
// 计算C = ambient + diffuse + specular
if(TotalTraceDepth == depth)
return color;
else
{
//计算射线和物体交点处的反射射线 Reflect;
GVector3 c = Tracer(Reflect, ++depth);
color += GVector3(color[0]*c[0],color[1]*c[1],color[2]*c[2]);
return color;
}
}
创建一个场景,然后执行代码,可以看到下面的效果。

Fig3 光线追踪渲染的场景1


如果设置Tracer的递归深度大于2的话,就可以看到两个球相互反射的情况。虽然这个光线追踪可以正常的执行,但是画面看起来总觉得缺少点什么。仔细观察你会发现画面虽然有光源,但是物体没有阴影,阴影可以增加场景的真实性。要计算阴影,我们应该从光源的出发,从光源出发的射线和物体如果有交点,而且这条射线与多个物体相交,那么除第一个交点外的后面所有交点都处于阴影中,这点很容易理解。于是,我们需要修改部分代码。
GVector3 Tracer(CRay R, int depth)
{
GVector3 color;
double shade = 1.0
for(/*遍历每一个物体*/)
{
for(/*遍历每一个光源*/)
{
GVector3 L = pObj[k]->getCenter() - Intersection;
double dist = norm(L);
L *= (1.0f / dist);
CRay r = CRay( Intersection,L );
for ( /*遍历每一个物体*/ )
{
CGObject* pr = pObj[s];
if (pr->isIntersected(r, dist)!=MISS)
{
shade = 0;
break;
}
}
}
}
if(shade>0)
{
// 计算C = ambient + diffuse + specular
// 递归计算反射
}
return color*shade;
}
增加了阴影计算后,再运行程序,就能看到下面的效果。
最后我们也可以让地面反射物体,然后再墙上添加很多小球,让画面变得复杂一些,如下图。

Fig5 光线追踪渲染的场景3


总结
这篇文章通过利用面向对象的方法来实现了光线追踪渲染场景。利用面向对象的方法来实现光线追踪使程序的扩展性得到增强,渲染复杂的场景或者复杂的几何物体的时候,或者有很多光源和复杂光照计算的时候,只需要从基类继承,然后利用多态性来实现不同物体的不同渲染方法。
从上面的类图可以看到,利用面向对象的方式可以很容易扩展程序。而且,由于光线追踪的这种结构,不论添加多少物体在场景中,不论物体多么复杂,这种结构总能很好地渲染出正确的画面。

但是,对光线追踪来说,越复杂的场景需要的渲染时间越长。有的时候渲染一帧的画面甚至需要几天的时间。所以好的算法和程序结构对于光线追踪来说是很重要的,可以通过场景管理、使用GPU或CUDA等等技术来提高渲染效率。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0