第一部分 光线追踪基础
今天,光栅化占领了实时渲染的大多数应用领域,因此许多寻找实时渲染技巧的读者上次遇到光线追踪可能还是在学生时代(可能几十年前)。本部分包含了介绍性的章节来帮助大家复习基础知识,在术语方面达成共识,并提供一些简单却很有用的代码模块。
第1章,“光线追踪术语”定义了本书中要用到的术语并且给出了引入这些术语的论文。对于第一次接触的读者来说,当你阅读文献时,会遇到令人费解、不断变化的术语,它们时而含义重复,时而模棱两可。在不理解这些术语如何演化到今天的情况下,直接去读30年前的论文会令人沮丧。该章节提供了一个基础的路线图。
第2章,“什么是光线?”覆盖了一些常见的关于光线的数学定义:如何思考光线,哪些公式通常会用于现代API中。虽然这是一个简单的章节,但是分离出这个基本结构的基础部分会有助于提醒读者数值精度问题比比皆是。对于光栅化,精度问题出现在深度缓存闪烁(z-fighting)和阴影贴图(Shadow Mapping)中;在光线追踪中,每光线查询都要小心处理以避免假性相交判断(精度问题在第6章中有更深入的探讨)。
*近,微软公司对外发布了DirectX光线追踪技术,它是DirectX光栅化API的扩展。第3章,“DirectX光线追踪介绍”,简单介绍了该编程接口引入的抽象、心智模型和新的着色器类型。除此之外,还解释了如何初始化,并提供示例代码以帮助你入门。
光线追踪降低了构建任意相机模型的难度,而不像传统的光栅化那样必须定义4×4的投影矩阵来定义相机。第4章,“天文馆球幕相机”给出了构建一个180°半球形球幕投影(比如天文馆)的相机模型的数学形式和示例代码。这个章节同样展示了在光线追踪中添加立体渲染或者景深功能是多么容易。
第5章,“计算子数组的*小值和*大值”描述了三种不同的计算方法(并权衡了计算时的各种因素),用于计算数组中任意子数组的*小值和*大值。表面上看,评估这样的查询和光线追踪没有明显的关系,但是它可以应用到科学计算可视化这样的领域,在这些领域经常用到光线查询。
第一部分的知识可以帮你理解现代光线追踪的基础知识和进行高效渲染所需的思维模式。
Chris Wyman
第1章 光线追踪术语
Eric Haines, Peter Shirley NVIDIA
本章节介绍了贯穿全书的背景知识和术语定义。
1.1 历史回顾
光线追踪在环境中追踪光的运动这一学科方向中具有悠久的历史,常常称为辐射传输(radiative transfer)。图形学从业者从中子迁移(neutron transport)[2]、热传导(heat transfer)[6]和照明工程(illumination engineering)[11]等领域引入了这些概念。因为有特别多的领域在研究这些概念,加上在学科之间或者学科内部术语经过不断发展,有时这些概念之间会出现分歧。经典的论文里也可能会错误地使用术语,给读者带来困扰。
光沿光线传播的基本度量是SI国际单位制(International System of Units)中的光谱辐射率(spectral radiance),其在真空中沿光线传播时保持不变,它*直观的感受就是亮度(brightness)。在得到标准化之前,光谱辐射经常被称为“强度”(intensity)或者“亮度”。计算机图形学丢弃了“光谱”这个前缀,因为我们从不使用非光谱的辐射(在所有波长上的辐射集合)。
图形学领域中涉及光线的术语在一直发展。几乎所有的现代光线追踪器都使用递归和蒙特卡罗法,因此现在很少有人会刻意称自己的渲染器为“递归式蒙特卡罗”光线追踪器。在1968年,苹果公司[1]使用光线来渲染图片。1979年,Whitted[16]和Kay,Greenberg[9]开发了一种递归光线追踪方法来描述精确的折射和反射。1982年,Roth[13]使用沿着光线的内向/外向区间列表以及局部实例化来进行CSG模型的渲染和体积估计。
1984年,Cook等人[4]提出了分布式光线追踪。在其他地方,为了避免和分布式处理混淆,此方法通常被称为随机(stochastic)光线追踪。几乎每一个现代光线追踪器都会使用随机采样的方法来实现景深(depth of field)、模糊反射(fuzzy reflection)和软阴影(soft shadow)等效果。1984年之后不久,研究人员使用传统的辐射传输方法重新描述了绘制过程。1986年,人们提出了两个重要的算法。Kajiya[8]将积分传输方程称为渲染方程(rendering euqation)。他尝试了多种解决方案,其中包括他称为路径追踪(path tracing)的蒙特卡罗方法。Immel、Cohen和Greenberg[7]提出了相同的传输方程,只是使用了不同的单位,并使用有限单元法(finite element method)来求解这个方程,该方法现在称为辐射度算法(radiosity)。
自三十多年前使用经典的辐射传输理论来重新描述这个图形学问题后,在如何从数值上进行问题求解方面大家做了大量的工作。一些关键的算法也发生了改变,如双向(bidirectional)路径追踪[10, 14]和20世纪90年代引入的基于路径的(path-based)方法[15]。更多细节,包含如何在项目中实现这些技术,在Pharr、Jakob和Humphreys的书[12]中有讨论。
1.2 定义
我们在此强调一些本书中的重要术语,反映了行业中对这些概念的用法。虽然说除了一些标准单位,尚不存在公认的标准术语列表。
光线投射(ray casting)技术返回沿着光线*近的那个物体,有时甚至返回光线穿过的所有的物体。一根光线通过一个像素离开相机,在场景中行进,直到碰撞到*近的物体。作为着色的一部分,可以在发生碰撞的点向光源投射一个新的光线,以确定这个物体是否在阴影里,如图1.1所示。
图1.1 光线投射。一根光线从相机的位置出发经过像素网格进入场景中。在每个碰撞点,朝光源投射另一根光线来确定表面被照亮还是在阴影里(图片来源Henrik,“光线追踪(图形学)”,维基百科)
光线追踪(ray tracing)利用光线投射的机制来递归地采集反射材质和折射材质对*终光强的贡献。比如,当光线碰到一个镜子,从镜子上的碰撞点沿反射方向投射一根光线。不管这个反射光线(reflection ray)和什么相交都会影响镜子*终的着色。类似的,透明物体如玻璃可能会同时产生反射光线和折射光线(refraction ray)。这个过程会不断递归下去,每一根新的光线都可能引发新的反射和折射光光线。递归通常都会有截断限制,比如光线的*大反弹次数。整个光线树会被反向地沿着光路评估,直至*后计算出颜色。和之前一样,我们在每个碰撞点向每个光源发射光线来确定该物体是否在阴影里,如图1.2所示。
图1.2 光线追踪。三束光线从相机射入场景。上方绿色的光线直接击中盒子。中间紫色的光线先击中镜子,经反射击中盒子的背部。下方蓝色光线击中玻璃球,产生反射和折射光线。折射光线接着产生两束子光线,其中一根穿过玻璃球
在Whitted算法(即经典光线追踪)中,假设物体表面绝对闪亮光滑,光源是方向光或者说位于无穷远处。在Cook算法(即随机光线追踪)中,光线树中每个节点可以发射更多的光线以产生各种效果。比如说,我们想象一个球面光源而非点光源。物体表面的局部被照亮,因此我们需发射非常多的光线到球面光源上不同的位置,来估计有多少光照可到达。我们对面光源的可见程度进行积分,完全落在阴影里的点位于在全影区(umbra),局部照亮的点落在半影区(penumbra),如图1.3所示。
图1.3 球面光源投射出柔和的半影区,全影区则完全在阴影当中
通过向反射方向上的锥形区域射出很多光线并对结果进行混合,我们得到带光泽的反射而非完全的镜面反射。如图1.4所示。这种分散采样点的理念同样可用于实现半透明(translucency)、景深(depth of field)、运动模糊(motion blur)等效果。
在真实世界中有很多光源,它们到达人眼的方式多种多样,包括折射和反射。光泽表面反射光线到很多方向上,不仅仅局限在反射方向上。漫反射或者哑光材质可以将光线分散到更广泛的范围。在路径追踪方法中,我们颠倒一下光的散射行为,使用出射方向和材质来决定入射方向*终着色的重要性。
追踪如此复杂的光线传输过程对计算资源的要求非常惊人,也容易造成低效的渲染。其实,为了产生一张图像,我们只需要光线沿着特定的一组方向穿过相机镜头。递归光线追踪(recursive ray tracing)及其衍生算法逆转了光线传播的物理过程,仅在相机处对*终图像内容的方向生成光线。
(a)入射光被反射后朝单个方向离开镜面
(b)在被打磨过的材质上(比如黄铜),在反射方向附近散布光线并使表面有光泽
(c)在漫反射或哑光的材质上(比如石膏),反射光线被散布到各个方向
图1.4 镜面(mirror)反射、光泽(glossy)反射及漫(diffuse)反射
在Kajiya风格算法(即路径追踪)中,场景中的哑光表面会反射光线,它可以模拟现实世界中的所有光路(衍射之类的相位效果除外)。这里的光路(path)指的是从相机到光源之间一系列的光与物体的相互作用。
每个表面的交点处都需要结合材质的反射属性来估算它周围所有方向上的光线的贡献。例如,一堵红色的墙旁边有白色的屋顶,墙会反射红色的光到屋顶,反之亦然。墙和屋顶会不断产生更多的相互反射,这些相互反射又会产生更多反射光线,不断影响彼此的着色。我们从相机的视角递归地累计这些效果,只有当光线击中光源时才结束该光路。通过这种方法,才可能生成出真实的基于物理的图像。
这里我们使用了“可能”,如果我们在一个粗糙的表面射出一组光,比方说一千束。然后对于每一束光,我们递归地发射出另外上千束,那我们仅仅是计算一个像素点就可以到天荒地老了。相反,当一束光线从人眼发射出去并碰到一个可见的表面上,在碰撞点光线追踪器在有用的方向上只产生一束光。这束光依次产生一束光并持续下去,这些光线并*终形成一个路径。对于一个像素,将多个路径的结果进行混合就可以估计出真正的像素辐射值。随着追踪的路径数量变多,*后的效果也会提升。通过适当的处理,路径追踪可以给出无偏(unbiased)的、符合物理现实的结果。
大多数现代的光线追踪器对每个像素使用多束光线作为蒙特卡罗(Monte Carlo,MC)算法的底层部分。Cook风格和Kajiya风格的算法就是例子。这些算法都在某些空间对各种概率密度函数(probability density function,PDF)有着特定的理解。比如,在Cook风格算法中,我们可能会在一个透镜空间包含一个PDF。在Kajiya风格算法中,PDF将会在我们称为路径空间(path space)的路径上出现。
蒙特卡罗算法使用不均匀分布的PDF采样来减少误差,这种采样称为重要性采样(importance sampling)。使用数论方法中的样本低差异模式而不是传统的伪随机数生成器来创建随机样本被称为准蒙特卡罗(Quasi-Monte Carlo,QMC)采样。大部分场合,计算机图形学从业者使用MC和QMC领域的标准术语。然而,这样的做法有时会造成同义词混淆。比如,计算机图形学中的“使用阴影光线的直接光照”是MC/QMC中“下一事件估计”的一个例子。
从形式化的角度看,渲染器求解的是传播方程(transport equation),在图形学领域
展开