标签归档:Path Tracing

致渲染(rendering)爱好者的一封信

各位渲染爱好者:
你们好!

您可能是一名游戏引擎/渲染器研发工程师,游戏开发者,技术美术,图形学研究者,老师或者学生等,您的工作可能涉及影视动画制作,游戏(引擎)开发,物理仿真,医学成像,室内设计,虚拟现实,以及其他视觉相关的工作等,但不管怎样,你们都有一个共同的愿望,那就是希望了解怎样将一个数字场景渲染成一幅符合物理的,非常逼真的,令人赏心悦目的图片,就像本文的封面图片,这幅图片来自皮克斯的电影《寻梦环游记》,也许您观看过这部电影,并曾经或者还在惊叹于它使用的图形技术,您能想象那个梦中的场景有826 5557个点光源吗?
那么我是谁呢?可能大部分朋友还/并不认识我。

但是很多朋友可能听说过我写的一本即将出版的关于渲染的图书:《全局光照技术:从离线到实时渲染》,这是一本纯聚焦于渲染的图形学图书,我花了三年多全职的时间去写作这本书。我于2016年就开放了一些这本书的试读内容,到目前为止百度网盘的文件下载量超过了2000多次,还不包括一些QQ群内无法统计的数字;我于2017年中发起了半本草稿书的众筹(《全局光照技术》- 原来图形技术的世界美如她所创造的世界),甚至有将近500多人购买了一本只包含一半内容的同时包含很多未矫正文法错误的半成品图书,这在中文出版史上可能是第一次;我再次于2017年底发起全书的正式预订(《全局光照技术》出版在即!揭秘工业中最前沿的渲染技术 – 摩点 – 文化创意众筹社区),在距离正式出版还有长达半年之久,有800多名读者就已经预订了这本图书!

那么今天我想与您说些什么呢?坦白讲我是为了宣传新书,这个我得首先向您坦诚,因为写作了三年,我当然希望它有一定的市场,每名作者也希望自己的创作能够被接受和认可。但是今天,我更想与您讨论的内容是:我是怎样从零开始学习计算机图形学知识的?我想向您描述我心中的图形学世界,我所看到的图形学逻辑知识图谱,以及我给您的一些学习建议!

我的图形学学习历程

我并不是计算机专业毕业,在大学也就是自己学了点编程知识(那时主要是C#和Ruby on Rails),以及旁听了一点计算机专业的基础课程,毕业后的头两年也就是做企业软件开发,那时候其实我还没有一点关于计算机图形学的概念,甚至我在大学基本上不玩游戏。

我进入游戏开发行业是一个偶然的机会,那时候Cocos2d-x团队希望与我所在的OpenXLive公司合作,将其移植到Windows Phone平台上,这个项目也是我加入OpenXLive的原因,那个时候我还在另一家公司,我跟Cocos2d-x的创始成员之一(我的好朋友)杨文生先生以及OpenXLive的老板在一起吃了次饭,商定由我来负责这个项目的移植。

其实那个时候(2011年底)我还没有一点关于3D渲染的概念(所以您可以猜到我全部的计算机图形学知识是从2012年才开始学习的),我之所以被委任这个项目,主要是因为我那时C#还算比较不错,以及他们对我做事态度的认可。后来我进入新公司后,我带领2名实习生,在Cocos2d-x团队的协助下,大概一个月左右的时间发布了一个预览版,然后大概3个月发布了正式版(cocos2d/cocos2d-x-for-xna),您可能会觉得仅仅是语言的移植会很简单,但其实这仍然涉及到很多工作,如果你不去学习了解3D知识,会遇到很多困难。

cocos2d-x-for-xna是我进入3D领域的一个时间点,尽管那个时候移动游戏开始逐渐兴起,很长一段时间我还是没有找到对游戏开发的激情,还记得我说过我在整个大学期间几乎没有怎么玩过游戏,我确实觉得玩游戏会浪费时间,所以Cocos2d-x项目之后我还是回到了传统的应用和企业开发。

直到后来有幸玩到了一个游戏:Superbrothers: Sword & Sworcery EP(Sword & Sworcery – NEWS),我才真正被游戏的魅力征服,从此开始全身心投入游戏行业,开启了我一发不可收拾的计算机图形学学习之旅。

后面的路可能给你们中的大部分朋友一样,疯狂地购买和阅读各种游戏开发的书,OpenGL图形编程之类的书等,到后来写作这本《全局光照技术》时大量阅读渲染方面的论文。我将把这些学习经验和方法留到最后一部分介绍。

我心中的渲染知识图谱

如果你看过 @Milo Yip 的 游戏程序员的学习之路,仅考虑图形部分,这里列出的还是比较优秀的图形学图书,也有60多本;如果你到《Real-time Rendering》官网查看一个图书列表(Real-Time Rendering Graphics books Page),这里甚至列出了多达数百本渲染相关的图书;如果你搜索渲染相关的论文,所有论文的数量甚至是以万计数的。

面对如此浩瀚的知识和技术,我们该如何着手,从哪里开始,这些不同的知识通向哪里,怎么判断你走在了正确并且最快捷的道路上?现在我就视图从我的知识系统出发,给您描述一副渲染的知识图谱,以帮助您更高效地作出选择和学习。

渲染的问题,本质上是一个积分的问题。对于无法通过解析的方式求解的积分,我们通常使用一些数值方法,最常用的就是蒙特卡洛方法,它通过在被积函数的定义域内随机采样,并通过将该样本代入到函数中计算其贡献值来近似该积分的计算。积分的结果是一个数值,但是如果我们存储了函数各个定义域处的贡献值,那么我们就能还原被积函数的分布,在渲染中也就是最终渲染图像的像素颜色值,这就是渲染的原理。

然而,摄像机描述的图像对应分布函数的形式并不是已知的,我们所要想了解的每个像素的颜色,源于光线从光源发出,然后在空中传播,并经过与场景表面的一系列交互,例如反射或折射,最后进入摄像机的过程,因此我们的被积函数的形式隐藏于这种光照传输当中,因此渲染的问题本质是也是一个光照传输的问题,为了描述被积函数的形式,我们需要研究光照的传输。

最直观的描述光照传输的方法就是光线追踪,它是表述光子从光源出发到达摄像机的整个传输过程,我们记录下每个到达摄像机的光线的能量以及其在图像空间中的位置,那么我们就得到了整个图像的分布。因此光线追踪的思路很简单,就是在图像空间中每个位置处发射一条光线,然后计算该光线的光照贡献。我们也称一条光线为一个样本,一条完整的光线也称为一个光线路径,因此跟踪这个光照传输的过程也称为路径采样,基于路径采样的光线追踪算法也称为路径追踪。

然而,一个数字场景通常包含了太多复杂的细节,以至于我们需要非常巨量的光线又能够比较真实的近似场景的实际视觉特征,比如每一帧可能需要数以亿计的光线,这样根据使用算法的不同,渲染一帧可能需要数分钟至数小时甚至数天的时间。

因此,路径追踪技术的主要聚焦点就在于寻找更有效的路径采样方法,因此光照传输的问题本质上又是采样的问题。在采样中,一个样本是一个随机数,由于随机数本身具有方差,因此这导致整个积分的估计也具有方差,并且在蒙特卡洛方法中,我们需要4倍多的样本数量才能换来2倍的方差减少,因此在路径采样问题中,我们又主要将关注点集中在减少估计的方差上。

方差是怎么产生的呢?首先要明白,方差是一种群集属性,它是指一系列随机样本的平均值与每个样本误差的综合。蒙特卡洛方法的直观形式,其实是根据某个分布函数采样来产生基础样本,然后将这些样本代入带被积函数以计算样本的贡献值,所有的贡献值最后被平均,最后通过统计某个区域内样本的数量来估计该区域的函数值,所以如果这些样本的贡献值越是相似或接近,那么估计的方差就越小。

因此,理想的方法就是使用图像的分布函数为采样函数,这样每个样本的贡献值将是绝对相同的,估计的误差为0。然而这确实不可能实现的,因为被积函数的分布正是我们试图去求解的结果。但是尽管我们无法知道整个图像分布函数的显示形式,但是我们知道它的部分表达式,例如BRDF分布函数,如果选取这些部分子函数作为采样的分布函数,那么估计的方差也可以被大大减小,这就是所谓的重要性采样,它几乎被用在所有需要采样的地方。

与重要性采样方法紧密相关的是复合重要性采样,这种方法基于这样一种理论:即对于同一个样本,它可以使用不同的分布函数进行采样,在上述的重要性采样方法中,函数的不同部分通常对其中的一些区域有着更好的拟合,但是对另一些区域拟合效果就会差得多,由于同一个样本可以来自于不同的分布函数,因此如果我们使用对个分布函数进行采样,然后将它们组合起来,让来自不同分布函数的样本去更好地拟合相应的函数部分,那么整个估计的方差也会被进一步降低。

那么来自两个不同分布函数的样本应该怎样被组合在一起呢?前面已经说过,蒙特卡洛方法的原理是假设每个样本的贡献值是相同的,最后仅仅涉及对样本数量的统计,因此如果我们使得来自不同分布函数样本的贡献值是相同的,那么它们就可以被放到一起进行统计计数,毕竟最终我们需要的只是数量么,并不在乎它是通过什么分布函数进行采样的,这就是复合重要性采样方法的核心。

需要注意的是,在路径采样中,一个采样分布函数是与特定的路径长度相关的,对于不同长度(或深度)的路径,我们通常需要使用不同的采样方法。另一方面,在双向路径采样中,一条路径中间会有一条子路径时通过直接连接(而不是采样)得到的,对于一条已经采样完毕的路径,我们可以直接通过解析的方法得到该路径对应的多个采样分布函数,这可以仅仅通过在不同的子路径之间执行连接操作即可,这个过程甚至不涉及其它昂贵的路径追踪过程,例如直线光线与场景的相交计算。

尽管重要性采样方法能够大大降低估计的方差,但是运用在渲染中仍然面临着另一个非常困难的问题:由于图像的分布函数不是已知的,因此它依赖于光线追踪来执行路径采样,然而由于路径是随机采样的,这使得一些路径样本很难被采样到,例如,两个被强隔离的房间,中间只有很小的一个通道,其中一只有一间房间内有光源,在这种情况下,另一件房间唯一的光照来自于那个狭小的通道,而要想渲染该房间内的图像,每一条光线都必须要经过那个通道才能达到光源。对于这种场景的渲染,大部分随机采样的路径都会无效,使得估计收敛的速度非常慢。
因此,对于有些场景,重要性采样方法仍然是无效或者低效的。离线的情况下,我们会需要一个完全正比于图像分布函数的采样函数才能够克服这样的问题,而梅特波利斯算法正是这样的一种采样方法,这种方法可以不要求事先知道被积函数的形式,它只需要我们能够计算每个样本的函数值,然后通过比较两个相邻样本的函数值,最后就能让样本的分布正比于目标被积函数。这种方法的的原理,就是利用了一个稳定状态空间的状态转移原理,在一个处于稳态的系统中,某种度量在各个状态之间的转移在宏观上是不变的,因此我们只要知道了每两个状态之间的转移关系,那么我们就可以在各个状态之间执行转移而保持整个系统的平衡。

梅特波利斯算法就是利用了这样的原理,尽管我们不知道被积函数的形式,但是我们知道每个样本的函数值,通过对当前样本执行一个扰动以产生一个新的样本,并通过比较该新的样本在其位置处的函数值,我们就知道这个新的样本是否位于函数区域内,通过这种比较来选择或拒绝该样本,在多个样本的效应下,就相当于我们会按照函数的分布来产生下一个样本,这就相当于产生了一个从当前位置到下一个位置的复合被积函数的转移函数,因此梅特波利斯算法能够产生复合被积函数的样本,这样的方法在用于克服上述的采样问题。同时由于采样函数正比于被积函数,因此其估计的方差也大大降低。

双向路径追踪算法有一个主要的缺陷,那就是当两条来自光泽表面的子路径相聚于一个漫反射表面时,它们几乎无法连接成为一个完整路径,这种路径也就是经典的焦散效果,我们也几乎从发从漫反射表面随机寻找到一个朝向一个光泽表面的方向,这样的路径几率太低,以至于在我们有限的时间要求下可以认为是不可能的。为了克服这个问题,随机的方向采样显然无法再被使用,于是人们提出一种称为光子映射的方法,这种方法的分为两个渲染通道,第一个通道从光源出发,其首先经过与光泽表面的交互,最终落于第一个漫反射表面上,其光出的光线不再是携带辐射亮度值,而是一个能量密度值,因此这条光线称为一个光子,这些光子最终落于场景中的漫反射表面上,当足够数量的光子散落于漫反射表面时,我们就可以使用回归分析的方法拟合出漫反射表面的光子密度分布,从而可以得出任意位置处的能量密度。

当漫反射表面的任意位置都知道能量密度时,后续的路径追踪通道,来自光泽表面的路径就不需要与其它路径“连接”,因为它击中的任何位置都可以计算出光照贡献。通过这种方式,光源子路径与摄像机子路径之间不再是“连接”,而是以一种“合并”的方式生成一条路径,一条摄像机子路径实际上与该点附近的多条光源子路径“合并”产生,尽管每条合并的路径的光照贡献被非参数回归使用的核函数加权从而大大变小,但是这种方法巧妙地解决了焦散路径的采样,并且由于第一通道生成的光子图可以被重用,这也在一定程度上弥补了路径贡献率低的问题。

我们以及介绍了目前为止离线渲染领域三大经典采样方法:(双向)路径追踪,梅特波利斯光照传输,以及光子映射,它们分别具有各自的优势,能够删除处理特定环境下的路径采样。那么这三种方法能不能被组合在一起呢,答案当然是可以的,因为上述每一种方法都是一种路径采样的方法,让我们得到一个样本时,我们能够按照复合重要性采样的思路把他们全部组合在一起,这样我们的整个渲染流程就有了一个统一的算法架构。

使用更好的采样方法就足够了么?显然还不够,上述这些方法的重心在于减少估计的方差,但是我们仍然需要数以亿计的光线,毕竟场景包含那么多的细节,上述的方法使得样本的分布更加符合图像的分布,这是依靠样本的数量正比于图像的分布来实现的。然而考虑图形分布函数的那些平滑的区域,这些区域由于频率变化极低,因此我们只需要在这样平滑的区域适应极少的样本就能很好地近似图像分布。

因此路径采样的另一个方向是分析图像的频率分布,使得我们仅需要对高频的区域适应更密集的路径样本,而在平滑的区域减少路径采样的样本,这种方法显然能够大大提高路径追踪的效率。

这方面的方法包括频率域分析,流形探索,和自适应采样方法,前两者这里不再详述,请读者日后参考《全局光照技术》,里面包含大量的篇幅,我们这里仅说明一下比较好理解的自适应采样方法。

只适应采样方法的思路很简单,整个采样采样过程是迭代式的,首先算法根据某种基础采样方法得到少量的路径,例如上面介绍的那些方法,这些少量的路径样本形成比较高噪的图像分布,由于这些样本可以看做一些已知的函数值,因此我们可以用它们来拟合出一个“真实”的分布图像,这正是回归分析的思路,最后我们从这个拟合的图像分析其频率分布,然后在下一个迭代阶段,对那些高频的区域放置更多的样本,而减少那些低频区域的样本数量,通过这样的方式来大大减少样本的数量。并且这样的拟合随着样本数量的增加而越来越精确。

更多关于自适应采样的介绍可以参见我的另一个回答:光线追踪渲染中遇到了自适应方法吗?

对于上述的路径采样,如果场景中的物体都是绝对光滑的镜面表面,那么路径在传输过程中的轨迹就绝对由反射和折射定理决定,采样过程能做的事情就是随机决定光线从摄像机或者光源发出的方向。

然而,当我们开始考虑表面的粗糙度时,事情就变得复杂起来!

首先我们需要明白的是,粗糙度到底是什么?它指的其实是表面的微观结构,那么要明白这种微观结构,以及怎样表述这种微观结构,我们需要明确一个“像素”的意义。

在渲染中,像素并不仅仅是指物体表面某个点的位置,当我们通常在讨论“像素”概念这个概念的时候,我们更多的是在描述它的位置坐标,然而像素最重要的特征其实是它的尺寸,当我们在对一个像素进行着色的时候,其实我们是在计算物体表面某一个尺寸范围内的平均颜色,随着距离摄像机的距离发生改变,一个像素的尺寸范围也在发生着改变,只不过因为有了如多级纹理过滤这样的硬件特征,我们在概念中忽略了像素的尺寸特征。

那么像素的尺寸是什么,它是跟图像的分辨率,以及我们的应用程序所能处理的几何尺寸相关的,当我们将摄像机的分辨率确定,并且选择了摄像机所能覆盖的空间面积时,像素的尺寸就被确定下来,在这个尺寸之下,一个像素就是应用程序所能计算的最小范围单元!

从这里可以看出,像素的尺寸是在发生变化的,因此一个像素的尺寸不能代表表面的微观结构尺寸,要想让一个像素对应这种微观结构尺寸,屏幕的分辨率就会变得非常巨大,而我们的计算量也变得非常惊人。

那怎样克服图形应用程序的着色尺寸与物体表面微观结构尺寸之间的不对等呢?答案就是使用一中抽象的概念来描述微观结构,也就是在描述一个像素所覆盖的那些微观结果时,我们使用一个关于这些微面元的分布函数,该分布函数说明,当光线从某个方向进入这些表面时,它有多少几率从观察反向反射出来,这就是Microfacet BRDF的本质。

Microfacet BRDF是一些经验模型,它们是对物体真实微观结构的分析得出的一些近似模型,这些模型可以以简单的方向函数的形式表述,例如游戏当中比较常用的GGX模型。Microfacet BRDF通常使用三个部分的乘积来表述微面元的反射特征:菲涅尔系数,微面元的法线分布函数,以及考虑微面元之间相互遮挡的遮挡函数,遮挡函数的出现是为了近似微面元的空间结构,因为法线分布函数是基于平面的,即它假设所有的微面元处于同一个平面上,因此缺乏微观结构的凸凹特征。

由于同一种物体的表面微观结构通常都是均匀的,所以我们可以认为表面的每个像素都可以使用同一个Microfacet BRDF函数进行表述,因此每个表面的反射特征就仅仅被记录在像素着色器中的一个数学公式,这大大避免了表述物体表面微观结构的内存开支。

Microfacet BRDF的思想其实不光可以用来表述这种小于像素级别的微观结构,它其实提供了一种基本思路:即当应用程序处理的单位尺寸远远大于其覆盖的实际尺寸,并且这些实际的“微小”尺寸的图形特征非常重要时,我们均可以用一个表述这些“微小”尺寸图形特征的分布函数,来为这个宏观的应用程序处理尺寸提供一个代理表述。

举个例子,在VXGI中,我们处理的是一个个体素,一个体素表述的是一个3D的几何区域,这些区域的尺寸可能远远大于一个像素的尺寸,这就是我们选择使用体素的原因,我们通过增加一个处理单元所能处理的空间面积(或体积),来减少计算量。这也好比我们使用像素级别的处理单元,来计算微面元级别的反射特征。因此在这里,为了能够反映每个体素内的表面的光照特征,我们也将该体素内所有像素的法线分布联合起来,组成一个宏观的法线分布函数,这就是VXGI中能够使用体素来渲染表面的精妙所在。

有了上述关于使用一个分布函数来描述着色级别的“微观结构”分布的思路(这里“微观结构”不再是指表面真实的微面元结构,而是指相对于着色尺寸来讲,那些小得多的尺寸,例如相对于一个体素来讲,这种微观结构可能就是一个像素),我们就会思考,能不能将这些分布函数与物体表面的几何表述结合起来,例如当物体离摄像机很近时,我们使用表面实际的微面元分布;当物体离摄像机较远一点时,我们能否将表面的顶点级别的分布与Microfacet BRDF结合起来,形成一个新的分布函数;当物体离摄像机更远时,我们能够把场景中物体网格之间的几何分布或者网格的LOD数据与Microfacet BRDF结合起来,形成一个更大范围的分布函数。这种几何表述与Microfacet BRDF的组合,不仅大大提升了计算的效率,还能够提升图像的精确度。

所以,你现在明白“像素”的意义了吗?同时明白了分布函数相对于应用程序处理的范围尺寸之间的关系了吗?更多关于Microfacet BRDF的知识,可以参见我的另一篇文章:More DETAILS! PBR的下一个发展在哪里? 

当我们把目光转向实时渲染时,实时的积分计算就变得非常奢侈,实时的光栅化管线甚至只考虑直接光照,而将间接光照留给其它的一些近似方法,下面我们将讨论实时渲染中计算间接光照的两种重要的基本思路。

首先说明一下,当前的大多数实时渲染中的全局光照算法,都大大地依赖于环境光照贴图,环境贴图是将摄像机置于环境中的某个位置,然后渲染整个360度的场景,这个环境光照贴图最后被用于提供间接光照,且它被认为是与位置无关的,因此对其采样是只需要提供方向就行。

所以,当我们需要计算一个表面的间接光照时,我们需要做什么事情呢?它实际上就是表面的BRDF分布函数与环境贴图光照乘积的一个积分,想象一下,每个像素都需要计算这样一个半球空间内的积分,这显然是非常奢侈的。

这里BRDF可以看做是一个核函数,因为它是归一化的,这是基于物理的Microfacet BRDF模型能量守恒的特征,因此这个积分可以看做是对环境贴图的一次过滤操作。但是如果这个BRDF核函数本身是各向同性的,那么对于每个反射方向的这种过滤操作都是相同的,于是我们可以将这个BRDF函数在环境贴图上做一个卷积计算,形成一个预过滤(pre-filtering)的环境贴图,它相当于把这个过滤的积分计算提前到预处理阶段,这样在实时阶段,像素的着色计算就是对预过滤的环境贴图执行一次纹理查询而已。

而万幸的是,当前工业中使用的GGX模型正是各向同性的,或者它被普遍使用的原因正是因为源于预过滤等计算的需求。

预过滤是一种非常重要的实时渲染方法,它的优点是能够将积分计算简化为单次纹理读取操作,但是其缺点是仅能处理圆对称的核函数,否则我们需要更高维度的纹理来存储预过滤的数据结果。

如果我们将预过滤的思想从环境贴图中提取出来,我们就会推断,所有关于使用包含一个圆对称核函数的积分,都可以借鉴这种预过滤的思路。例如在前面介绍的VXGI中,在对体素进行采样时仍然会涉及到BRDF函数与体素中的光照分布的积分计算,如果使用各向同性的BRDF函数,这个积分计算仍然可以被转化为预积分,实际上这也是VXGI的思想之一。

而预过滤的另一个在实时渲染中的重要运用,可能被大多数人忽视,或者说没有意识到,那就是多级纹理(mimap)。为什么要使用多级纹理,那就是因为当采样尺寸远远大于物体表面的细节时,例如那些离摄像机比较远的表面,使用单个表面的采样点会导致走样,所以我们希望在这个范围内使用多个采样点来计算该区域的平均值,或者按照某个核函数对这些采样点执行加权平均,这实际上就是一个积分了,但是和上面的原因一样,我们仍然不可能在实时渲染去,去对每一个像素执行一个积分计算,考虑到这个加权的核函数一般是圆对称的,所以我们直接将该核函数与表面的纹理中的纹素执行预过滤操作,这就是多级纹理的原理和思路。

实时渲染中几乎一般以上的全局光照算法,基本上都涉及到预过滤的计算,因为它是实现简化积分计算的主要手段和工具,《全局光照技术》中有大量的篇幅介绍各种预过滤技术,例如阶层式预过滤,基于重要性采样的预过滤,以及一些能够实现实时计算的预过滤方法。

实时渲染的另一个重要的思路是投影近似,投影近似的概念可以使用传统的矢量投影的概念进行描述。考虑一个3D空间的矢量,它的各个分量x,y,z,如果现在我们考虑只有一个1D的空间,要想在这个1D空间内最找到一个对3D空间中矢量的最佳描述,我们可以怎么做呢,答案就是将这个3D的矢量投影到这个1D空间,为什么它是最佳近似呢,因为原始3D矢量和这个投影矢量之间的距离最近。

现在我们将这个投影的概念扩展到函数上,假设我们有一组互不相同的基函数,并且某个空间的任意一个函数都可以表述为这些基函数的一个线性组合,那么组基函数就构成了一个函数空间,有了这组基函数,我们就可以用一个由这些基函数的系数构成的矢量来描述任意一个函数。傅里叶变换就是基于这样的概念,其中那些正余弦函数就构成一组基函数,我们可以用这些正余弦函数的线性组合来描述任意一个函数。

如果我们将傅里叶变换的基函数的维度由单位圆上扩展单位球面上,这就形成了所谓的球谐函数,所以球谐函数的方法基本上和傅里叶变换的方法是一致的,因为他们本质上是相似的概念,只是基函数的维度不一样。

在傅里叶变换或球谐函数中,每个基函数都代表了函数的不同频率特征,理论上傅里叶基函数或球谐函数的维度是无限的,因此我们就需要无限个系数来表述一个函数。但是如果某个函数是低频的,例如间接光照,那么我们能否去掉高频部分的基函数部分,仅仅保留有限个基函数的系数来近似一个函数呢?答案显然是可以的!

实时渲染中有很多这有的处理,例如环境贴图在某些情况下也可以表述为低阶的球谐函数,特别是那些涉及到每像素计算时,或者每一帧需要处理多个方向分布函数时,函数投影近似就是一种非常重要且必要的手段,例如环境中可能需要在多个位置存储于位置有关的环境贴图,以向动态运动的物体提供间接光照,或者某些地方不能预计算,只能实时地执行积分计算,这时候更是需要使用更少量的系数来表述一个方向分布函数。

更多关于渲染中的数学知识可以参见本专栏的文章:渲染中的数学知识

更多关于渲染的概念,可以参见本专栏的文章:在《硬影像》与罗登导演聊渲染技术

以上只是简单介绍一些渲染中的一些重要方法的基本原理和概念,更多更详细的探讨,可以参见《全局光照技术》图书的内容。

我的学习方法建议

第一部分我已经说过,我的起点并不高,我甚至没有经过计算机科学的专业训练,我入行3D渲染领域也大概只有6年多的时间,跟你们中大多数的朋友一样,我也经历过在书海中苦苦求索的经历,我也曾希望寻求一些好的学习方法建议,好的参考资料推荐,我也是一点一点慢慢去摸索过来的,那么我是如何自学并到现在写出《全局光照技术》这本图书的呢?

我觉得计算机很多分支学科知识的学习会包括三个阶段:

  1. 学习编程语言及工具
  2. 大量编程实践
  3. 深入研究理论

计算机科学需要较好的编程基础及能力,考虑到它的基础地位,我会建议将编程能力的培养放到你所在学科的最前面。比如在第一个阶段,你只需要学习最简单的语言及相关开发工具,这个时候不需要将编程涉及到您的复杂专业知识中,你可以选择你所在行业中一些比较简单的示例用来辅助编程语言及工具的学习。

以我自己的经历,我在大学的时候开始自学ASP.NET,C#和Ruby on Rails,那个时候我还不知道怎样用ASP.NET去实现复杂的企业应用以及各种高端的架构知识,因此也就不会纠结于过于繁杂而导致选择迷茫,在这个阶段我就是使用ASP.NET和RoR做了一些简单的示例性的项目,这使我能够快速地入门一种语言和工具。当然的具体语言选择并不重要,编程的很多思想都是想通的。

是不是掌握了基本的编程能力就开始解决或者实现专业问题呢?我的答案是否定的,拿计算机图形学为例,很多算法和技术都是和对编程的理解有联系的,比如当我们在说针对硬件的优化的时候,你需要了解语言的指令怎样在处理器中被执行,变量如果由主存到缓存再到寄存器,最后被用于执行指令;比如当我们在讨论加速结构的时候,你需要了解这些结构为什么在内存中的访问是高效的等等。很多这些知识都可以在脱离特定复杂的领域问题背景下被学习,所以你应该首先具备非常扎实的编程能力,然后你在学习和处理领域问题的时候才会变得更加顺手。

怎样才能训练你的编程能力呢?答案当然是大量的实践,这个没有其他方法,你不可能靠看一本书来提高编程能力。这个没什么可说的,但是我想强调的时候,这种大量的编程实践不能寄希望于公司的项目开发,在软件工程中,公司的项目开发往往都会出于效率的考虑,公司会有大量的架构和编程实践及规范,你是在这些架构和规范下工作,这就像工厂里的一名流水人员,因此你所做的事情多半也是非常简单和重复性的事情,你在这样的背景下其编程能力的提升会非常慢和低效。

要想快速提升编程能力,你必须有一个自己的相对比较复杂的个人项目,或者说你在公司有机会从零去构建一个项目。自己的项目因为需要考虑方方面面的事情,你不但在锻炼软件工程的思维,你可以有更多编程实践的机会,并且你在实践过程中,各个细小的细节会扔给你非常多你不曾了解的问题,然后你需要去学习来解决这些问题,这个成长的非常会非常的快和高效。

比如我在毕业第一年的时候,那时候我想在Web浏览器中实现一个类似App Store的东西,于是我要了解怎么托管用户的App,怎样在app之间调度,我希望开发者能够基于Windows Azure来使用传统的流程开发app,于是我需要去学习Azure,然后发现Azure不能够分配太多账号,用户需要自己单独购买Azure非常麻烦,于是我深入分析Azure REST API的格式,实现了一个完全的反向代理服务器,它完全映射了当时Azure的30多个API,使得我可以将Azure虚拟成无数个账号供开发者使用。最后整个项目下来,我学习到了非常多的知识,并且编程能力得到大大提升,我的那个项目可以参考这里:艺街开放平台开源计划

最后,当你具备扎实的编程能力之后,你对专业领域知识的学习就会更加高效。在计算机领域,很多朋友都会很推崇阅读源代码的学习方法,我其实不太赞成,至少在图形学领域我觉得这种方法并不是最高效的,可能在传统的企业软件开发中,代码实现本身并不涉及太多专业的知识,唯有代码本身可能就是最好的解释。但是在图形学领域,图形学知识本身有比代码实现更复杂的理论,这些理论,有时候你听过阅读代码本身是很难理解的,所以最高效的方法应该是首先从理论着手,对理论的原理理解之后再去阅读代码实现。所以这也是我为什么强度编程能力在先的原因,因为你必须在阅读那些技术论文的时候,对其所涉及的关于实现部分的介绍,不要有任何理解上的障碍,否则这种障碍也会阻止你对理论的理解。

经常会有人问这样的问题:新手适合看什么书?有哪些书值得推荐的?《全局光照技术》适合萌新吗?需要什么基础才能阅读《全局光照技术》?

通常对于这种问题,我都不想去回答,或者说我其实真的不知道该怎么去回答,在我的心中,我确实对于图书没有很严格的质量划分,或者等级划分,除了大家都公认的那少数几本书,大多数对我来说都是一样的。

对于图书的选择,我有两个观点:首先是不要对一本图书寄予太多期望,只要一本图书能够真正帮助你解决某个比较大的困惑和理解,你就已经赚到了;其次,不要视图去给图书和自己定级,你唯一需要考虑的是“这本书是不是某个领域最好的书”?

对于第一个观点,同类图书之间通常都有很大的重合度,所以你不可能希望某本书的全部内容都是对你帮助巨大的;其次,一本书的作者通常都有不同的专注点和擅长的层面,每本书在某些方面都可能有着非常独到的解释和逻辑,但是在其它方面可能并不是很通俗易懂。所以针对这种情况,你有时候需要交叉阅读,哪怕是相同的内容,不同的图书可能给你不同的启发。所以当我在开始学习一种新的技术或概念的时候,我通常都会买几本同类图书,交叉阅读和理解,我基本上不会去区分哪些图书更好,只要能给我理解上带来帮助,我觉得就不错了,毕竟和知识相比,那个书价真的就是不值一提。

第二个观点是不要尝试给自己定级,给自己画一个圈圈!很多朋友会有很强烈的自觉性,很严格地把自己划分为初级中级和高级,当然这里面最多的是第一种情况,所以你看到整个世界都是萌新,我认为这是一种非常不利于成长的习惯。在我整个计算机知识自学的过程中,我几乎从来不会去思考说哪本书是适合新手或者说我这个级别的,我一般只会去询问哪本书是这个方面最好的参考资料。这里有两个原因,首先如果你将自己划到某个小圈圈里面,那么你就把自己与一些非常好的学习资料隔离起来;其次,对于一名图书作者来讲,他通常都不会去严格地把图书的内容分级,比如说完全针对高级开发者,这是因为知识的理解需要背景也需要连贯性,所以每本图书基本上都要从很基本的概念讲起,所以是除非是向论文这种完全不考虑读者背景而仅专注研究内容的介绍,几乎大部分图书都是适合所有读者的,或者说至少初学者也能从中收获不少知识的。那既然是这样,为什么不直接选择最好的资料呢,要知道这些图书的作者对知识的理解更加深刻,他们讨论的内容可能更加高级,但是他们对知识的逻辑理解可能会让你进步的更快。

更多关于我的学习心得,还是留到我的技术巡讲中吧?写这么多文字还是有点累哈!

诚挚邀请您来参加我的渲染技术巡讲活动

看了这些描述,你是不是有些心动了呢?《全局光照技术》不仅包含所有上述这些内容,还包含远远多于这些的深度内容!
更重要的是,我即将于2018年5-6月期间在全国举行一次线下的渲染技术巡讲,您将能够聆听到来自我亲身的技术讲述,这可能是国内目前为止在渲染方面最专业最全面的技术分享,如果您看了这些内容很感兴趣,就赶紧去瞧瞧吧!

渲染的秘密:《全局光照技术》线下技术巡讲暨众筹预订 – 摩点 – 文化创意众筹社区

祝您学习愉快!
秦春林

关于离线渲染有哪些资料可以推荐的?

这是回答知乎上的一个问题,贴在这里做个记录。

关于离线渲染最近最全面的Survey可能是Per H. Christensen和Wojciech Jarosz 2016年的:https://www.cs.dartmouth.edu/~wjarosz/publications/christensen16path.html

我针对其中的一些重要内容做点说明,同时考虑到这篇Survey是偏工业运用的,我再补充一点处于研究性质的一些热点新技术。由于这方面资料几乎都是论文为主,我说些相应概念和技术名称,可以对照阅读相关主题的论文。分两个部分:

首先,工业运用中的主流技术,这方面包括Pixar的RenderMan,Disney的Hyperion以及Weta的Manuka等渲染器,主流当然还是CPU渲染,几个比较突出的技术包括:

  • VCM/UPS:由于BPT的缺点是不能处理SDS(specular-diffuse-specular)路径,PM的缺点是处理diffuse表面不如BPT,2012年的VCM/UPS(Vertex connection merging/Unified path sampling)算法是一个很大的突破,它开始尝试将BPT和PM结合起来,使用PM对light subpath采样,并且将算法统一到BPT中,这样BPT就可以有效处理SDS。近几年中VCM/UPS几乎成了现在主流的离线渲染解决方案(参见The Path to Path-Traced Movies这篇论文);当然,VCM/UPS的缺点是,因为它仍然是BPT的思路,eye subpath并不知道light subpath的情况,所以尽管它能处理SDS,但是两个subpath连接的时候形成的很多full path由于可见性(尤其对于复杂visibility的场景)而对光照贡献率很低,而MLT则很擅长处理Visibility的问题,所以Robust Light Transport Simulation via Metropolized Bidirectional Estimators这篇论文就基于VCM/UPS来使用MLT对light subpath进行采样,这样保证了两个subpath之间的连接更符合最终图像分布。这篇论文也就同时把BPT,PM和MLT三大基础算法组合在了一起! 不过目前MLT方法在工业中运用还不多。
  • 光线排序和连贯性:由于光线传输的随意性使得其并不能利用硬件的指令执行模型以及缓存系统,所以考虑光线的连贯性或者对光线进行排序是能够大大提升路径追踪效率的方法,这方面,基于连贯性(coherence)的路径追踪技术将这些数据分组成一些小的数据包,称为光线包(ray packet),这些数据包包含多个内存相邻的数据,并能能够被同一个指令执行。传统的基于光线包的技术主要是针对主要光线(Primary rays),即摄像机向场景发射出的光线,之后的光线可能向场景随处发射,并且对性能影响更大。2013年,迪斯尼的Christian Eisenacher等在一篇名为《sorted deferred shading for production path tracing》的论文中提出了一种改进方法,这种方法的核心思想是在实际计算之前对光线进行排序,这是Disney的In-house渲染器Hyperion的重要组成部分,这些技术最早用于《超能陆战队》当中。
  • 降噪技术:由于蒙特卡洛方法带来的方差,使得需要增加4倍的采样数量才能换来2倍的方差减少,所以每像素采样数是制约路径渲染器性能的重要因素。然而,光增加采样数量并不能有效的解决噪点问题,因为图像的噪点分部通常是不均匀的,所以我们非常渴望能够在方差更大的区域放置更多的采样,这样能够更有效的减少方差,从而在少量的spp条件下获得非常高的图像质量。降噪技术是目前工业当中非常重要的部分,《疯狂动物城》《海底总动员2》这些没有一部不是严重依赖于降噪技术。降噪技术的核心是适应性采样和适应性重建,这些技术将采样的过程分成过个迭代的过程,每次对采样结果进行误差分析,根据误差分布来得出一个采样分布图,用于在下一迭代中在方差更大的地方放置更多的采样。降噪技术主要有两大类方法:基于路径空间的方法,这类方法直接对路径采样的过程进行跟踪,比如使用频率域分析或者微分分析等,由于计算量比较大,工业中主要使用第二种基于图像空间的方法,这方面的核心人物是Fabrice Rousselle。

其次,处于研究中的一些热点技术:传统的蒙特卡洛方法中每个抽样之间是独立的,它并不能有效辨识这种频率变化特征,要想能够辨识图像的分布特征,我们需要了解每个路径的局部特征,即我们要考虑光线之间的相关性,这可以从多个层面改善路径追踪算法的效率。以下举几种热点技术:

  • 频率域分析:这类技术在光线追踪的过程中,还同时跟踪一条该光线(称为中心光线)对应的一个局部光照场(Local Light Field),这其实是一个像素范围内的空间-方法-时间(spatial-direction-temporal)分布,这个分布会随着光线的传输过程变化,例如表面的BRDF反射,直线传播,阴影遮挡等,如果能够跟踪这个分布的变化,就能够有效地分析图像的一些局部特征。这可以用来实现纹理过滤,适应性采样和适应性重建等很多有用的运用。频率域分析当前比较高效的实现是协方差追踪,它用一个椭圆形的高斯分布来进行这个局部光照场的分布,而这个椭圆形高斯分布可以用该局部光照场的协方差矩阵来表示,这方面的核心人物是Frédo Durand和Laurent Belcour等。
  • 流形/微分几何:流形探索技术的思路是利用费马原理能够非常有效地寻找光泽路径,它主要用于MLT中用于寻找镜面,光泽路径,但是它不能成为一种独立采样算法,因为流形探索依赖于一条已有路径;另外,流形探索在Weta还被用于在漫反射顶点处计算透过光泽的半透明体直达光源的“直接光源”计算,因此渲染焦散效果,层表面散射非常有效。这方面的核心人物是PBRT的作者之一Wenzel Jakob等。
  • 梯度域渲染:梯度域渲染技术也是与具体采样算法相对独立,可以是传统的PT,BPT或MLT,它通过在已有路径上计算梯度来提供图像的高频部分信息,最后通过泊松方程重建图像,传统的蒙特卡洛算法善于对低频部分进行采样,而筛选泊松方程的实质是梯度场充当了高频区域的采样,所以同样同等采样算法和采样数下渲染质量高得多。

以上只是些相对重要和热门的技术,当然不是全部,这些内容都会在THEGIBOOK 中探讨。

聊聊最近几年的路径追踪技术的进展(一)

(注:本文是对《全局光照技术:从离线到实时渲染》一书中路径追踪技术相关内容的一些高度概括和总结,本文仅讨论概念和思路,详细的内容请参考原书相关章节内容,转载请注明源自:http://www.thegibook.com)

路径追踪技术(Path tracing,PT)已经是当下工业中离线渲染使用的主流技术,不管是商业渲染器如皮克斯的RenderMan,Solid Angle的Arnold等,还是迪士尼的in-house渲染器Hyperion以及Weta Digital的Manuka都是基于路径追踪技术。路径追踪算法非常简单,它首先将光照方程表述为面积积分的形式,然后一束光线从光源经过各个物体表面及内部多次交互(反射,折射,散射)后进入图像平面的贡献值(即辐射照度)被转换为这些表面交互点形成的路径的一个积分,其被积函数为这些表面的双向散射系数(BSDF),顶点之间的积分变量变化(change of variables)以及顶点之间可见性的乘积。在统计中,积分可以很简单地使用蒙特卡洛方法进行估计,因此光照传输的问题就转化为对场景进行路径采样,然后对每条路径的贡献值进行平均求和的蒙特卡洛积分计算。

尽管自基本的路径追踪算法被提出以来,各种增强改进的方法被整合进来,然而上述路径追踪技术的“基础架构”几乎没有多少实质性的变化。对于任何行业,工业中主流的技术一般不是当下最先进的技术,而是最成熟可工业化的方案,当前工业中的路径追踪技术优化主要集中在优化算法的执行效率,主要是针对处理器硬件架构进行优化,例如针对缓存系统的优化,增强数据局部性和指令局部性,以及改进光线连贯性(ray coherence)使其能够利用SIMD指令进行高效计算等,这方面比较突出的是迪士尼的Hyperion渲染器,一些渲染器的优化更是几乎能达到某些场景复杂度范围内实时渲染,例如Embree。

然而以上这些内容却不是本文关注的重点(上述内容会在thegibook中详细讨论),本文我们要讨论的是一些路径追踪算法基础架构层面的改进,这些算法还很少出现在当前工业解决方案中,但是确实未来的重要发展趋势。

传统的路径追踪算法是一个单纯的积分问题,因此可以使用蒙特卡洛方法来估计,然而蒙特卡洛方法的每个抽样是独立的,因此很难有效快速对一些比较困难的路径进行采样,一些很难被采样的路径需要巨量的采样数才能达到“令人满意”的结果,这导致非常缓慢的收敛速度。尽管开始的收敛速度非常快(4倍采样数量可以减少1/2的误差),但是越到后面会花费更多倍的时间。传统的方差缩减技术如重要性采样,分层采样,拟蒙特卡洛方法(Quasi-Monte Carlo)仍然不能改变这个收敛速度。

要提升蒙特卡洛估计的收敛速度,直观上,我们需要能够辨识每个图像的分布特征,然后在一些高频变化区域增加采样的密度。然而,传统的蒙特卡洛方法中每个抽样之间是独立的,它并不能有效辨识这种频率变化特征,尽管梅特波利斯光照传输(Metropolis light transport,MLT)通过采样点之间的相关性能够寻找一些困难路径,本质上它仍然并不善于处理和分析频率域特征。要想能够辨识图像的分布特征,我们需要了解每个路径的局部特征,即是我们需要对路径追踪算法引入微分运算。路径的微分涉及路径与附近局部范围内光线的差值,即图像的局部特征,因此它引入了光线之间的相关性,可以从多个层面改善路径追踪算法的效率(也正是如此,我们认为这是近几年路径追踪技术领域的重大进展)。

最近几年路径追踪领域的一些重大进展正是微分几何(differential geometry)的引入,由于微分反应的是流形(manifold)的局部变化,因此非常善于寻找图像分布中的高频部分,目前路径追踪算法中的微分运用主要包括:以通过计算纹理过滤函数实现反走样的光线/路径微分(ray/path differential)和协方差追踪技术(covariance tracing);以通过计算梯度图像用于利用筛选泊松方程(Screened Poisson equation)重建目标图像的梯度域渲染(gradient-domain rendering);以及通过将微分几何结合费马定理(Fermat principle)用来寻找镜面/光泽路径的流形探索(manifold exploration),半矢量空间光照传输(half vector space light transport)等技术;此外,微分还被用于适配性采样(adaptive sampling),成为重要的降噪技术(Denoising)。

以下我们就来从概念和思路上分析上述这些技术,当然我们可以提前知道的这些技术的一个重要共同点就是:这些微分计算通常不需要通过光线追踪引入新的采样点,否则那样的计算成本就很高,这些技术通常都是利用微分几何和某些假设进行单纯的微分计算,这里主要的工具是一阶泰勒展开式(first-order Taylor approximation)和顶点处的切线平面空间(tangent space),因此,这些新技术通常都可以很容易地集成于目前主流的路径追踪技术基础架构之上,更详细的内容请参考thegibook中相关章节。

接下来我们讨论的每一种微分的运用都涉及一个相对比较独立的层面,在阅读这些内容的时候,你首先需要非常清晰的了解的是它用来解决什么问题,因此我会在每一节的开头提出这个问题,然后读者可以围绕这个问题来阅读每一小节,即每一种技术。

光照传输的局部频率域分析

微分反应的是图像的局部特征,因此其在图像渲染中最直观的运用就是用于反走样(anti-aliasing)。光线追踪算法中由于采用不足导致的走样问题可以分为两大类:对路径空间(path space)的采样不足和对纹理空间(texture space)的采样不足,本质上这都是由于路径采样的不足导致的,但是它们通常使用不同的方法来解决。一个像素区域内的光线从屏幕空间发射出去之后,随着光线在表面的各种交互这个面积可能会无限扩散,所以单纯增加路径采样的数量可能也很难弥补纹理走样(texture aliasing)的问题。所以它们分别对应路径追踪算法中两个比较独立的反走样技术:路径空间采样不足主要对应于降噪技术,纹理空间采样不足对应于纹理过滤技术。

目前降噪技术主要的思路是针对图像的分布特征进行适配性采样以及适配性重建,前者对应于在图像的高频区域放置更高的路径采样密度;后者对应于根据图像特征对不同的区域使用不同的重建过滤器(reconstruction filter)。对路径进行适配性采样的方法可以分为两类:第一种直接对光照传输方程进行分析,称为先验方法(a priori method);而第二种只对屏幕空间的图像进行分析,称为后验方法(a posteriori method)即是说与路径采样的方法无关。

光照传输的局部频率域分析可以用于纹理过滤以及作为降噪技术中的先验方法。以下我们讨论局部频率域分析的方法和思路。

路径的局部频率域分析虽然不是源自光线微分(ray differential)技术,但是跟它有很大的联系。光线微分技术的核心思路是随着光线的传播跟踪该光线对应的频率区域一个像素的大小,这称为光线的足迹(footprint),如下图所示,在路径追踪算法中追踪每条路径的时候,同时沿屏幕空间x和y方向分别发射一条一个像素大小的偏移路径,然后对每条基础路径同时跟踪两条偏移路径。路径在每个顶点处的足迹大小则可以通过积分计算出来。当然光线微分技术并没有完全利用微分几何的知识,因为它假设偏移路径和基础路径在每个路径顶点处位于同于平面-即该顶点的正切平面(tangent space)上,所以光线微分的计算大大简化了。

由于假设偏移路径和基础路径和表面的交点位于同一平面,上述光线微分技术仅适用于镜面表面(specular surface),路径微分(path differential)在此基础上将光线微分扩展到了光泽面(glossy surface)和漫反射表面(diffuse surface),它虽然有比较理论的推导,但是实践上主要使用经验方法,即根据表面的粗糙度用来缩放足迹的大小以支持光泽面和漫反射表面。

上述的光线/路径微分技术实际上是光线的一种局部特征,传统的路径追踪算法中每条光线之间的独立的,所以不能够分辨这种局部特征。然而上述方法还是有很多缺点,例如光线经过多次传播之后可能形状非常不规则,而两条偏移路径计算的四边形无法表达这种形状;其次偏移路径可能被阻挡,而光线/路径微分是忽略这种阻挡的;为了更好的追踪光线的局部特征,我们需要更完整的对光线的传输过程进行分析。

目前对光线进行局部分析的方法的思路主要是将光线的局部特征表示为一个空间-方向分布(spatial-angular distribution)的局部光照场(local light field),如下图所示,黄色的中心光线表示基础光线,周围的一些具有不同位置和方法分布的光线是一些虚拟光线,它们共同构成一个局部的空间-方向分布。例如对于摄像机光线,它的局部光照场的分布就是一个像素范围大小的一个过滤器,该过滤器对于每个不同方向和位置处的光线都具有一个不同的权重值,这个过滤器就反映了摄像机路径的局部特征;对于光源路径,如果是面积光,则局部光照场的方向为固定值,而位置分布于面积光源的各个位置。

有了局部光照场的描述,剩下的事情就是计算该光照场随着中心光线的传输。早期的思路是将光线在传输过程中的各种交互过程看做一个操作函数,例如光线的直线传输(transfer),反射,折射,阴影遮挡等,如下图所示,然后在局部光照场函数的频率空间进行交互,这样就可以跟踪局部光照场随中心光线的传播过程。

上述的傅里叶分析过程计算成本相对较高,为了简化局部光照场的传输,协方差追踪(covariance tracing)技术考虑可以将一个4D的空间-方向分布近似为一个椭圆形高斯分布(elliptical Gaussian filter),如下图所示,然后该分布可以使用该4D光照场的协方差来表示,所以局部光照场可以用其协方差矩阵(covariance matrix)来表示。然后这些对局部光照场的传播操作数就转变为矩阵操作,最后在计算足迹的时候再从协方差计算出该4D局部光照场的尺寸,这就可以用来替代上述的光线/路径微分技术用来实现纹理过滤。

协方差追踪不仅用于纹理过滤中计算局部光照场足迹的大小,还被用于适配性采样,如果我们从光源路径方向追踪光源的空间-方向分布至屏幕空间,则我们可以在屏幕空间分析图像的频率分布,进而可以用于适配性采样,如下图所示。下图的局部光照场是一个5D空间,它还包含了时间维度,因此可以用于计算运动模糊(motion blur)效果,在该图中,首先从光源发出光线经过一次表面交互(即直接光)之后投射一个椭圆形分布到屏幕空间,然后根据这些分布就可以计算出图像的频率分布,这些分布特征被用于适配性采样。

除了协方差追踪,光照传输的局部频率域分析还包括其他一些方法,但是目前协方差追踪是更简单和高效的一种技术。当然协方差追踪的限制是它将局部光照场近似为一个椭圆形的分布,实际的局部光照场分布特征可能还需要更负责的形状表述,这也是未来的优化方向。

下一篇我们将讨论梯度域渲染和流形探索或者半矢量空间光照传输。

Robust Light Transport Simulation via Metropolized Bidirectional Estimators

今年SIGGRAPH Asia上最感兴趣的莫过于这篇Robust Light Transport Simulation via Metropolized Bidirectional Estimators,看标题就很牛逼:原本比较独立的光线追踪两大经典算法BPT和MLT合体了!这里简要描述下这篇论文的思路:

传统上我们认为光线追踪有三大基础算法:BPT,PM和MLT,它们都是相对比较独立的,因为它们各自采用不同形式的光照公式,例如BPT的路径积分形式,PM的范围估计,MLT则是将整个图像看成一个分布,这些看起来都是完全独立的。然而它们各自都有优缺点,好的想法是能不能把它们组合起来。

例如BPT的缺点是不能处理SDS,PM的缺点是处理diffuse表面不如BPT,MLT由于抽样点的相关性导致处理高光表面很低效,因为Markov chain始终在高频尖锐的部分徘徊,从整个图像上看stratification不够好;

2012年的VCM/UPS算法是一个很大的突破,它开始尝试将BPT和PM结合起来,使用PM对light subpath采样,并且将算法统一到BPT中,这样BPT就可以有效处理SDS。近几年中VCM/UPS几乎成了现在主流的离线渲染解决方案(参见The Path to Path-Traced Movies这篇论文);

然而VCM/UPS的缺点是,因为它仍然是BPT的思路,eye subpath并不知道light subpath的情况,所以尽管它能处理SDS,但是两个subpath连接的时候形成的很多full path由于可见性(尤其对于复杂visibility的场景)而对光照贡献率很低,而MLT则很擅长处理Visibility的问题,所以这篇论文就基于VCM/UPS来使用MLT对light subpath进行采样,这样保证了两个subpath之间的连接更符合最终图像分布。这篇论文也就同时把BPT,PM和MLT三大基础算法组合在了一起!

具体做法就是,首先使用传统MC在图像平面生成eye subpaths,这里不使用MCMC产生eye subpath的原因是MLT在图像平面(image plane)的stratification不好;然后从这些eye subpath的位置开始产生Markov chain来产生light subpath(所以MLT在这里是用来产生一个subpath分布而不是整个图像分布)。这样整个算法能处理高光和复杂Visibility这两大难题的场景。

论文下载:http://www.ci.i.u-tokyo.ac.jp/~hachisuka/

全局光照技术进化史1-光线追踪篇

本来计划是最近一年专心写书,不要花心思和精力写博客的,因为写一篇优质的博客文章其实也要花费不少的时间构思的:单篇博客虽然文字少但是你可能需要花费更多的精力在有限的篇幅内包括更多上下文信息,以及更精简地组织内容,在我看来它的创作付出不亚于图书内容写作(当然如果作者对自己要求没那么严谨的话可能也没那么严重)。

《游戏引擎全局光照技术》采取了一种新的出版形式,它从写作第一章开始,就积极和社区互动并开始宣传,其方式和游戏发行的思路一致:即在开发阶段不断推出测试版积极和玩家互动,并收集反馈信息进行持续改进。这样做的好处是:读者较早获得试读版的信息,了解和监督了书的写作质量,从而可以做出有效判断是否值得购买,糟糕的图书质量可能在这一阶段就直接被淘汰,甚至失去了出版意义,从而保障读者利益;同时对于作者,我能够持续吸收社区反馈意见以改进内容质量,使图书的质量可以不断地形成增益,好的内容能够被社区传播扩散;这对于读者和作者都是一件共赢的事情。

为此,我已经提供了该书1,2,3三章共计157页正文的试读内容供所有对该书感兴趣的朋友免费下载,如果您还不知道可以从这里下载。然而尽管如此,从目前社区讨论的信息来看,大部分读者还是不太明白这本书跟其它同类书籍会有什么不同,尽管我已经使用一个问答的形式来简要概述这本书的特点,然而我们都知道,这些文字跟你在技术大会上那些厂商递给你的一个介绍他们公司产品的小册子上的文字一样:在你使用它们的产品之前,那些文字通常跟屁没有什么差别。

所以我需要写一点更通俗篇幅相对长一点的文章来解释这本书的内容和特点,通俗使得您可以像读其它社交信息一样很轻松地进行阅读,不需要太多思考和理解,而篇幅需要长到足以介绍这本书覆盖的内容和特点。正巧在百度贴吧的“孤岛危机吧”看到一篇想要了解各种全局光照技术的特点及联系的帖子,他所提出的问题正是本书试图去讨论的内容,所以我就想回答了他的问题其实基本上能够说明这本书的内容和特点。

学习全局光照技术(Global Illumination,以下简称GI)比较头疼的一个因素是它的方法派别太多了,每个算法可能涉及到完全不同的数学方法,而如果我们没有对方法本身的思路具有一定的理解,在运用方面也不会很顺手,尤其涉及到要进行优化或修改以满足特定需求时,你必须要比较全面的理解该方法:它的起源,历史,数学模型,新方法的优缺点,它在计算性能和图像质量方面有着怎样的折中等等;并且,通常每种方法并不像一个软件模块那样比较独立很好理解,每种GI算法往往涉及CPU/GPU的数据结构表述,内存布局以及访问,和渲染管线其它阶段(如Deferred Shading,AA等)的协作,图形接口的运用,算法处理器级别的优化等,这些因素使得GI的学习并不那么轻松。

所以我在2015年初就萌生了写作一本围绕各种GI技术来进行内容组织的书籍,它不像《Real-time Rendering》这类书籍一样基本上以各个理论知识点为中心,它以方法为中心(尽管如此,本书还是包括了将近300页的基本理论知识的介绍),着重讨论各种GI方法背后的思路以及方法之间的联系,因此它较偏理论性的书籍拥有更强的实践性。这种写作思路其实类似于《Advanced Global Illumination》(简称AGI),但是AGI基本上主要围绕路径追踪和辐射度理论两种方法来讲述,其它一些如光子映射等有一定的介绍,但是篇幅极短;本书不但会介绍路径追踪和辐射度理论等这些离线全局光照技术,还会介绍时下比较流行的距离场,体素等实时的全局光照技术,并且本书结合Unreal Engine等游戏引擎来讲述,读者更够更好地理解这些引擎的功能特性。

这篇文章,我们就来看看全局光照技术的进化史。遵循通俗的原则,本文不会包含数学公式,全部内容是以文字和配图描述,当然这样的方式肯定不可能包含很多细节,它更注重的是思路描述,更具体的信息还请您参考《游戏引擎全局光照技术》图书内容。

我们从光线追踪算法开始。光线追踪算法的起源可以早至1968年,Arthur Appel在一篇名为《Ray-tracing and other Rendering Approaches》的论文中提出的Ray Casting的概念,我们称为光线投射,如下图所示,光线投射其实只是一根单一的从一个点向一个方向发射出光线,它与场景中的物体相交时停止,Appel的算法中使用了View Ray和Shadow Ray两条光线,这计算出来的其实就是光照方程中的直接光照部分。

Ray Casting

1979年,Turner Whitted在光线投射的基础上,加入光与物体表面的交互,是光线在物体表面沿着反射,折射以及散射方式上继续传播,直到与光源相交。如下图所示,这种算法形成了一个递归的光线穿梭,因此此时不再是一根单一的光线,而是形成了一个光传输的路径,此时的算法称为递归光线追踪(Recursive Ray Tracing,或者Whitted-style ray tracing);显然,光线在多个表面上反射或折射,间接光被考虑进来,光在经过每一个表面的时候,通过该表面的反射/折射率的“过滤”,它将物体的颜色渗透到邻近物体的表面上,这就是我们所说的Bleeding。

Recursive Ray Tracing

然而Whitted的模型是基于纯高光反射的,它假设物体表面绝对光滑,这显然和大自然中大部分物质的表面属性不相符。在计算机图形学中,一个像素的尺寸远远大于光的波长,在这个微观尺寸(Microfacet)下,物体表面是不光滑的,也就是说进入一个像素的多个光线可能分别被反射到不同的方向上,根据表面粗糙度的不同,这些散射的方向呈现不同的分布,非常粗糙的表面可能比较均匀地周围反射,而比较光滑的表面反射光则集中在光滑表面的反射方向附近。在现代渲染技术中,这些反射特性通常被使用Microfacet BRDF公式表述出来,它基本上使用一个简单的粗糙度方向就可以模拟出比较真实的光反射分布。结合金属性等一些参数,这就是目前流行的基于物理的渲染模型。

ray-optics-6

通常这些不同的分布被使用漫反射和高光反射两种分布来表述,如上图所示。1984年,Cook提出了分配光线追踪(Distribution ray tracing),他使得原来一束单一的反射光变为围绕一个空间中漫反射或高光反射范围内的积分计算,如下图所示。为了计算积分方程,蒙特卡洛方法被引入,所以Cook的方法又称为随机光线追踪(stochastic ray tracing)。

Stochastic ray tracing

Stochastic ray tracing

然而,Cook的模型计算代价非常高,每一条从摄像机发出的光线在表面点是被反射至多个不同的方向,分散成多束光线,以此递归,每条光线最终形成一个光线树(a tree of rays),尤其对于间接漫反射光,它几乎要反射至整个可见空间。

Cook的模型是由光照公式递归的特性决定的,光照公式中每个入射光的值来源于其它许多表面点反射的计算结果。1986年,Kajiya统一了光照公式,并推导出了光照公式的路径表述形式,使得光照公式由一个递归的结构,变成一个路径函数的积分,因此蒙特卡洛的每个随机数只要产生一条路径即可,这些路径不需要是递归的,因此每条路径可以随机生成,然后每个路径的值作为一个随机数用于计算最终的光照结果。这种新的形式称为路径追踪(Path tracing),如下图所示。在路径追踪算法中,首先在场景中物体的表面随机产生一些点,然后将这些点和光源以及摄像机链接起来形成一条路径,每个路径就是一个路径函数的随机值。这样的路径,根据场景的复杂度,每帧可能包括上亿条光线,因此传统的路径追踪算法很难运用到实时渲染领域。

path tracing

path tracing

上述这样的产生随机路径的方式有一个问题,有相当部分的路径组合由于表面间可能被遮挡而形成无效的路径,对最终光照没有任何贡献,因此大部分实现都是以增量的形式,在每个有效的反射或折射方向上进行随机采样,已形成更多有效的路径;

另一个问题是由于光源面积相对于整个场景很小,由摄像机出发的路径最终落在光源面积内的几率很小,因此双向路径追踪(Bidirectional path tracing)被提出,它分别从光源和摄像机两个方向出发,分别经过一定的路径之后,将该两条路径的终点链接起来形成一条完整的路径,这样大大增加了光源的有效贡献,可以从下图看出两者之间的区别。

untitled

当前大部分高质量的离线渲染器基本上都是基于路径追踪算法实现的,然而路径追踪的计算成本仍然非常高昂。现代路径追踪算法的发展主要有两大方向:其一是围绕提升上亿光线之间的连贯性(coherence)来提升处理器的利用率从而大幅提升计算效率,其二是微软Metropolis算法来使采样的随机路径更接近最终图像的真实颜色分布,后者称为MLT(Metropolis Light transfer)。

光线/路径追踪算法高昂的计算成本不仅来源于蒙特卡洛积分使用的大数定律要求的巨大的光线数量,另一方面重要的因素还在于路径追踪算法及其使用的数据结构不能适应现代CPU/GPU使用的执行模型。首先是内存执行模型,由于处理器计算单元的速度和内存数据存取的速度存在巨大差异,计算单元从内存中获取数据存在巨大的延迟(Latency),现代处理器非常严重地依赖于缓存技术,即将较大一块的内存数据缓存在具有更高读取速度的缓存中,如下图所示,缓存系统通常设计为多层机构,每一层比下一层具有更高的访问速度,但是更高速度的缓存硅片的成本更高,所以更高速度的缓存往往存储的数据量更小,这样顶层的一级缓存的速度跟寄存器的访问速度比较接近,通过缓存系统,计算单元到内存的数据访问延迟就被掩藏了。

path-14

缓存系统是根据传统应用程序的特点设计的,通常,相邻指令使用的数据在内存区域也是相邻的,所以相对寄存器更大一块的数据能够被多条指令使用。当指令从上一级缓存获取不到需要的数据时,就会从下一级缓存一次性获取另一块数据,并替换原来的数据,这种情况称为缓存失效。因此应用程序必须在数据上保持一定的连贯性,才能充分利用缓存特性,以提高计算性能。而路径追踪算法显然不符合这样的条件,每一条光线可能随机穿向任意一个方向,从而与环境中任意表面进行相交,所以相邻的指令使用的数据往往分散在内存中的各个区域,大大减少缓存命中的几率。

处理器架构的另一个特点是称为单指令多数据(SIMD)的计算模型,在SIMD中,寄存器一次性读取多个数据变量,这些数据被同一条指令执行,例如传统的CPU环境中SIMD寄存器可以读取128位数据,分别可以表示4个32位的数据,如下图所示,而在GPU环境下,每个GPU线程束可以一次性计算32个线程,当这32个线程所需要的数据在内存结构上相邻时,它们可以被一次性存储,大大减少每个线程获取线程带来的事务开销。

path-16-2

所以,基于连贯性(coherence)的路径追踪技术将这些数据分组成一些小的数据包,称为光线包(ray packet),这些数据包包含多个内存相邻的数据,并能能够被同一个指令执行。传统的基于光线包的技术主要是针对主要光线(Primary rays),即摄像机向场景发射出的光线,之后的光线可能向场景随处发射,并且对性能影响更大。

2013年,迪斯尼的Christian Eisenacher等在一篇名为《sorted deferred shading for production path tracing》的论文中提出了一种改进方法,这种方法的核心思想是在实际计算之前对光线进行排序,如下图所示。

path-10

在实际处理中,这主要分为三步(如上图左边的流程):首先,在每次增量计算光线与场景相交时,首先对光线进行排序,并将这些经过排序的光线按照方向以及数量大小分成多个包,然后以这些包为单位进行计算;其次,对场景建立一个BVH的加速结构,经过打包后的光线是连贯的,所以其相交计算涉及的场景表面在空间上也是连续的,因此能够比较友好地使用缓存和SIMD处理器特性;最后,由于来自各个方向光线可能与同一表面进行相交,所以在相交计算时直接计算光照反射并不是很高效,所以迪斯尼将着色分离出来,在相交计算的时候,所有相交点与纹理信息进行关联,纹理被划分成一些区域,然后着色计算以纹理区域为单位进行计算。通过以上这些优化,如下图所示,Disney新的路径追踪渲染器渲染性能得到极大提高,这些技术连同Disney的BSDF等技术一同被首次运用在《超能陆战队》以及后续的电影中。

path-12

基于连贯性的路径追踪算法的思想能够很好地利用现代处理器的架构特征,不管什么样的路径追踪算法基本上都可以在这方面进行改善,这也是传统路径追踪技术走向实时的方向。

基于Metropolis算法的MLT方法则着重于更准确地对路径进行采样,以计算更高品质的图像,毕竟传统的路径追踪算法很多路径采样的贡献率可能很低。Metropolis算法的核心思想是使用马尔可夫链(Markov Chain),它对当前随机数进行一个适当尺度的扰动(perturbation)以产生一个新的随机数,然后使用逼近真实分布函数的概率来对新的随机点进行取舍,这样使得新的采样点满足实际分布函数的分布。

qq20161106-02x

由于路径追踪算法每个随机数产生的是一个路径计算的光照结果,因此MLT中的每一个随机数是一个路径,这个新的路径根据一定的策略对前一路径的某些部分进行扰动,以产生一条新的路径,然后计算出的该路径的光照结果就是每个随机数计算出的光照数值。

由于MLT中的每个随机数是一个路径的结果,整个路径的分布就是整个图像的颜色分布,因此MLT计算出的是整个图像的结果,然后每个像素点需要使用过滤器对每个像素周围的颜色值按照一定的权重比例进行加权平均。

qq20161106-12x

原始的MLT算法直接在路径空间(Path space)对路径中的顶点进行扰动,这样新产生的路径可能存在被遮挡的可能,因此产生非常多的无效的被舍弃的随机数,Csaba Kelemen等于2012年的论文《A Simple and Robust Mutation Strategy for the Metropolis Light Transport Algorithm》中提出在一个超立方体(hypercube)作用域内,即所谓的Primary space对路径进行扰动,超立方体内的每个随机数是[0,1]范围内的均匀分布产生的随机数,这个随机数使用BRDF等分布的逆向变换算法求出实际的方向或者点灯随机数,这些随机数用于产生新的路径,这样的基于Primary space产生的随机路径具有较高的接受率。

qq20161106-22x

更新的基于MLT的算法基于路径求导来产生更好的符合表面光照分布的扰动,例如对于高光,它能够在高光方向上选择更多的方向,以使MLT计算的结果更接近于真实图像的分布,如上图所示。关于路径求导,以及其它如基于梯度的MLT算法等这种使马尔可夫链产生的随机数分布更加接近图像真实分布的思路,是路径追踪技术方向目前比较热门的主题。