作者在这一篇中将解释如何去跟踪折射光线。这将涉及到在相交点处产生新的光线,并且计算新光线的方向。此外,作者还将运用Lambert-Beer 定律来解释光线在物体内部的吸收情况。最后作者将展示如何加入反锯齿的效果,并且如何对光线跟踪器进行加速优化。
Introduction
作者在这一篇中将解释如何去跟踪折射光线。这将涉及到在相交点处产生新的光线,并且计算新光线的方向。此外,作者还将运用Lambert-Beer 定律来解释光线在物体内部的吸收情况。最后作者将展示如何加入反锯齿的效果,并且如何对光线跟踪器进行加速优化。
Refractions
折射的情况如图1所示,注意观察光线在几何体表面是如何改变方向的,又是如何穿过几何体后面那个点的。在这个点后面的物体会看起来是倒转的镜像。
几何体表面处光线的弯曲程度取决于两种物质的折射率:光线进入几何体前所在的物质的折射率,构成几何体的物质的折射率。例如:空气和真空的折射率为1.0,20摄氏度的水的折射率为1.33。
![2008041302.jpg](/images/cnblogs_com/phinecos/20080415/2008041302.jpg)
下面这段代码应该让你感到很熟悉了吧。它就是构造折射光线,递归地跟踪它,并将结果颜色值加入到产生折射光的光线中去。有一点值得注意:法向量乘以了一个值’result’。这个值是对每个几何体的相交测试时获得的,值是1或0,分别表示是否命中。当然,还有第3个选择:-1,这表示是从几何体内部命中的。这点也是很重要的:如果一个光线命是从外部命中一个几何体的,那其实是没有命中这个几何体,而是它附件的物质。因此,其法向量应该取反。
// calculate refraction
float refr = prim->GetMaterial()->GetRefraction();
if ((refr > 0) && (a_Depth < TRACEDEPTH))
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
float rindex = prim->GetMaterial()->GetRefrIndex();
float n = a_RIndex / rindex;
vector3 N = prim->GetNormal( pi ) * (float)result;
float cosI = -DOT( N, a_Ray.GetDirection() );
float cosT2 = 1.0f - n * n * (1.0f - cosI * cosI);
if (cosT2 > 0.0f)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
vector3 T = (n * a_Ray.GetDirection()) + (n * cosI - sqrtf( cosT2 )) * N;
Color rcol( 0, 0, 0 );
float dist;
Raytrace( Ray( pi + T * EPSILON, T ), rcol, a_Depth + 1, rindex, dist );
a_Acc += rcol * transparency;
}
}
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
下图就是加入折射的效果图:
![2008041303.jpg](/images/cnblogs_com/phinecos/20080415/2008041303.jpg)
毫无疑问,你会注意到现在光线跟踪器很慢的,这是因为每条光线要与每个几何体进行相交测试来找出最近的相交点。当然,有很多方法可以对其进行改进,作者在后面的文章中会使用一种空间细分的方法来对相交测试的数量进行限制的。
Lambert-Beer定律
上面的图中,你会发现球是蓝色的,因此折射图的颜色也是稍微偏蓝色的。这是因为折射光返回的颜色值会与几何体颜色相乘。这在反射,散射以及镜面光的计算中都有这种情况。
现在让我们来想象有这么一个水池,里面充满了带颜色的物质(比如说水里面混合着蓝墨水)。池子的浅处大概10cm深,深处呢有1米深。如果你从上往底下看,很明显可以看到在较深的那端的底部受到颜色的影响会比浅处的要大。这种效应就叫Beer定律。
Beer定律可以用下列公式表示:
light_out = light_in * e(e * c * d)
这个公式主要是用来计算溶解在水中的物质的光吸收度的。’e’是一个常量,表明溶剂的吸收度(准确来说,是单位为L/mol*cm的摩尔吸光率)。’c’是溶质的数量,单位mol/L.’d’是光线的路径长度。一般我们可以简化公式如下:
light_out = light_in * e(d * C)
这个d是路径长度,C是一个常量,表示物质的密度,减小它的值会使得光线在穿越物体时的时间增大。
吸收及透明度的计算需要以颜色因子来单位进行逐个计算。
// apply Beer's law
Color absorbance = prim->GetMaterial()->GetColor() * 0.15f * -dist;
Color transparency = Color( expf( absorbance.r ), expf( absorbance.g ), expf( absorbance.b ) );
a_Acc += rcol * transparency;
下面是效果图:
![2008041301.jpg](/images/cnblogs_com/phinecos/20080415/2008041301.jpg)
对于目前程序中使用的场景来说,应用这个定律对于画面的质量没有多大影响。不过一旦你开始使用复杂的场景,情况就大不一样了。
很多光线跟踪器都使用下述简单的方法:每个物体都有一个“反射”变量,它会与折射光返回的颜色值相乘,同时还有一个“折射”变量,会与折射光返回的颜色值相乘。然而对于折射光来说,这并不正确:每条折射光线在进入和退出几何体时会被计算两次。而且,从一个薄的物体穿过与穿过一个厚的物体有相同的衰减度。最大的问题更在于我们直观上会感觉不对:光线密度在几何体表面没有减小,而只会在几何体内部减小。
SuperSampling
假设我们使用下列代码来替换产生光线处的代码:
for ( int tx = 0; tx < 4; tx++ ) for ( int ty = 0; ty < 4; ty++ )
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
vector3 dir = vector3( m_SX + m_DX * tx / 4.0f, m_SY + m_DY * ty / 4.0f, 0 ) - o;
NORMALIZE( dir );
Ray r( o, dir );
float dist;
Primitive* prim = Raytrace( r, acc, 1, 1.0f, dist );
}
![](/Images/OutliningIndicators/None.gif)
int red = (int)(acc.r * 16);
int green = (int)(acc.g * 16);
int blue = (int)(acc.b * 16);
![](/Images/OutliningIndicators/None.gif)
这段代码作用是为每个像素点发射16条光线,因此结果图像具有抗锯齿效果,但缺点就是耗时太多。
也许你注意到了光线跟踪器返回的是指向几何体的指针,它指向被primary ray命中的几何体。我们做如下修改,代码的性能就大幅提升:
// fire primary rays
Color acc( 0, 0, 0 );
vector3 dir = vector3( m_SX, m_SY, 0 ) - o;
NORMALIZE( dir );
Ray r( o, dir );
float dist;
Primitive* prim = Raytrace( r, acc, 1, 1.0f, dist );
int red, green, blue;
if (prim != lastprim)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
lastprim = prim;
Color acc( 0, 0, 0 );
for ( int tx = -1; tx < 2; tx++ ) for ( int ty = -1; ty < 2; ty++ )
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
vector3 dir = vector3( m_SX + m_DX * tx / 2.0f, m_SY + m_DY * ty / 2.0f, 0 ) - o;
NORMALIZE( dir );
Ray r( o, dir );
float dist;
Primitive* prim = Raytrace( r, acc, 1, 1.0f, dist );
}
red = (int)(acc.r * (256 / 9));
green = (int)(acc.g * (256 / 9));
blue = (int)(acc.b * (256 / 9));
}
![](/Images/OutliningIndicators/None.gif)
else
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
red = (int)(acc.r * 256);
green = (int)(acc.g * 256);
blue = (int)(acc.b * 256);
}
if (red > 255) red = 255;
if (green > 255) green = 255;
![](/Images/OutliningIndicators/None.gif)
if (blue > 255) blue = 255;
![](/Images/OutliningIndicators/None.gif)
ok,it’s over.在下一篇中作者将介绍“空间分割”思想的运用。
原文链接:http://www.devmaster.net/articles/raytracing_series/part3.php