光线跟踪理论&实践 第一课——介绍

Raytracing: Theory & Implementation Part 1, Introduction

编译自原文:http://www.devmaster.net/articles/raytracing_series/part1.php
代码下载:
http://www.devmaster.net/articles/raytracing_series/raytracer1.zip

介绍



在这一系列文章中,我将带你领略光线跟踪的魅力,首先我们会从一些简单元素出发(球体、平面、反射),让你来熟悉基础概念。

然后我们会加入折射、面光源和反走样,进一步改善绘制效果。而质量提高的代价是绘制速度会大大减慢,因此我们还需要添加简单的空间划分来进行加速。

最后,我将会介绍关于全局光照明的内容。

基础

光线跟踪的出发点来源于模仿自然现象,眼睛通常观察到的颜色,是由太阳发出,经过各种反射,最后进入人眼。如下图所示:

 

图 1: 从太阳到人眼的光路图

我在图中绘制了一些光线,黄色的光线直接从光源出发达到相机。红色的光线在进入相机之前经过了球迷的反弹。而蓝色的光线是穿过了球体,并引起了传播方向的变化。

图中没有画出由光源发出但是没有到达观察者的光线,因为这些光线对观察者没有影响。因此光线跟踪使用了这样一种方法,让光线从观察者出发,看看它们都击中了什么物体。

 

代码结构

在文章底部,有一个源代码的链接(VC6),其中包含了一些基础的代码,以及核心代码raytracer,包含在raytracer.cpp/.h和scene.cpp/.h中。向量计算,pi值,屏幕分辨率等定义在文件common.h中。

产生射线

在 raytracer.h中, 可以看到光线的类有如下定义:

class

Ray

{

public
:

Ray() : m_Origin( vector3( 0, 0, 0 ) ), m_Direction( vector3( 0, 0, 0 ) ) {};

Ray( vector3& a_Origin, vector3& a_Dir );



private
:

vector3 m_Origin;

vector3 m_Direction;

};

每个射线包含一个原点和一个方向,由于射线都是从相机发出,所以原点通常都是一个固定点,从原点出发设想屏幕所在的每个像素点。在二维上看就是下图的效果:

 

图 2: 从相机出发生成射线

raytracer.cpp中生成射线的代码如下:

vector3 o( 0, 0, -5 );

vector3 dir = vector3( m_SX, m_SY, 0 ) - o;

NORMALIZE( dir );

Ray r( o, dir );

在这段代码中,一条射线从原点o出发,指向屏幕平面的一个点,方向向量被单位化,这条射线就生成了。

这里的屏幕平面是指虚拟空间中的一个矩形,用来代表屏幕。它的宽度为8个单位,长为6个单位,正好适合于800*600的分辨率。当然你也可以在空间中移动、旋转这个矩形。

创建场景

接下来,我们需要创建一个绘制所需要的场景。通常会包括一些几何体像球体、平板,你也可以选择使用三角形,并用三角形来构造其他所有几何题。

看一下scene.h中的类定义, 'Sphere' 和 'PlanePrim' 都继承自 'Primitive',都包含一个'Material'属性,并实现了对应的求法线和求交算法。

场景本身被保存在一个'Scene'的类中,场景的初始化代码如下:

void

Scene::InitScene()

{

m_Primitive = new
Primitive*[100];

// ground plane


m_Primitive[0] = new
PlanePrim( vector3( 0, 1, 0 ), 4.4f );

m_Primitive[0]->SetName( "plane" );

m_Primitive[0]->GetMaterial()->SetReflection( 0 );

m_Primitive[0]->GetMaterial()->SetDiffuse( 1.0f );

m_Primitive[0]->GetMaterial()->SetColor( Color( 0.4f, 0.3f, 0.3f ) );

// big sphere




m_Primitive[1] = new
Sphere( vector3( 1, -0.8f, 3 ), 2.5f );

m_Primitive[1]->SetName( "big sphere" );

m_Primitive[1]->GetMaterial()->SetReflection( 0.6f );

m_Primitive[1]->GetMaterial()->SetColor( Color( 0.7f, 0.7f, 0.7f ) );

// small sphere


m_Primitive[2] = new
Sphere( vector3( -5.5f, -0.5, 7 ), 2 );

m_Primitive[2]->SetName( "small sphere" );

m_Primitive[2]->GetMaterial()->SetReflection( 1.0f );

m_Primitive[2]->GetMaterial()->SetDiffuse( 0.1f );

m_Primitive[2]->GetMaterial()->SetColor( Color( 0.7f, 0.7f, 1.0f ) );

// light source 1




m_Primitive[3] = new
Sphere( vector3( 0, 5, 5 ), 0.1f );

m_Primitive[3]->Light( true
);

m_Primitive[3]->GetMaterial()->SetColor( Color( 0.6f, 0.6f, 0.6f ) );

// light source 2


m_Primitive[4] = new
Sphere( vector3( 2, 5, 1 ), 0.1f );

m_Primitive[4]->Light( true
);

m_Primitive[4]->GetMaterial()->SetColor( Color( 0.7f, 0.7f, 0.9f ) );

// set number of primitives




m_Primitives = 5;

}

这段代码给场景中增加了一组平板、两个球体,以及两个光源。这里的光源只是被标注为“光源”的球体。

光线跟踪

现在光线跟踪前的准备工作已经就绪,我们可以来看一下这个过程的伪代码:



For each pixel

{

Construct ray from camera through pixel

Find first primitive hit by ray

Determine color at intersection point

Draw color

}


为了确定和光线相交的最近的几何体,需要对场景中的所有物体进行测试,这个是在raytracer.cpp中实现的。

求交代码

初始化以后,下面的代码将被执行:

// find the nearest intersection


for
( int
s = 0; s < m_Scene->GetNrPrimitives(); s++ )

{

Primitive* pr = m_Scene->GetPrimitive( s );

int
res;

if
(res = pr->Intersect( a_Ray, a_Dist ))

{

prim = pr;

result = res; // 0 = miss, 1 = hit, -1 = hit from inside primitive




}

}

这个循环对场景中的每个物体进行操作,调用每个的求交方法。函数Intersect的参数是一条射线,返回一个整数说明是否相交,已经相交点到射线起点的距离。这个循环中保存着当前找到的最近的交点。

颜色

当我们可以确定哪个物体被光线射中以后,光线的颜色就可以被计算出来。简单实用物体的材质颜色实在太无趣。在例子中,使用两种光的漫射进行了计算,通过下面这个循环来实现。

// determine color at point of intersection


pi = a_Ray.GetOrigin() + a_Ray.GetDirection() * a_Dist;

// trace lights


for
( int
l = 0; l < m_Scene->GetNrPrimitives(); l++ )

{

Primitive* p = m_Scene->GetPrimitive( l );

if
(p->IsLight())

{

Primitive* light = p;

// calculate diffuse shading




vector3 L = ((Sphere*)light)->GetCentre() - pi;

NORMALIZE( L );

vector3 N = prim->GetNormal( pi );

if
(prim->GetMaterial()->GetDiffuse() > 0)

{

float
dot = DOT( N, L );

if
(dot > 0)

{

float
diff = dot * prim->GetMaterial()->GetDiffuse();

// add diffuse component to ray color




a_Acc += diff * prim->GetMaterial()->GetColor() * light->GetMaterial()->GetColor();

}

}

}

}

这段代码计算了交点到光源的向量,用这个向量和物体表面的法线点积来表示亮度。这样的结果就是物体上正对光源的一定被照的很亮,而周围的点则会暗一些。在这里点积大于0,表示面向光源可以被照射到。

 

后记

下一篇文章将讲述如何增加更多的关系和阴影,下面是一些效果图。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值