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

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

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

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

None.gif// -----------------------------------------------------------
None.gif
// Ray class definition
None.gif
// -----------------------------------------------------------
None.gif
class Ray
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif
public:
ExpandedSubBlockStart.gifContractedSubBlock.gif    Ray() : m_Origin( vector3( 
000 ) ), m_Direction( vector3( 000 ) ) dot.gif{};
InBlock.gif    Ray( vector3
& a_Origin, vector3& a_Dir );
ExpandedSubBlockStart.gifContractedSubBlock.gif    
void SetOrigin( vector3& a_Origin ) dot.gif{ m_Origin = a_Origin; }
ExpandedSubBlockStart.gifContractedSubBlock.gif    
void SetDirection( vector3& a_Direction ) dot.gif{ m_Direction = a_Direction; }
ExpandedSubBlockStart.gifContractedSubBlock.gif    vector3
& GetOrigin() dot.gifreturn m_Origin; }
ExpandedSubBlockStart.gifContractedSubBlock.gif    vector3
& GetDirection() dot.gifreturn m_Direction; }
InBlock.gif
private:
InBlock.gif    vector3 m_Origin;
//光线的起点
InBlock.gif
    vector3 m_Direction;//光线的方向
ExpandedBlockEnd.gif
}
;
None.gif

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

2008040903.jpg

None.gif// -----------------------------------------------------------
None.gif
// Fires rays in the scene one scanline at a time, from left
None.gif
// to right
None.gif
// -----------------------------------------------------------
None.gif
bool Engine::Render()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
// render scene
InBlock.gif
    vector3 o( 00-5 );
InBlock.gif    
// initialize timer
InBlock.gif
    int msecs = GetTickCount();
InBlock.gif    
// reset last found primitive pointer
InBlock.gif
    Primitive* lastprim = 0;
InBlock.gif    
// render remaining lines
InBlock.gif
    for ( int y = m_CurrLine; y < (m_Height - 20); y++ )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif//逐条扫描线处理
InBlock.gif
        m_SX = m_WX1;
InBlock.gif        
// render pixels for current line
InBlock.gif
        for ( int x = 0; x < m_Width; x++ )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{//对当前扫描线上的所有像素点处理
InBlock.gif            
// fire primary ray
InBlock.gif
            Color acc( 000 );
InBlock.gif            vector3 dir 
= vector3( m_SX, m_SY, 0 ) - o; //发射出的光线的方向
InBlock.gif
            NORMALIZE( dir );
InBlock.gif            Ray r( o, dir );
InBlock.gif            
float dist;
InBlock.gif            Primitive
* prim = Raytrace( r, acc, 11.0f, dist );
InBlock.gif            
int red = (int)(acc.r * 256);
InBlock.gif            
int green = (int)(acc.g * 256);
InBlock.gif            
int blue = (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.gif            m_Dest[m_PPos
++= (red << 16+ (green << 8+ blue;
InBlock.gif            m_SX 
+= m_DX;
ExpandedSubBlockEnd.gif        }

InBlock.gif        m_SY 
+= m_DY;
InBlock.gif        
// see if we've been working to long already
InBlock.gif
        if ((GetTickCount() - msecs) > 100
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
// return control to windows so the screen gets updated
InBlock.gif
            m_CurrLine = y + 1;
InBlock.gif            
return false;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif    
// all done
InBlock.gif
    return true;
ExpandedBlockEnd.gif}

None.gif

注意这段代码:

None.gifvector3 o( 00-5 );
None.gifvector3 dir 
= vector3( m_SX, m_SY, 0 ) - o;
None.gifNORMALIZE( dir );
None.gifRay r( o, dir );
None.gif

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

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

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

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

None.gif    // -----------------------------------------------------------
None.gif
// Scene class definition
None.gif
// -----------------------------------------------------------
None.gif

None.gif
class Scene
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif
public:
ExpandedSubBlockStart.gifContractedSubBlock.gif    Scene() : m_Primitives( 
0 ), m_Primitive( 0 ) dot.gif{};
InBlock.gif    
~Scene();
InBlock.gif    
void InitScene();
ExpandedSubBlockStart.gifContractedSubBlock.gif    
int GetNrPrimitives() dot.gifreturn m_Primitives; }
ExpandedSubBlockStart.gifContractedSubBlock.gif    Primitive
* GetPrimitive( int a_Idx ) dot.gifreturn m_Primitive[a_Idx]; }
InBlock.gif
private:
InBlock.gif    
int m_Primitives;
InBlock.gif    Primitive
** m_Primitive;//保存的是指向各种元素的指针
ExpandedBlockEnd.gif
}
;
None.gif
None.gif
void Scene::InitScene()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    m_Primitive 
= new Primitive*[100];//最多100个立体元素
InBlock.gif    
// ground plane
InBlock.gif
    m_Primitive[0= new PlanePrim( vector3( 010 ), 4.4f );
InBlock.gif    m_Primitive[
0]->SetName( "plane" );
InBlock.gif    m_Primitive[
0]->GetMaterial()->SetReflection( 0 );
InBlock.gif    m_Primitive[
0]->GetMaterial()->SetDiffuse( 1.0f );
InBlock.gif    m_Primitive[
0]->GetMaterial()->SetColor( Color( 0.4f0.3f0.3f ) );
InBlock.gif    
// big sphere
InBlock.gif
    m_Primitive[1= new Sphere( vector3( 1-0.8f3 ), 2.5f );
InBlock.gif    m_Primitive[
1]->SetName( "big sphere" );
InBlock.gif    m_Primitive[
1]->GetMaterial()->SetReflection( 0.6f );
InBlock.gif    m_Primitive[
1]->GetMaterial()->SetColor( Color( 0.7f0.7f0.7f ) );
InBlock.gif    
// small sphere
InBlock.gif
    m_Primitive[2= new Sphere( vector3( -5.5f-0.57 ), 2 );
InBlock.gif    m_Primitive[
2]->SetName( "small sphere" );
InBlock.gif    m_Primitive[
2]->GetMaterial()->SetReflection( 1.0f );
InBlock.gif    m_Primitive[
2]->GetMaterial()->SetDiffuse( 0.1f );
InBlock.gif    m_Primitive[
2]->GetMaterial()->SetColor( Color( 0.7f0.7f1.0f ) );
InBlock.gif    
// light source 1
InBlock.gif
    m_Primitive[3= new Sphere( vector3( 055 ), 0.1f );
InBlock.gif    m_Primitive[
3]->Light( true );
InBlock.gif    m_Primitive[
3]->GetMaterial()->SetColor( Color( 0.6f0.6f0.6f ) );
InBlock.gif    
// light source 2
InBlock.gif
    m_Primitive[4= new Sphere( vector3( 251 ), 0.1f );
InBlock.gif    m_Primitive[
4]->Light( true );
InBlock.gif    m_Primitive[
4]->GetMaterial()->SetColor( Color( 0.7f0.7f0.9f ) );
InBlock.gif    
// set number of primitives
InBlock.gif
    m_Primitives = 5;
ExpandedBlockEnd.gif}

None.gif

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

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

None.gifFor each pixel
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    Construct ray from camera through pixel
InBlock.gif    Find first primitive hit by ray
InBlock.gif    Determine color at intersection point
InBlock.gif    Draw color
ExpandedBlockEnd.gif}
 
None.gif

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

None.gif
None.gif
// -----------------------------------------------------------
None.gif
// Naive ray tracing: Intersects the ray with every primitive
None.gif
// in the scene to determine the closest intersection
None.gif
// -----------------------------------------------------------
None.gif
Primitive* Engine::Raytrace( Ray& a_Ray, Color& a_Acc, int a_Depth, float a_RIndex, float& a_Dist )
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
if (a_Depth > TRACEDEPTH) return 0;
InBlock.gif    
// trace primary ray
InBlock.gif
    a_Dist = 1000000.0f;
InBlock.gif    vector3 pi;
InBlock.gif    Primitive
* prim = 0;
InBlock.gif    
int result;
InBlock.gif    
// find the nearest intersection
InBlock.gif
    for ( int s = 0; s < m_Scene->GetNrPrimitives(); s++ )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        Primitive
* pr = m_Scene->GetPrimitive( s );
InBlock.gif        
int res;
InBlock.gif        
if (res = pr->Intersect( a_Ray, a_Dist )) 
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            prim 
= pr;
InBlock.gif            result 
= res; // 0 = miss, 1 = hit, -1 = hit from inside primitive
ExpandedSubBlockEnd.gif
        }

ExpandedSubBlockEnd.gif    }

InBlock.gif    
// no hit, terminate ray
InBlock.gif
    if (!prim) return 0;
InBlock.gif    
// handle intersection
InBlock.gif
    if (prim->IsLight())
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// we hit a light, stop tracing
InBlock.gif
        a_Acc = Color( 111 );
ExpandedSubBlockEnd.gif    }

InBlock.gif    
else
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// determine color at point of intersection
InBlock.gif
        pi = a_Ray.GetOrigin() + a_Ray.GetDirection() * a_Dist;
InBlock.gif        
// trace lights
InBlock.gif
        for ( int l = 0; l < m_Scene->GetNrPrimitives(); l++ )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            Primitive
* p = m_Scene->GetPrimitive( l );
InBlock.gif            
if (p->IsLight()) 
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                Primitive
* light = p;
InBlock.gif                
// calculate diffuse shading
InBlock.gif
                vector3 L = ((Sphere*)light)->GetCentre() - pi;
InBlock.gif                NORMALIZE( L );
InBlock.gif                vector3 N 
= prim->GetNormal( pi );
InBlock.gif                
if (prim->GetMaterial()->GetDiffuse() > 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    
float dot = DOT( N, L );
InBlock.gif                    
if (dot > 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
dot.gif{
InBlock.gif                        
float diff = dot * prim->GetMaterial()->GetDiffuse();
InBlock.gif                        
// add diffuse component to ray color
InBlock.gif
                        a_Acc += diff * prim->GetMaterial()->GetColor() * light->GetMaterial()->GetColor();
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif    
// return pointer to primitive hit by primary ray
InBlock.gif
    return prim;
ExpandedBlockEnd.gif}

None.gif

其中这段代码:

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

ExpandedBlockEnd.gif    }

None.gif

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

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

None.gif// determine color at point of intersection
None.gif
    pi = a_Ray.GetOrigin() + a_Ray.GetDirection() * a_Dist;
None.gif    
// trace lights
None.gif
    for ( int l = 0; l < m_Scene->GetNrPrimitives(); l++ )
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif{
InBlock.gif        Primitive
* p = m_Scene->GetPrimitive( l );
InBlock.gif        
if (p->IsLight()) 
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            Primitive
* light = p;
InBlock.gif            
// calculate diffuse shading
InBlock.gif
            vector3 L = ((Sphere*)light)->GetCentre() - pi;
InBlock.gif            NORMALIZE( L );
InBlock.gif            vector3 N 
= prim->GetNormal( pi );
InBlock.gif            
if (prim->GetMaterial()->GetDiffuse() > 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
float dot = DOT( N, L );//点积
InBlock.gif
                if (dot > 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    
float diff = dot * prim->GetMaterial()->GetDiffuse();
InBlock.gif                    
// add diffuse component to ray color
InBlock.gif
                    a_Acc += diff * prim->GetMaterial()->GetColor() * light->GetMaterial()->GetColor();
ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

ExpandedBlockEnd.gif        }

None.gif

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

2008040901.jpg

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值