【译】光线跟踪:理论与实现(一) 简介

本文介绍光线跟踪的基本原理,包括光线的定义、如何从摄像机发射光线并计算光线与场景元素的交点,以及如何通过考虑光源来计算交点处的颜色。

光线跟踪的目的是为了模拟自然现象:你能见到各种颜色是因为太阳发射出来的光线,经过各种自然物体的反射或折射后,最终进入你的眼睛。若我们暂时不去计较其他因素,所有的这些光线都应该是直线。
2008040902.jpg
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

如图所示,黄色的光直接从太阳射入照相机中;红色的光线在跟场景发生发射后到达照相机,而蓝色的光线被玻璃球折射后命中照相机。图中没有画出的是那些无法到达观察者的光线,这些光线也是我们不从光源往照相机进行跟踪的原因,而是采用想反的路径。上图标识的是一种理想情形,因为光线的方向没有影响。

从上面我们得到一个启示:与其等待光源发射一条光线穿过一个目前颜色还是黑色的像素,不如我们自己从照相机发射光线去穿过平面的每个像素,去观察这些光线能击中几何体上的哪些像素。

None.gif//-----------------------------------------------------------
None.gif
//Rayclassdefinition
None.gif
//-----------------------------------------------------------
None.gif
classRay
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif
public:
ExpandedSubBlockStart.gifContractedSubBlock.gifRay():m_Origin(vector3(
0,0,0)),m_Direction(vector3(0,0,0))dot.gif{};
InBlock.gifRay(vector3
&a_Origin,vector3&a_Dir);
ExpandedSubBlockStart.gifContractedSubBlock.gif
voidSetOrigin(vector3&a_Origin)dot.gif{m_Origin=a_Origin;}
ExpandedSubBlockStart.gifContractedSubBlock.gif
voidSetDirection(vector3&a_Direction)dot.gif{m_Direction=a_Direction;}
ExpandedSubBlockStart.gifContractedSubBlock.gifvector3
&GetOrigin()dot.gif{returnm_Origin;}
ExpandedSubBlockStart.gifContractedSubBlock.gifvector3
&GetDirection()dot.gif{returnm_Direction;}
InBlock.gif
private:
InBlock.gifvector3m_Origin;
//光线的起点
InBlock.gif
vector3m_Direction;//光线的方向
ExpandedBlockEnd.gif
}
;
None.gif

一条光线有它的起点和方向。当从照相机发射光线时,起点一般是一个固定点,并且光线会穿过屏幕表面的像素。

2008040903.jpg

None.gif//-----------------------------------------------------------
None.gif
//Firesraysinthesceneonescanlineatatime,fromleft
None.gif
//toright
None.gif
//-----------------------------------------------------------
None.gif
boolEngine::Render()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif
//renderscene
InBlock.gif
vector3o(0,0,-5);
InBlock.gif
//initializetimer
InBlock.gif
intmsecs=GetTickCount();
InBlock.gif
//resetlastfoundprimitivepointer
InBlock.gif
Primitive*lastprim=0;
InBlock.gif
//renderremaininglines
InBlock.gif
for(inty=m_CurrLine;y<(m_Height-20);y++)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{//逐条扫描线处理
InBlock.gif
m_SX=m_WX1;
InBlock.gif
//renderpixelsforcurrentline
InBlock.gif
for(intx=0;x<m_Width;x++)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{//对当前扫描线上的所有像素点处理
InBlock.gif
//fireprimaryray
InBlock.gif
Coloracc(0,0,0);
InBlock.gifvector3dir
=vector3(m_SX,m_SY,0)-o;//发射出的光线的方向
InBlock.gif
NORMALIZE(dir);
InBlock.gifRayr(o,dir);
InBlock.gif
floatdist;
InBlock.gifPrimitive
*prim=Raytrace(r,acc,1,1.0f,dist);
InBlock.gif
intred=(int)(acc.r*256);
InBlock.gif
intgreen=(int)(acc.g*256);
InBlock.gif
intblue=(int)(acc.b*256);
InBlock.gif
if(red>255)red=255;
InBlock.gif
if(green>255)green=255;
InBlock.gif
if(blue>255)blue=255;
InBlock.gifm_Dest[m_PPos
++]=(red<<16)+(green<<8)+blue;
InBlock.gifm_SX
+=m_DX;
ExpandedSubBlockEnd.gif}

InBlock.gifm_SY
+=m_DY;
InBlock.gif
//seeifwe'vebeenworkingtolongalready
InBlock.gif
if((GetTickCount()-msecs)>100)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif
//returncontroltowindowssothescreengetsupdated
InBlock.gif
m_CurrLine=y+1;
InBlock.gif
returnfalse;
ExpandedSubBlockEnd.gif}

ExpandedSubBlockEnd.gif}

InBlock.gif
//alldone
InBlock.gif
returntrue;
ExpandedBlockEnd.gif}

None.gif

注意这段代码:

None.gifvector3o(0,0,-5);
None.gifvector3dir
=vector3(m_SX,m_SY,0)-o;
None.gifNORMALIZE(dir);
None.gifRayr(o,dir);
None.gif

一条光线起始点在’o’,方向朝向屏幕平面上的一个位置,并且方向进行了单位化处理,从而建立了这条光线。

屏幕平面指的是一个漂浮在虚拟世界的一个矩形,用来表示屏幕。代码中它以原点为中心,宽为8个单位,高为6个单位,这对于800*600的分辨率是合适的。你可以对这个平面做各种处理:若你将它移开照相机,则光线的宽度就变窄,从而物体会在屏幕上变大。若你旋转这个平面(且照相机以它为中心),你会得到虚拟世界的另一种视图。

接下来,我们需要一个场景来进行光线跟踪。一个场景中包含各种元素:如球体和平面等几何物体。你也可以使用三角面片,并且用这些三角面片来构造其他各种元素。

元素SpherePlanePrim是从Primitive继承下来的,每个元素都有一个Material,并且都实现了方法IntersectGetNormal.

None.gif//-----------------------------------------------------------
None.gif
//Sceneclassdefinition
None.gif
//-----------------------------------------------------------
None.gif

None.gif
classScene
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif
public:
ExpandedSubBlockStart.gifContractedSubBlock.gifScene():m_Primitives(
0),m_Primitive(0)dot.gif{};
InBlock.gif
~Scene();
InBlock.gif
voidInitScene();
ExpandedSubBlockStart.gifContractedSubBlock.gif
intGetNrPrimitives()dot.gif{returnm_Primitives;}
ExpandedSubBlockStart.gifContractedSubBlock.gifPrimitive
*GetPrimitive(inta_Idx)dot.gif{returnm_Primitive[a_Idx];}
InBlock.gif
private:
InBlock.gif
intm_Primitives;
InBlock.gifPrimitive
**m_Primitive;//保存的是指向各种元素的指针
ExpandedBlockEnd.gif
}
;
None.gif
None.gif
voidScene::InitScene()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gifm_Primitive
=newPrimitive*[100];//最多100个立体元素
InBlock.gif
//groundplane
InBlock.gif
m_Primitive[0]=newPlanePrim(vector3(0,1,0),4.4f);
InBlock.gifm_Primitive[
0]->SetName("plane");
InBlock.gifm_Primitive[
0]->GetMaterial()->SetReflection(0);
InBlock.gifm_Primitive[
0]->GetMaterial()->SetDiffuse(1.0f);
InBlock.gifm_Primitive[
0]->GetMaterial()->SetColor(Color(0.4f,0.3f,0.3f));
InBlock.gif
//bigsphere
InBlock.gif
m_Primitive[1]=newSphere(vector3(1,-0.8f,3),2.5f);
InBlock.gifm_Primitive[
1]->SetName("bigsphere");
InBlock.gifm_Primitive[
1]->GetMaterial()->SetReflection(0.6f);
InBlock.gifm_Primitive[
1]->GetMaterial()->SetColor(Color(0.7f,0.7f,0.7f));
InBlock.gif
//smallsphere
InBlock.gif
m_Primitive[2]=newSphere(vector3(-5.5f,-0.5,7),2);
InBlock.gifm_Primitive[
2]->SetName("smallsphere");
InBlock.gifm_Primitive[
2]->GetMaterial()->SetReflection(1.0f);
InBlock.gifm_Primitive[
2]->GetMaterial()->SetDiffuse(0.1f);
InBlock.gifm_Primitive[
2]->GetMaterial()->SetColor(Color(0.7f,0.7f,1.0f));
InBlock.gif
//lightsource1
InBlock.gif
m_Primitive[3]=newSphere(vector3(0,5,5),0.1f);
InBlock.gifm_Primitive[
3]->Light(true);
InBlock.gifm_Primitive[
3]->GetMaterial()->SetColor(Color(0.6f,0.6f,0.6f));
InBlock.gif
//lightsource2
InBlock.gif
m_Primitive[4]=newSphere(vector3(2,5,1),0.1f);
InBlock.gifm_Primitive[
4]->Light(true);
InBlock.gifm_Primitive[
4]->GetMaterial()->SetColor(Color(0.7f,0.7f,0.9f));
InBlock.gif
//setnumberofprimitives
InBlock.gif
m_Primitives=5;
ExpandedBlockEnd.gif}

None.gif

这个方法中我们加入了一个地表平面,两个球体以及2个光源。

现在就开始跟踪光线了,首先来看下处理的伪代码:

None.gifForeachpixel
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gifConstructrayfromcamerathroughpixel
InBlock.gifFindfirstprimitivehitbyray
InBlock.gifDeterminecoloratintersectionpoint
InBlock.gifDrawcolor
ExpandedBlockEnd.gif}

None.gif

为了确定光线命中的最近的一个元素,我们必须对其所有可能的交点做测试。

None.gif
None.gif
//-----------------------------------------------------------
None.gif
//Naiveraytracing:Intersectstheraywitheveryprimitive
None.gif
//inthescenetodeterminetheclosestintersection
None.gif
//-----------------------------------------------------------
None.gif
Primitive*Engine::Raytrace(Ray&a_Ray,Color&a_Acc,inta_Depth,floata_RIndex,float&a_Dist)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif
if(a_Depth>TRACEDEPTH)return0;
InBlock.gif
//traceprimaryray
InBlock.gif
a_Dist=1000000.0f;
InBlock.gifvector3pi;
InBlock.gifPrimitive
*prim=0;
InBlock.gif
intresult;
InBlock.gif
//findthenearestintersection
InBlock.gif
for(ints=0;s<m_Scene->GetNrPrimitives();s++)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifPrimitive
*pr=m_Scene->GetPrimitive(s);
InBlock.gif
intres;
InBlock.gif
if(res=pr->Intersect(a_Ray,a_Dist))
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifprim
=pr;
InBlock.gifresult
=res;//0=miss,1=hit,-1=hitfrominsideprimitive
ExpandedSubBlockEnd.gif
}

ExpandedSubBlockEnd.gif}

InBlock.gif
//nohit,terminateray
InBlock.gif
if(!prim)return0;
InBlock.gif
//handleintersection
InBlock.gif
if(prim->IsLight())
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif
//wehitalight,stoptracing
InBlock.gif
a_Acc=Color(1,1,1);
ExpandedSubBlockEnd.gif}

InBlock.gif
else
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif
//determinecoloratpointofintersection
InBlock.gif
pi=a_Ray.GetOrigin()+a_Ray.GetDirection()*a_Dist;
InBlock.gif
//tracelights
InBlock.gif
for(intl=0;l<m_Scene->GetNrPrimitives();l++)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifPrimitive
*p=m_Scene->GetPrimitive(l);
InBlock.gif
if(p->IsLight())
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifPrimitive
*light=p;
InBlock.gif
//calculatediffuseshading
InBlock.gif
vector3L=((Sphere*)light)->GetCentre()-pi;
InBlock.gifNORMALIZE(L);
InBlock.gifvector3N
=prim->GetNormal(pi);
InBlock.gif
if(prim->GetMaterial()->GetDiffuse()>0)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif
floatdot=DOT(N,L);
InBlock.gif
if(dot>0)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif
floatdiff=dot*prim->GetMaterial()->GetDiffuse();
InBlock.gif
//adddiffusecomponenttoraycolor
InBlock.gif
a_Acc+=diff*prim->GetMaterial()->GetColor()*light->GetMaterial()->GetColor();
ExpandedSubBlockEnd.gif}

ExpandedSubBlockEnd.gif}

ExpandedSubBlockEnd.gif}

ExpandedSubBlockEnd.gif}

ExpandedSubBlockEnd.gif}

InBlock.gif
//returnpointertoprimitivehitbyprimaryray
InBlock.gif
returnprim;
ExpandedBlockEnd.gif}

None.gif

其中这段代码:

None.gif
None.gif
//findthenearestintersection
None.gif
for(ints=0;s<m_Scene->GetNrPrimitives();s++)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{//对所有的元素做测试
InBlock.gif
Primitive*pr=m_Scene->GetPrimitive(s);
InBlock.gif
intres;
InBlock.gif
if(res=pr->Intersect(a_Ray,a_Dist))
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{//找到第一个命中的元素
InBlock.gif
prim=pr;
InBlock.gifresult
=res;//0=miss,1=hit,-1=hitfrominsideprimitive
ExpandedSubBlockEnd.gif
}

ExpandedBlockEnd.gif}

None.gif

对场景中的所以元素做循环处理,为每个元素调用其Intersect方法,这个方法以一条光线为参数,返回一个整数表明是命中还是没有命中,以及相交的距离是在体内还是体外。除此以外还会记录下最近相交的记录。

一旦我们知道光线命中的是那个元素,那么就可以来计算光线的颜色了。若只是简单地使用元素的材质颜色就太简单了,并且结果颜色也很枯燥。因此,我们使用两个点光源来计算散射阴影。

None.gif//determinecoloratpointofintersection
None.gif
pi=a_Ray.GetOrigin()+a_Ray.GetDirection()*a_Dist;
None.gif
//tracelights
None.gif
for(intl=0;l<m_Scene->GetNrPrimitives();l++)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gifPrimitive
*p=m_Scene->GetPrimitive(l);
InBlock.gif
if(p->IsLight())
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifPrimitive
*light=p;
InBlock.gif
//calculatediffuseshading
InBlock.gif
vector3L=((Sphere*)light)->GetCentre()-pi;
InBlock.gifNORMALIZE(L);
InBlock.gifvector3N
=prim->GetNormal(pi);
InBlock.gif
if(prim->GetMaterial()->GetDiffuse()>0)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif
floatdot=DOT(N,L);//点积
InBlock.gif
if(dot>0)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif
floatdiff=dot*prim->GetMaterial()->GetDiffuse();
InBlock.gif
//adddiffusecomponenttoraycolor
InBlock.gif
a_Acc+=diff*prim->GetMaterial()->GetColor()*light->GetMaterial()->GetColor();
ExpandedSubBlockEnd.gif}

ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif

这段代码计算从相交点(pi)到光源(L)的向量,并用这个向量和相交点的单位向量的叉积来计算出光源的亮度。这个计算出的亮度是元素朝向光源的那一点被光源照亮,而其他点就是阴暗的了。叉积大于0为了防止面与光源反向。

2008040901.jpg

好了,这一篇就到这里了,没有反射,没有折射,更没有加入阴影,这些东东在后续的文章中会慢慢加入的,而这只是最简单的一个光线跟踪而已

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值