本文共 4777 字,大约阅读时间需要 15 分钟。
全球图形学领域教育的领先者、自研引擎的倡导者、底层技术研究领域的技术公开者,东汉书院在致力于使得更多人群具备内核级竞争力的道路上,将带给小伙伴们更多的公开技术教学和视频,感谢一路以来有你的支持。我们正在用实际行动来帮助小伙伴们构建一套成体系的图形学知识架构,你在我们这里获得的不止于那些毫无意义的代码,我们这里更多的是代码背后的故事,以及精准、透彻的理解。我们不会扔给人们一本书或者给个思路让人们去自学,我们是亲自来设计出好的课程,让人们明白到底背后还有哪些细节。
这里插播一个引擎大赛的消息,感兴趣的同学可以看一眼,这也是东汉书院的立项使命:
大赛官方主页东汉书院-自己动手写游戏引擎edu.battlefire.cn
简介
我们终于来到了跟渲染有一点点关系的部分了,这部分我们看完之后就可以去改进我们的多重采样部分的代码了。我们应该抽象的看待我们之前获取到的那些数学公式:
这里实际上相当于 。然后S就相当于是概率,我们应该想起来我们先前推导来推导去的那些公式:pdf、cpdf、r、e、f。
动机与初心
还记得不记得我们在渲染的时候碰到的是啥问题?我们碰到的问题是不知道如何去给一个光线权重的问题,我们如果对某一个像素进行渲染的时候,只发射一根光线去计算这个像素点的颜色的话,会有很大的噪声。
为了解决这个问题,我们引入了多重采样,但是在多重采样后,我们又面临了曝光度过大的问题,所以我们需要给每一根光线一个权重,来缩放它们最终生成的颜色在最终画面里的比重。这样一来,我们在光线追踪学习的时候直接采用的是平均值的方式,我们先把所有光线生成的颜色加起来,然后除以光线的总数量。这种方法简单粗暴,但是不是最好的办法,我们其实更期望能根据光线自身的特性来给予光线一个权重,于是乎我们开始了蒙特卡洛方法的研究之旅。
有些人学着学着就把初心给丢了,我们一次又一次的帮助同学们捡起这个玩意。
回归初心
在故事的开始,我们是要计算面积的:
年轻的我们写下了这样的代码来解决这个问题:
#include #include #include float randf() {
return float(rand()) / float(RAND_MAX);
}
float srandf() {
return randf()*2.0f - 1.0f;
}
int main(int argc, char **argv) {
int count = 1000000;
float sum = 0.0f;
for (int i=0;i
float x = randf()*2.0f;
sum += x * x;
}
printf("area is %f\n", 2.0f*sum /float(count));
return 0;
}
此时我们可以把后来我们捣腾的这些东西塞进之前的代码里去了,当我们采用蒙特卡洛方法来计算面积的时候,x的取值采用的是随机数,而不是像黎曼和里一样采用左柱子的高度或者采用右柱子的高度。
所以这样的算法就有一个特点就是:你随机的次数越多,得到的结果就越逼近真实的值。所以我们在这里做一个代码到理论的思路映射:
我们这里使用蒙特卡洛方法来计算面积,所以代码里的那个x实际上是随便拿过来的一个东西,这个东西既可以是随机数r本身,也可以是r经过某个函数f处理后的结果,最重要的是,我们要通过这个x知道这个x的pdf和cpdf之类的东西。于是乎,我们想起来之前给同学们推了半天的那些pdf和cpdf、r以及e。我们来修改代码一下,如果pdf为
,那么:
#include #include #include float randf() {
return float(rand()) / float(RAND_MAX);
}
float srandf() {
return randf()*2.0f - 1.0f;
}
int main(int argc, char **argv) {
int count = 1000000;
float sum = 0.0f;
for (int i=0;i
float r=randf();
float e=sqrtf(4*r);
sum += e * e;
}
printf("area is %f\n", 2.0f*sum /float(count));
return 0;
}
上面这部分就是for循环里面的代码,所以我们把之前的代码与先前我们捣腾的一系列公式练习起来了。同样的在这里r的范围是0~1,e的范围是0~2,这样求出来的东西也是满足我们条件的。与先前的代码相比,唯一不一样的地方就是,我们使用了一个f函数对我们的随机数进行了处理,即便进行了处理,我们依然可以把e看成一个随机数,且它的范围满足我们的代码需求,在0~2的范围内。
只不过e与r不一样的地方就在于,我们有关于e的pdf和cpdf。如果我们把这里for循环的次数看成是对某一个东西的采样次数,我们在通过蒙特卡洛方法去估算一个目标结果的时候,采样次数越多,那么估算的精度就越高。而在以往的算法里,每一次随机出来的数据的权重都是1,我们认为这么干是不科学的,我们要给每次采样出来的数据给一个大概的权重,那么这个就可以通过pdf来获取。当我们能够给出每一次采样数据的权重了,之后,我们最终的结果就可以用sum直接除以count,而不必非得采用黎曼和进行面积积分的公式来算了。意思就是说,最后的面积可以直接等于:
因为我们在for循环中会给每次采样的样本一个权重了。而每个样本的数据权重是多少呢?这是我们要思考的问题了,现在我们列举一下我们知道的东西:一个随机数r,它的范围是0~1
一个函数f,它是根据pdf推导出来的
一个数值e,它是f把它变换出来的
一个pdf,它的全称叫:概率密度函数(probability density function)
一个cpdf,它的全称叫:累积分布函数(cumulative probabilitydistribution function)
我们知道这里的推导顺序是,先有个r,然后我们捏造一个合适的pdf,然后就可以推导出cpdf,进而能推导出f函数。因为f函数跟cpdf是一个互为反函数的关系。这里这个pdf的选择就是一个经验性问题,在渲染里面也叫做importance sampling。
综上所述,我们r是随机数,pdf是自己选择出来的一个importance sampling函数,然后其他的东西都是通过这俩家伙推导出来的,那么这到底与咱们的多重采样有个毛儿的关系呢?法克,怎么这么绕,我女朋友手里的蚕豆都掉地上了好吗。
多重采样
作为程序员,我们始终要知道从哪里输入,以及从哪里输出。我们的输入就是r和你自己选的一个pdf,然后输出是e、cpdf以及那个把r转换成e的函数f。
我们现在关注的核心是如何给你一个r,然后这个r输入给f函数,然后生成一个e,然后你要给e多大的权重的问题。实际上这个非常简单,你就给e与它出现的概率相关的权重不就完事了嘛。这也是pdf做的事情,它能影响你最终的采样结果数据中,你更重视哪一块的数据,要不然你叫他概率密度函数干啥,既然是概率密度,那么就意味着有的地方概率大,有的区域的数据的概率小。数值如果概率大,那么它的影响权重当然就大,如果它的概率小,那么它的影响权重当然就小啦。
对应到代码咱们怎么处理呢?我们可以直接让权重等于
,他喵的为啥?为鸟个啥它的权重可以这么玩?
还是前一段话我们描述的问题,如果某数据出现的概率大,那么它的权重就高,我们就采样它的数据更多一点,如果它出现的概率小,那么就让它的权重低,我们就采样它的数据更少一点。
如果作为一个码农,实在是抽象不到那个级别,那么我们就采用特殊法来带入看看对不对,我们用数学归纳法来证明,也就是先给出结论,然后你自己弄几个特例去试试就完事了,比如说,我令权重为
,那么我们for循环里的代码应该这么写:
float r=randf();
float e=sqrtf(4*r);
float weight=
sum+=e*e*weight;
然后我们举几个特殊的例子就知道了,当
的时候,我们有
那么这个图中,我们可以看到的是,越靠近左边,概率越低,因为概率是积分面积嘛。所以这里就有,当e的值越小的时候,概率越低,e的值越大的时候,概率越高。
辣么,我们举个例子扔进去试试:
当r=0.25的时候,e=1.0,此时weight=2
当r=1.0的时候,e=2.0,此时weight=1
此时与我们的预期是相符合的,因为
这样的pdf会让0~1之间的数值在进行平方操作了之后更靠近0。所以靠近0的部分的概率会更大一定。
因此这里的pdf作为importance sampling函数,它代表了我们的采样意愿,我们更愿意采样哪里的数据来作为我们的画面颜色。
当我们选用
的时候,我们的代码应该是下面这样
当我们的pdf选用
的时候,那么我们的cpdf和f函数都要变化,其中比较重要的是f函数,它的推导方法我们在先前的文章中已经介绍,就不再做解释,f函数求出来应该为
最后的最后,我们把求面积的公式采用非黎曼和的方式,而是采用真实的定积分公式进行积分的话,应该是这样子的:
对应的cpdf为:
积分出来后得到在0~2的区间上为:
所以如此一来,f就可以知道了:
好了,公式推完上代码:
完结、撒花,我们到这里就搞明白了一个问题,怎么把概率密度跟多重采样时每个样本的权重结合起来考虑。所以只要你给我一个定积分的公式,加上一个随机数,我就给你一个权重,而这个不正是咱们光照计算里那些玩意嘛?每个光照有多少被反射、有多少被折射。
经过这篇文章后,你的多重采样模块的代码不再是直接对所有样本的和求平均了,你会给每个样本一个权重,而这里每个样本拿到的权重都是与它自身有关。
参考资料《Ray Tracing_ the Rest of Your Life》电子书www.realtimerendering.com疯狂的程序员:引擎内核基础技术-极坐标是何物zhuanlan.zhihu.com疯狂的程序员:引擎内核基础技术-球面坐标是何物zhuanlan.zhihu.com疯狂的程序员:蒙特卡洛方法-最简单的代码zhuanlan.zhihu.com疯狂的程序员:基本理论-蒙特卡洛方法与定积分zhuanlan.zhihu.com疯狂的程序员:蒙特卡洛方法-概率密度函数zhuanlan.zhihu.com疯狂的程序员:蒙特卡洛方法-练习使用概率密度函数zhuanlan.zhihu.com疯狂的程序员:蒙特卡洛方法-累积分布函数zhuanlan.zhihu.com
我们核心关注和讨论的领域是引擎的底层技术以及商业化方面的信息,可能并不适合初级入门的同学。另外官方维护两个公众号,第一个公众号是关于我们企业自身产品的信息与动态的公众号,如果对我们自身信息与动态感兴趣的同学,可以关注图形之心。我们核心关注和讨论的领域是引擎的底层技术以及商业化方面的信息,可能并不适合初级入门的同学。另外官方维护两个公众号,第一个公众号是关于我们企业自身产品的信息与动态的公众号,如果对我们自身信息与动态感兴趣的同学,可以关注图形之心。
除此之外,我们为了更频繁的发布一些咨询与文章,我们维护的第二个公众号是“内核观察”,内核观察提供的主要是一些与我们无关的咨询与文章。
只言片语,无法描绘出整套图形学领域的方方面面,只有成体系的知识结构,才能够充分理解和掌握一门科学,这是艺术。我们已经为你准备好各式各样的内容了,东汉书院,等你来玩。
转载地址:http://leqsx.baihongyu.com/