【译】光线跟踪:理论与实现(三) 折射与Lambert-Beer 定律

本文深入讲解光线追踪技术,包括折射光线的跟踪、Lambert-Beer定律的应用、以及如何实现抗锯齿效果。通过具体代码示例展示了如何计算折射光线的方向、如何模拟光线在物体内部的吸收情况,并介绍了几种加速光线追踪的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Introduction<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者在这一篇中将解释如何去跟踪折射光线。这将涉及到在相交点处产生新的光线,并且计算新光线的方向。此外,作者还将运用Lambert-Beer 定律来解释光线在物体内部的吸收情况。最后作者将展示如何加入反锯齿的效果,并且如何对光线跟踪器进行加速优化。

Refractions

折射的情况如图1所示,注意观察光线在几何体表面是如何改变方向的,又是如何穿过几何体后面那个点的。在这个点后面的物体会看起来是倒转的镜像。

几何体表面处光线的弯曲程度取决于两种物质的折射率:光线进入几何体前所在的物质的折射率,构成几何体的物质的折射率。例如:空气和真空的折射率为1.020摄氏度的水的折射率为1.33
2008041302.jpg

下面这段代码应该让你感到很熟悉了吧。它就是构造折射光线,递归地跟踪它,并将结果颜色值加入到产生折射光的光线中去。有一点值得注意:法向量乘以了一个值’result’。这个值是对每个几何体的相交测试时获得的,值是10,分别表示是否命中。当然,还有第3个选择:-1,这表示是从几何体内部命中的。这点也是很重要的:如果一个光线命是从外部命中一个几何体的,那其实是没有命中这个几何体,而是它附件的物质。因此,其法向量应该取反。

None.gif // calculaterefraction
None.gif
float refr = prim -> GetMaterial() -> GetRefraction();
None.gif
if ((refr > 0 ) && (a_Depth < TRACEDEPTH))
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
floatrindex=prim->GetMaterial()->GetRefrIndex();
InBlock.gif
floatn=a_RIndex/rindex;
InBlock.gifvector3N
=prim->GetNormal(pi)*(float)result;
InBlock.gif
floatcosI=-DOT(N,a_Ray.GetDirection());
InBlock.gif
floatcosT2=1.0f-n*n*(1.0f-cosI*cosI);
InBlock.gif
if(cosT2>0.0f)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifvector3T
=(n*a_Ray.GetDirection())+(n*cosI-sqrtf(cosT2))*N;
InBlock.gifColorrcol(
0,0,0);
InBlock.gif
floatdist;
InBlock.gifRaytrace(Ray(pi
+T*EPSILON,T),rcol,a_Depth+1,rindex,dist);
InBlock.gifa_Acc
+=rcol*transparency;
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif

下图就是加入折射的效果图:
2008041303.jpg

毫无疑问,你会注意到现在光线跟踪器很慢的,这是因为每条光线要与每个几何体进行相交测试来找出最近的相交点。当然,有很多方法可以对其进行改进,作者在后面的文章中会使用一种空间细分的方法来对相交测试的数量进行限制的。

Lambert-Beer定律

上面的图中,你会发现球是蓝色的,因此折射图的颜色也是稍微偏蓝色的。这是因为折射光返回的颜色值会与几何体颜色相乘。这在反射,散射以及镜面光的计算中都有这种情况。

现在让我们来想象有这么一个水池,里面充满了带颜色的物质(比如说水里面混合着蓝墨水)。池子的浅处大概<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chmetcnv tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="10" unitname="cm" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">10cm</span></chmetcnv>深,深处呢有<chmetcnv tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="1" unitname="米" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">1</span><span style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana">米</span></chmetcnv>深。如果你从上往底下看,很明显可以看到在较深的那端的底部受到颜色的影响会比浅处的要大。这种效应就叫Beer定律。

Beer定律可以用下列公式表示:

None.gif light_out = light_in * e(e * c * d)

这个公式主要是用来计算溶解在水中的物质的光吸收度的。’e’是一个常量,表明溶剂的吸收度(准确来说,是单位为L/mol*cm的摩尔吸光率)。’c’是溶质的数量,单位mol/L.’d’是光线的路径长度。一般我们可以简化公式如下:

None.gif light_out = light_in * e(d * C)

这个d是路径长度,C是一个常量,表示物质的密度,减小它的值会使得光线在穿越物体时的时间增大。

吸收及透明度的计算需要以颜色因子来单位进行逐个计算。

None.gif // applyBeer'slaw
None.gif
Colorabsorbance = prim -> GetMaterial() -> GetColor() * 0.15f * - dist;
None.gifColortransparency
= Color(expf(absorbance.r),expf(absorbance.g),expf(absorbance.b));
None.gifa_Acc
+= rcol * transparency;

下面是效果图:
2008041301.jpg
对于目前程序中使用的场景来说,应用这个定律对于画面的质量没有多大影响。不过一旦你开始使用复杂的场景,情况就大不一样了。

很多光线跟踪器都使用下述简单的方法:每个物体都有一个反射变量,它会与折射光返回的颜色值相乘,同时还有一个折射变量,会与折射光返回的颜色值相乘。然而对于折射光来说,这并不正确:每条折射光线在进入和退出几何体时会被计算两次。而且,从一个薄的物体穿过与穿过一个厚的物体有相同的衰减度。最大的问题更在于我们直观上会感觉不对:光线密度在几何体表面没有减小,而只会在几何体内部减小。

SuperSampling

假设我们使用下列代码来替换产生光线处的代码:

None.gif for ( int tx = 0 ;tx < 4 ;tx ++ ) for ( int ty = 0 ;ty < 4 ;ty ++ )
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gifvector3dir
=vector3(m_SX+m_DX*tx/4.0f,m_SY+m_DY*ty/4.0f,0)-o;
InBlock.gifNORMALIZE(dir);
InBlock.gifRayr(o,dir);
InBlock.gif
floatdist;
InBlock.gifPrimitive
*prim=Raytrace(r,acc,1,1.0f,dist);
ExpandedBlockEnd.gif}

None.gif
None.gif
int red = ( int )(acc.r * 16 );
None.gif
int green = ( int )(acc.g * 16 );
None.gif
int blue = ( int )(acc.b * 16 );
None.gif

这段代码作用是为每个像素点发射16条光线,因此结果图像具有抗锯齿效果,但缺点就是耗时太多。

也许你注意到了光线跟踪器返回的是指向几何体的指针,它指向被primary ray命中的几何体。我们做如下修改,代码的性能就大幅提升:

None.gif // fireprimaryrays
None.gif
Coloracc( 0 , 0 , 0 );
None.gifvector3dir
= vector3(m_SX,m_SY, 0 ) - o;
None.gifNORMALIZE(dir);
None.gifRayr(o,dir);
None.gif
float dist;
None.gifPrimitive
* prim = Raytrace(r,acc, 1 , 1.0f ,dist);
None.gif
int red,green,blue;
None.gif
if (prim != lastprim)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.giflastprim
=prim;
InBlock.gifColoracc(
0,0,0);
InBlock.gif
for(inttx=-1;tx<2;tx++)for(intty=-1;ty<2;ty++)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifvector3dir
=vector3(m_SX+m_DX*tx/2.0f,m_SY+m_DY*ty/2.0f,0)-o;
InBlock.gifNORMALIZE(dir);
InBlock.gifRayr(o,dir);
InBlock.gif
floatdist;
InBlock.gifPrimitive
*prim=Raytrace(r,acc,1,1.0f,dist);
ExpandedSubBlockEnd.gif}

InBlock.gifred
=(int)(acc.r*(256/9));
InBlock.gifgreen
=(int)(acc.g*(256/9));
InBlock.gifblue
=(int)(acc.b*(256/9));
ExpandedBlockEnd.gif}

None.gif
None.gif
else
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gifred
=(int)(acc.r*256);
InBlock.gifgreen
=(int)(acc.g*256);
InBlock.gifblue
=(int)(acc.b*256);
ExpandedBlockEnd.gif}

None.gif
if (red > 255 )red = 255 ;
None.gif
if (green > 255 )green = 255 ;
None.gif
None.gif
if (blue > 255 )blue = 255 ;
None.gif

ok,it’s over.在下一篇中作者将介绍“空间分割”思想的运用。

原文链接:http://www.devmaster.net/articles/raytracing_series/part3.php

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值