4 光线追踪

渲染三维对象是计算机图形学的基本任务之一,一个场景或模型通常由排列在 3D 空间中的许多几何对象组成,并生成从特定试点观察三维场景的一个 2D 图像。几个世纪以来,建筑师和工程师通过绘制图纸来传达他们的设计,这是相同的操作。

从根本上说,渲染是一个将一组对象作为其输入,并产生一组像素作为其输出的过程。以一种或另一种方式,渲染考虑每个对象以什么样的方式对每个像素做出贡献。一般可以通过两种方式进行组织。在对象顺序渲染中,依次考虑每个对象,并为找到每个对象影响和更新的所有像素。在图像顺序渲染中,依次考虑每个像素,并为每个像素找到影响它的所有对象并计算像素值。您可以考虑循环嵌套方面的差异:在图像顺序渲染中,“每个像素”循环在外部,而在对象顺序渲染中,“每个对象”循环在外部。

图像顺序和对象顺序渲染方法可以计算完全相同的图像,但它们适合计算不同类型的效果并且具有完全不同的性能特征。 讨论完这两种方法后,我们将在第 8 章中探讨这些方法的比较优势,但是,从广义上讲,图像顺序渲染更容易工作可以产生的更灵活的效果,并且通常(尽管并非总是如此) ) 需要更多的执行时间来生成可比较的图像。

光线追踪是一种用于渲染 3D 场景的图像顺序算法,我们将首先考虑它,因为可以在不开发任何用于对象顺序渲染的数学机制的情况下让光线追踪器工作。

4.1 基本的光线追踪算法

光线追踪器的工作原理是一次计算一个像素,对于每个像素,基本任务是找到在图像中该像素位置看到的对象。每个像素“看”的方向不同,像素看到的任何对象都必须与观察光线相交,这是一条从视点沿像素看的方向发出的线。我们想要的特定对象是与距离摄影机最近的观察光线相交的对象,因为它会阻挡后面任何其他对象的视图。找到该对象后,着色计算将使用交点、曲面法线和其他信息(取决于所需的渲染类型)来确定像素的颜色。这如图4.1所示,其中光线与两个三角形相交,但只有第一个三角形T2被命中并且被着色。

因此,基本光线跟踪器有三个部分:

  • 光线生成,根据相机几何体计算每个像素的观察光线的原点和方向;
  • 光线相交,查找与观察光线相交的最近对象;
  • 着色,它根据光线相交的结果计算像素颜色。

基本光线跟踪程序的结构为:

for each pixel do
    compute viewing ray
    find first object hit by ray and its surface normal n
    set pixel color to value computed from hit point,light,and n

本章介绍光线生成、光线相交和着色的基本方法,这些方法足以实现简单的演示光线跟踪器。对于一个真正有用的系统,需要添加第12章中更有效的光线相交技术,并且将使用第10章中更高级的着色方法和第13章中的其他渲染技术来查看光线跟踪器的真正潜力。

4.2 视角

在计算机出现之前的几百年,艺术家们就已经研究过用二维图形或绘画来表现三维物体或场景的问题。比如照片用2D图像表示3D场景。尽管有许多非传统的图像制作方法,从立体派绘画到鱼眼镜头(图4.2)再到外围摄像头,但艺术和摄影以及计算机图形学的标准方法都是线性透视,将三维对象投影到图像平面上,使场景中的直线成为图像中的直线。

最简单的投影类型是平行投影,在平行投影中,通过沿投影方向移动3D点,直到它们到达图像平面,从而将3D点映射到2D(图4.3–4.4)。生成的视图由投影方向和图像平面的选择决定。如果图像平面垂直于视图方向,则投影称为正交投影;否则称为斜向。

平行投影通常用于机械和建筑图形,因为它们保持平行线平行,并保留平行于图像平面的平面对象的大小和形状。

平行投影的优点也是它的局限性。在我们的日常经验中(在照片中更是如此),物体越远看起来越小,因此,后退到远处的平行线看起来并不平行。这是因为眼睛和照相机不会从单一的观察方向收集光线;它们收集通过特定视点的光线。正如文艺复兴以来的艺术家所认识到的那样,我们可以使用透视投影产生自然景观:我们只是沿着穿过一个点的线投影,即视点,而不是沿着平行线投影(图4.4)。这样,距离视点较远的对象在投影时自然会变小。透视视图由视点(而不是投影方向)和图像平面的选择决定。与平行视图一样,有倾斜和非倾斜透视视图;根据图像中心的投影方向进行区分。

您可能已经了解了三点透视的艺术惯例,这是一个手动构建透视图的系统(图4.5)。透视图的一个重要事实是,如果我们遵循透视图背后的简单数学规则,透视图的所有规则都将自动遵循:对象直接投影到眼睛,并在眼睛前面的视图平面处绘制。

4.3 计算观察光线

从上一节开始,光线生成的基本工具是视点(或平行视图的视图方向)和图像平面。有很多方法可以计算出相机几何体的细节;在本节中,我们将解释一种基于正交基的方法,该方法支持法线和倾斜平行视图和正交视图。

为了生成光线,我们首先需要光线的数学表示。射线实际上只是一个原点和一个传播方向,这可以使用三维参数化线进行表达。如第2.5.7节所述,从眼睛e到图像平面上点s(图4.6)的3D参数线如下所示:

p(t)=e+(s-e)

这应该被解释为,我们从e沿着向量(s-e)前进一个分数距离t以找到点p。所以给定t,我们可以确定点p。点e是光线的原点,s-e是光线的方向。

注意p(0)=2 , p(1)=s ,更一般地说,如果0<t1<t2 ,那么p(t_{1})p(t_{1}) 更靠近眼睛。同样,如果t<0,那么p(t)在眼睛后面。当我们搜索不在眼睛后面的光线击中的最近物体时,这些将非常有用。

为了计算视线,我们需要知道e(给出的)和s。 如果我们看右坐标系中的问题,发现s可能似乎很困难,但它实际上是直截了当的。

我们所有的光线生成方法都是从一个称为相机帧的正交坐标系开始的,我们用e表示视点,用u、v和w表示三个基向量,u指向右(从相机的视图),v指向上,w指向后,这样{u,v,w}形成右手坐标系。构造相机帧最常用的方法是从视点(变为e)、视图方向(变为-w)和上方向向量(用于构造在由视图方向和上方向定义的平面上具有v和w的基础)开始,使用第2.4.7节中描述的从两个向量构造正交基的过程

4.3.1 正交视图

对于正交视图,所有光线的方向都为-w。即使平行视图本身没有视点,我们仍然可以使用摄影机帧的原点来定义光线开始的平面,以便对象可能位于摄影机后面。

观察光线应从点e和向量u和v定义的平面开始;所需的唯一剩余信息是图像在飞机上的位置。我们将用四个数字定义图像尺寸,用于图像的四个侧面:l和r是图像左右边缘的位置,从e沿u方向测量;b和t是图像的下边缘和上边缘的位置,从e沿v方向测量。通常l<0<r,b<0<t(见图4.9)。

在第3.2节中,我们讨论了图像中的像素坐标。在第3.2节中,我们讨论了图像中的像素坐标。为了将具有nx×ny像素的图像适配到大小为(r-l)×(t-b)的矩形中,像素在水平方向上间隔(r-l)/nx距离,在垂直方向上间隔(t-b)/ny距离,边缘周围有半个像素空间,以使像素网格在图像矩形内居中。这意味着光栅图像中位置(i,j)处的像素具有:

\begin{aligned}
u=l+(r-l)(i+0.5)/n_{x} \\
v=b+(t-b)(j+0.5)/n_{y}
\end{aligned}

其中(u,v)是相对于原点e和基{u,v}测量的像素在图像平面上的位置的坐标。

在正交视图中,我们可以简单地使用像素的图像平面位置作为光线的起点,并且我们已经知道光线的方向就是视图方向。生成正交观察光线的步骤如下:

compute u and v using(4.1)
ray.direction = -w
ray.origin = e+uu+vv

制作一个倾斜平行视图非常简单:只需将图像平面法线w与视图方向d分开指定即可。然后,程序完全相同,但用d代替-w。当然,w仍然用于构造u和v。

4.3.2 透视图

对于透视视图,所有光线在视点处具有相同的原点;每个像素的方向不同。图像平面不再位于e处,而是位于e前面的某个距离d处;这个距离就是图像平面距离,通常被松散地称为焦距,因为选择d与在真实相机中选择焦距的作用相同。每条光线的方向由视点和像素在图像平面上的位置定义。这种情况如图4.9所示,所得程序与正交程序类似:

compute u and v using (4.1)
ray.direction = -dw + uu + vv
ray.origin = e

与平行投影一样,可以通过分别指定投影方向的图像平面法线,然后在光线方向的表达式中用dd替换-dw来实现斜透视视图。

4.4 射线-对象相交

生成光线e+td后,接下来需要找到与t>0的任何对象的第一个交点。在实践中,它对于解决一个稍微更一般的问题是有用的:找到光线和间隔[t0,t1]内t处出现的曲面之间的第一个交点。最基础的光线相交的情况是t_{0}=0,t_{1}=+\infty。我们解决了球面和三角形的这个问题。在下一节中,将讨论多个对象。

4.4.1 射线-球相交

给定一个射线p(t) = e + td 以及一个隐式曲面f(p)=0 (看2.5.3节) ,我们想知道它们在哪里相交。当光线上的点满足隐式方程时,交点出现,因此我们寻求的t值是求解方程的值:

f(p(t)) = 0

或者

f(e + td) = 0

圆心为c=(x_{c},y_{c},z_{c} ) ,半径为R 的圆可由下面公式隐式表达:

(x-x_{c} )^{2} + (y-y_{c} )^{2} + (z-z_{c} )^{2} - R^{2} =0

我们可以把这个方程写成向量形式:

(p-c)\cdot (p-c) - R^{2} =0

满足这个方程的任何点p都在球面上。如果我们把射线p(t) = e + td 带入上述方程,就会得到一个关于t的参数方程:

(e+td-c)\cdot (e+td-c) - R^{2} =0

展开公式得:

(\mathbf{d} \cdot \mathbf{d}) t^{2}+2 \mathbf{d} \cdot(\mathbf{e}-\mathbf{c}) t+(\mathbf{e}-\mathbf{c}) \cdot(\mathbf{e}-\mathbf{c})-R^{2}=0

在这里,除了参数t,其他都是已知的,所以这是t中的一个经典二次方程,这意味着它的形式是:

At^{2} + Bt + C = 0

第2.2节讨论了该方程的解。二次解中平方根符号下的项B^{2} - 4AC被称为判别式,它告诉我们有多少个真正的解。如果判别式为负,则其平方根为虚,且直线和球体不相交。如果判别式为正,则有两种解决方案:一种是光线进入球体,另一种是光线离开球体。如果判别式为零,则光线将擦过球体,并恰好在一个点接触它。对上式求解:

t=\frac{-\mathbf{d} \cdot(\mathbf{e}-\mathbf{c}) \pm \sqrt{(\mathbf{d} \cdot(\mathbf{e}-\mathbf{c}))^{2}-(\mathbf{d} \cdot \mathbf{d})\left((\mathbf{e}-\mathbf{c}) \cdot(\mathbf{e}-\mathbf{c})-R^{2}\right)}}{(\mathbf{d} \cdot \mathbf{d})}

在实际实现中,您应该首先检查判别式的值,然后再计算其他项。如果球体仅用作更复杂对象的边界对象,那么我们只需确定是否击中它;检查判别式就足够了。

如第2.5.4节所述,点p处的法向量由梯度n=2(p-c) ,单位向量为(p-c)/R

4.4.2 射线三角形相交

计算光线三角形交点的算法有很多种。我们将介绍使用重心坐标表示包含三角形的参数平面的形式,因为除了三角形的顶点之外,它不需要长期存储(Snyder&Barr,1987)。

为了使光线与参数曲面相交,我们建立了以下方程组:

\left.\begin{array}{rl}
x_{e}+t x_{d} & =f(u, v) \\
y_{e}+t y_{d} & =g(u, v) \\
z_{e}+t z_{d} & =h(u, v)
\end{array}\right\} \quad \text { or, } \quad \mathbf{e}+t \mathbf{d}=\mathbf{f}(u, v)

这里,我们有三个方程和三个未知数(t、u和v),所以我们可以用数值方法求解未知数。如果我们幸运的话,我们可以用解析的方法来求解。

如果参数曲面是参数平面,则参数方程可写成矢量形式,如第2.7.2节所述。如果三角形的顶点是a、b和c,则

e+td = a + \beta (b-a) + \gamma (c-a)

如图4.10所示,交点P位于e+td处。同样通过参考第2.7.2节,当且仅当\beta > 0,\gamma > 0 以及\beta + \gamma<1 时,交点位于三角形内。否则,光线会击中三角形外的平面,因此会错过三角形。如果没有解,要么三角形退化,要么光线平行于包含三角形的平面。

为了求解未知数t,\beta,\gamma ,我们把公式4.2从向量形式展开成三个坐标的三个方程:

\begin{aligned}
x_{e}+t x_{d} &=x_{a}+\beta\left(x_{b}-x_{a}\right)+\gamma\left(x_{c}-x_{a}\right), \\
y_{e}+t y_{d} &=y_{a}+\beta\left(y_{b}-y_{a}\right)+\gamma\left(y_{c}-y_{a}\right), \\
z_{e}+t z_{d} &=z_{a}+\beta\left(z_{b}-z_{a}\right)+\gamma\left(z_{c}-z_{a}\right) .
\end{aligned}

上述公式可以被改写为以下矩阵:

\left[\begin{array}{lll}
x_{a}-x_{b} & x_{a}-x_{c} & x_{d} \\
y_{a}-y_{b} & y_{a}-y_{c} & y_{d} \\
z_{a}-z_{b} & z_{a}-z_{c} & z_{d}
\end{array}\right]\left[\begin{array}{l}
\beta \\
\gamma \\
t
\end{array}\right]=\left[\begin{array}{l}
x_{a}-x_{e} \\
y_{a}-y_{e} \\
z_{a}-z_{e}
\end{array}\right] .

求解这个3×3线性系统最快的经典方法是克拉默法则,可参考:

\begin{array}{c}
\beta=\frac{\left|\begin{array}{lll}
x_{a}-x_{e} & x_{a}-x_{c} & x_{d} \\
y_{a}-y_{e} & y_{a}-y_{c} & y_{d} \\
z_{a}-z_{e} & z_{a}-z_{c} & z_{d}
\end{array}\right|}{|\mathbf{A}|}, \\
\gamma=\frac{\left|\begin{array}{lll}
x_{a}-x_{b} & x_{a}-x_{e} & x_{d} \\
y_{a}-y_{b} & y_{a}-y_{e} & y_{d} \\
z_{a}-z_{b} & z_{a}-z_{e} & z_{d}
\end{array}\right|}{|\mathbf{A}|}, \\
t=\frac{\left|\begin{array}{lll}
x_{a}-x_{b} & x_{a}-x_{c} & x_{a}-x_{e} \\
y_{a}-y_{b} & y_{a}-y_{c} & y_{a}-y_{e} \\
z_{a}-z_{b} & z_{a}-z_{c} & z_{a}-z_{e}
\end{array}\right|}{|\mathbf{A}|},
\end{array}

上式的A矩阵为:

\mathbf{A}=\left[\begin{array}{lll}
x_{a}-x_{b} & x_{a}-x_{c} & x_{d} \\
y_{a}-y_{b} & y_{a}-y_{c} & y_{d} \\
z_{a}-z_{b} & z_{a}-z_{c} & z_{d}
\end{array}\right]

\left | A \right | 指A矩阵的行列式。3×3行列式具有可利用的公共子项。参考下列的矩阵公式:

\left[\begin{array}{lll}
a & d & g \\
b & e & h \\
c & f & i
\end{array}\right]\left[\begin{array}{l}
\beta \\
\gamma \\
t
\end{array}\right]=\left[\begin{array}{l}
j \\
k \\
l
\end{array}\right]

根据克拉默法则,

\begin{aligned}
\beta &=\frac{j(e i-h f)+k(g f-d i)+l(d h-e g)}{M} \\
\gamma &=\frac{i(a k-j b)+h(j c-a l)+g(b l-k c)}{M} \\
t &=-\frac{f(a k-j b)+e(j c-a l)+d(b l-k c)}{M}
\end{aligned}

其中,M为:

M=a(e i-h f)+b(g f-d i)+c(d h-e g)

我们可以通过重用诸如“ei-minus-hf ”之类的数字来减少操作数量。

射线三角形相交的算法的伪代码如下:

参考原书伪代码

4.4.3 射线-多边形相交

给定一个经过P_{1} - P_{m}m个点的,且曲面法向为n的参数平面,计算射线e + td与该平面的交点,可通过以下参数方程:

\left(\mathbf{p}-\mathbf{p}_{1}\right) \cdot \mathbf{n}=0

设定P = e + td ,则:

t=\frac{\left(\mathbf{p}_{1}-\mathbf{e}\right) \cdot \mathbf{n}}{\mathbf{d} \cdot \mathbf{n}}

则可以通过上式计算交点P。

我们可以通过将点和多边形顶点投影到xy平面并在那里回答,来回答p是否在多边形内部的问题。做到这一点的最简单方法是从p发送任何2D光线,并计算该光线与多边形边界之间的交点数(Sutherland、Sproull和Schumacker,1974;Glassner,1989)。如果交点的数量为奇数,则该点位于多边形内部;否则就不是了。这是正确的,因为进入的光线必须离开,从而创建一对交点。只有从内部开始的光线不会创建这样的一对。为了简化计算,2D射线也可以沿x轴传播:

\left[\begin{array}{l}
x \\
y
\end{array}\right]=\left[\begin{array}{l}
x_{p} \\
y_{p}
\end{array}\right]+s\left[\begin{array}{l}
1 \\
0
\end{array}\right]

计算该射线与边的交点很简单。

但是,对于投影到xy平面上是直线的多边形,会出现一个问题。为了解决这个问题,我们可以从xy、yz或zx平面中选择最好的,可参考以下伪代码进行处理:

参考原书伪代码

另一种处理多边形的方法是用几个三角形替换多边形,这在实践中经常使用。

4.4.4 相交一组对象

当然,大多数有趣的场景都由多个对象组成,当光线与场景相交时,我们必须沿光线找到离摄影机最近的交点。实现这一点的一种简单方法是将一组对象视为另一种类型的对象。要使光线与组相交,只需将光线与组中的对象相交,并返回具有最小t值的相交。

参考原书伪代码

4.5 光照模型

已知像素的可见表面后,通过计算着色模型来计算像素值。如何做到这一点完全取决于应用-方法范围从非常简单的启发式到精细的数值计算。
在本章中,我们将介绍两种最基本的着色模型;第10章讨论了更高级的模型。

大多数着色模型都是以这样或那样的方式设计的,用于捕捉光反射的过程,即表面由光源照明,并将部分光反射到相机。简单着色模型是根据点光源的照明来定义的。光反射中的重要变量是光方向l,它是指向光源的单位矢量;视图方向v,其是指向眼睛或照相机的单位向量;表面法线n,是在发生反射的点处垂直于表面的单位矢量;以及取决于特定模型的表面颜色、光泽度或其他特性。

4.5.1 兰伯特光照模型

最简单的着色模型是基于18世纪兰伯特(Lambert)的观察:落在表面区域上的光源能量大小取决于表面与光的角度。直接朝向光的表面接收最大照明;与灯光方向相切(或背向灯光)的曲面不接收照明,在两者之间,照明度与表面法线和光源之间夹角\theta的余弦成正比(图4.12)。兰伯特的光照模型公式如下:

L=k_{d} I \max (0, \mathbf{n} \cdot \mathbf{l})

其中,L 是像素颜色,k_{d}是漫反射系数或者表面颜色,l是光源的强度。因为nl是单位向量,我们可以使用\mathbf{n} \cdot \mathbf{l} 作为\cos \theta的简写。该方程(与本节中的其他着色方程一样)分别适用于三个颜色通道,因此像素值的红色分量是红色漫反射分量、红色光源强度和点积的乘积;绿色和蓝色也是如此。

通过从光源位置减去光线和曲面的交点来计算向量l ,向量v,ln都必须是单位向量,如果在实际计算过程中没有单位化这些向量将会导致错误。

4.5.2 Blinn-Phong光照模型

Lambertian着色与视图无关:曲面的颜色不取决于查看的方向。许多真实曲面显示一定程度的光泽度,产生高光或镜面反射,这些高光或反射似乎随着视点的变化而移动。Lambertian着色不会产生任何高光,并导致非常无光、白垩色的外观,许多着色模型向Lambertian着色添加镜面反射组件;然后,朗伯部分是漫反射组件。

Phong(Phong,1975)提出了一个非常简单且广泛使用的镜面反射高光模型,后来Blinn(J.F.Blinn,1976)将其更新为当今最常用的形式。其思想是,当vl在曲面法线上对称定位时,产生最亮的反射,此时将发生镜面反射;然后,当矢量从镜像配置移开时,反射平滑减小。

通过比较半矢量h(v和l之间夹角的平分线)与曲面法线(图4.16),我们可以知道我们离镜像配置有多近。如果半矢量接近曲面法线,则镜面反射分量应明亮;如果离得很远,它应该是暗淡的。这个结果是通过计算h和n之间的点积(记住它们是单位向量,因此当向量相等时,n·h达到其最大值1),然后将结果取幂p>1,使其减小得更快。幂或Phong指数控制曲面的外观光泽度。半向量本身很容易计算:因为vl的长度相同,所以它们的和是一个向量,将它们之间的角度平分,只需要归一化就可以得到h

综上所述,Blinn Phong着色模型如下所示:

\begin{aligned}
\mathbf{h} &=\frac{\mathbf{v}+\mathbf{l}}{\|\mathbf{v}+\mathbf{l}\|} \\
L &=k_{d} I \max (0, \mathbf{n} \cdot \mathbf{l})+k_{s} I \max (0, \mathbf{n} \cdot \mathbf{h})^{p}
\end{aligned}

其中k_{s}是曲面的镜面反射系数或者镜面反射颜色。

4.5.3 环境光

完全不接收照明的曲面将渲染为完全黑色,这通常是不可取的。避免黑色阴影的一个粗略但有用的启发式方法是向着色模型添加一个常量组件,该组件对像素颜色的贡献仅取决于对象命中,而完全不依赖于曲面几何体。这就是所谓的环境明暗处理,它就像表面被来自各地的“环境”光照亮一样。为便于调整参数,环境光明暗处理通常表示为曲面颜色与环境光颜色的乘积,因此可以单独调整曲面的环境光颜色,也可以一起调整所有曲面的环境光颜色。与Blinn Phong模型的其余部分一起,环境光着色完成了简单而有用的着色模型的完整版本:

L=k_{a} I_{a}+k_{d} I \max (0, \mathbf{n} \cdot \mathbf{l})+k_{s} I \max (0, \mathbf{n} \cdot \mathbf{h})^{n},

其中k_{a}表示环境光系数或者环境光颜色,I_{a}表示在有问题时将环境光颜色设置为相同的值。

4.5.4 多点光源

光的一个非常有用的特性是叠加。由多个光源引起的效应只是各个光源效应的总和。因此,我们的简单着色模型可以轻松扩展以处理N个光源:

L=k_{a} I_{a}+\sum_{i=1}^{N}\left[k_{d} I_{i} \max \left(0, \mathbf{n} \cdot \mathbf{l}_{i}\right)+k_{s} I_{i} \max \left(0, \mathbf{n} \cdot \mathbf{h}_{i}\right)^{p}\right]

其中,I_{i} , $\mathbf{l}{i}\mathbf{h}{i}分别为第i$个光源的强度、方向和半向量。

4.6 光线追踪程序

现在我们知道了如何为给定像素生成观察光线,如何找到与对象最近的交点,以及如何对生成的交点进行着色。这些是一个程序所需的所有部分,该程序生成阴影图像并删除隐藏表面。

在实际实现中,曲面相交例程需要以某种方式返回对命中对象的引用,或者至少返回其法向量和着色相关的材质属性。这通常是通过传递带有此类信息的记录/结构来实现的。在面向对象的实现中,最好有一个名为surface的类,该类包含派生类triangle、sphere、group等。光线可以相交的任何对象都位于该类之下。然后,光线跟踪程序将对整个模型的“曲面”进行一次引用,并且可以透明地添加新类型的对象和有效结构。

4.6.1 光线追踪程序的面向对象设计

如前所述,光线跟踪器中的关键类层次结构是构成模型的几何对象。这些应该是一些几何对象类的子类,它们应该支持hit函数(Kirk&Arvo,1988)。为了避免因使用“对象”一词而产生混淆,surface是常用的类名。有了这样一个类,您可以创建一个光线跟踪器,该光线跟踪器具有一个通用接口,该接口几乎不需要对基本体进行建模,并仅使用球体对其进行调试。重要的一点是,任何可以被光线“击中”的对象都应该是该类层次结构的一部分,例如,即使是曲面集合也应该被视为曲面类的子类。这包括效率结构,例如边界体积层次结构;他们可能被射线击中,所以他们在类中。

另一个有用的类是material。这允许您抽象材质行为,然后透明地添加材质。链接对象和材质的一种简单方法是在surface类中添加指向材质的指针,尽管可能需要更多可编程行为。一个大问题是如何处理纹理;他们是材料类的一部分还是生活在材料类之外?这将在第11章中详细讨论。

4.7 阴影

一旦有了一个基本的光线跟踪程序,就可以很容易地添加阴影。回想第4.5节,光来自某个方向l。如果我们想象自己在一个被着色的表面上的点p,如果我们“看”方向l并看到一个物体,该点就在阴影中。如果没有对象,则灯光不会被遮挡。

这如图4.17所示,其中光线p+tl不会击中任何对象,因此不在阴影中。点q处于阴影中,因为光线q+tl确实击中了对象。向量l对于两个点都是相同的,因为灯光距离“远”。这一假设稍后将放宽。确定阴影内或阴影外的光线称为阴影光线,以区别于观察光线。

.......

请注意,无论p是否处于阴影中,都会添加环境光颜色。如果有多个光源,我们可以在评估每个光源的着色模型之前发送阴影光线。上面的代码假设d和l不一定是单位向量。这对于d来说是至关重要的,尤其是如果我们希望在以后干净地添加实例(请参见第13.2节)。

4.8 理想的镜面反射

将理想的镜面反射或镜像反射添加到光线跟踪程序中非常简单。关键观察如图4.19所示,其中从e方向观看的观众可以从表面上看到r方向的东西。向量r是使用Phong照明反射方程(10.6)的一种变体求出的。符号会发生变化,因为在这种情况下,向量d指向曲面,所以:

\mathbf{r}=\mathbf{d}-2(\mathbf{d} \cdot \mathbf{n}) \mathbf{n}

在现实世界中,当光线从表面反射时,会损失一些能量,而这种损失对于不同的颜色可能有所不同。例如,黄金比蓝色更有效地反射黄色,因此它会改变所反射对象的颜色。这可以通过在raycolor中添加递归调用来实现:

\text { color } c=c+k_{m} \text { raycolor }(\mathbf{p}+s \mathbf{r}, \epsilon, \infty)

其中,k_{m}是镜面反射的RGB颜色。

上面递归调用的问题是它可能永远不会终止。例如,如果光线在房间内开始,它将永远反弹。这可以通过添加最大递归深度来修复。如果仅当k_{m}不为零(黑色)时才生成反射光线,则代码将更有效。

4.9 历史注释

光线跟踪是在计算机图形学的早期发展起来的(Appel,1968),但直到有足够的计算能力可用时才被广泛使用(Kay&Greenberg,1979;Whitted,1980)。

光线跟踪的渐近时间复杂度低于基本对象顺序渲染(Snyder&Barr,1987;Muuss,1995;S.Parker等人,1999;Wald,Slusallek,Benthin和Wagner,2001)。尽管它传统上被认为是一种离线方法,但实时光线跟踪实现正变得越来越普遍。

常见问题

为什么光线跟踪中没有透视矩阵?

z缓冲区中存在透视矩阵,因此我们可以将透视投影转换为平行投影。这在光线跟踪中是不需要的,因为通过从眼睛发出光线,可以很容易地隐式地进行透视投影。

光线跟踪可以进行交互吗?

对于足够小的模型和图像,任何现代PC都具有足够的强大功能,光线跟踪可以进行交互。实际上,全屏实现需要多个具有共享帧缓冲区的CPU。计算机功率的增长速度远远快于屏幕分辨率,而传统的PC机能够以屏幕分辨率对复杂场景进行光线跟踪只是时间问题。

光线跟踪在硬件图形程序中有用吗?

光线跟踪常用于拾取。当用户在3D图形程序中的像素上单击鼠标时,该程序需要确定该像素内可见的对象。光线跟踪是确定这一点的理想方法。