光线追踪理论
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
光线跟踪的目的是为了模拟自然现象:你能见到各种颜色是因为太阳发射出来的光线,经过各种自然物体的反射或折射后,最终进入你的眼睛。
若我们暂时不去计较其他因素,所有的这些光线都应该是直线。
如图所示,黄色的光直接从太阳射入照相机中;红色的光线在跟场景发生发射后到达照相机,而蓝色的光线被玻璃球折射后命中照相机。
图中没有画出的是那些无法到达观察者的光线,这些光线也是我们不从光源往照相机进行跟踪的原因,而是采用想反的路径。
上图标识的是一种理想情形,因为光线的方向没有影响。
从上面我们得到一个启示:与其等待光源发射一条光线穿过一个目前颜色还是黑色的像素,不如我们自己从照相机发射光线去穿过平面的每个像素,去观察这些光线能击中几何体上的哪些像素。
当然与之对应的光线称之为”secondary rays”,如下图所示:
图中的蓝线是反射光线,绿线是折射光线。
后者比前者要难计算,但也是可以做的。
它的计算主要涉及到折射因子和折射定律。
红线是用来探测光源的。
一般来说,如果你想计算散射光,那么若对于相交点来说光源是可见的,就对点积乘以1,否则就乘以0,将其排除出去。
当然若光源是半可见就乘以.
如果你跟踪从照相机中发出的一条黄线的话,你会发现每条黄线都可以产生出一系列secondary rays:一条反射光线,一条折射光线,并为每个光源产生一条阴影线。
这些光线产生后(除了阴影线外)都可以被视为普通的光线。
这意味着一条反射光线可以再被反射和折射,这种方法叫做“递归光线跟踪”。
每条新产生的光线都增加了它先前光线聚集的地方的颜色,最终每条光线都对最开始由primary ray穿过的像素点的颜色做出了自己的贡献。
为了防止无穷的循环,一般会对递归的层次有一个限制。
Reflections
为了对于一个已知的平面法向量时对一条光线进行反射,我们使用下面这个公式:
这里R是被反射的向量,V是入射光向量,N是平面法向量
这段计算的代码就放到为每个光源计算散射光的循环后面:
结合进这些改进的话,我们就获得了一些灵活性。
一个物体可以有散射,有镜面反射,而我们可以通过调整其权值来设置高亮区的大小。
但这仍是不够的。
散射光是可以的:一个散射物体会向各个方向散射光线,所以它最亮的地方恰好就是物体面向光源那儿。
用法向量和到光源的向量之间的点积就是这个结果。
镜面光就有点不同了,一般来说,镜面高亮区是对光源的散射性反射。
你可以观察下生活中这样的情况:拿一个闪闪发亮的物体,把它放到桌子上并置于灯下,然后移动的视线。
你会注意到当你移动眼睛时物体上的发光点并不是始终保持在同一个位置处。
这是因为反射作用,当你的视点变化时,它的位置就自然发生改变。
Phong于是提出了如下的光照模型,它最大的特点就是将反射向量考虑进来了。
intensity = diffuse * + specular * n
(这里L是从相交点到光源的向量,N是平面法向量,V是视线方向,R是L在表面上的反射向量)。
注意这个公式包含了散射和镜面反射光。
vector3 V = ();//光线方向
vector3 R = L - * DOT( L, N ) * N;
float dot = DOT( V, R );
if(dot > 0)
{
float spec = powf( dot, 20) * prim->GetMaterial()->GetSpecular () * shade;
// add specular component to ray color
a_Acc += spec * light->GetMaterial()->GetColor();
}
增加了Phong光照模型的计算后,产生的结果如下图:
Shadows
最后一种类型的secondary ray是阴影线。
这种光线和其他的不同:它对于产生它的光线的颜色没有贡献;相反,它们经常用来判段一个光源是否可以“看见”一个相交点。
// handle point light source
float shade = ;
if(light->GetType() == Primitive::SPHERE)
{
vector3 L = ((Sphere*)light)->GetCentre() - pi;
float tdist = LENGTH( L );
L *= / tdist);
Ray r = Ray( pi + L * EPSILON, L );
for( int s = 0; s < m_Scene->GetNrPrimitives(); s++ )
{
Primitive* pr = m_Scene->GetPrimitive( s );
if((pr != light) && (pr->Intersect( r, tdist )))
{
shade = 0;
break;
}
}
}
这段代码做的事情应该很熟悉了吧。
测试的结果保存在一个浮点数”shade’中:值为1代表一个可见的光源,为0则是一个要排除的光源。
这里使用一个浮点数是有点奇怪,但到后面我们会增加面光源,而面光源一般是部分可见的。
在那种情况下我们会使用值在0到1之间的’shade’。
另外不得不提的是,上面这段代码并不一定可以找到阴影线与场景中的几何体之间最近的相交点。
这点并不影响:只要能找到比光源近的几何体就可以了。
这样做是出于优化的考虑,因为只要我们尽快找到一个相交点就马上终止循环了。
上图就是我们这篇文章最后得到的效果了,两个光线跟踪的球体,带反射,散射,和镜面光照,还有来自两个光源产生的阴影。
附带一句,这个光线跟踪器有一个bug:阴影测试的结果不仅用于计算散射因子,也用来计算镜面反射因子。
严格来说,这是不对的,作者在后面的文章中会提出一个解决方案的。