【技术美术图形部分】PBR全局光照:理论知识补充

写在前面

最近做东西的流程是这样的,想实现一个风格化森林小场景,场景体现的主体是风格化树和交互草 --> 于是用了两天时间学SpeedTree做树模型 --> 用了两天时间SD做了树干贴图 --> SD做的贴图是根据PBR Metal-Roughness(金属度/粗糙度)工作流整理的:

突然发现PBR这块儿还有空缺:学了TA快一年了,居然没尝试写一个PBR!这怎么可以!!!

于是,上个周末就开启造轮子之旅,想先在Build-in管线下写一个复刻Standard Shader的PBR Shader,接着把它改到URP管线下,也算是为之后实现PBR卡通渲染做铺垫吧!

emmmm,写完理论片后发现篇幅过长。。。决定把理论和实现篇分开。


1 全局光照

(叙述主要参考3D 实时全局光照技术(一)

全局光照,即Global Illumination(GI),是既考虑直接光照(场景中光源直接照射),又考虑物体间反射光的间接光照的渲染技术:

Global Illumination = Direct Illumination + Indrect Illumination

2 渲染方程

(本小节叙述主要参考自《Real-Time Rendering 3rd》提炼总结

1986年,Kaijiya将渲染方程首次引入图形学,所有实现全局光照方法都是以渲染方程作为理论基础,对其进行简化求解,在实现渲染的同时达到优化性能的目的。方程如下图所示(图截自参考文章),描述了从x点某一方向看的光放射的总额

至于方程的具体推导,不是本篇文章的重点涉及内容,感兴趣学习的话推荐Games101,闫神将渲染方程这个知识点放在了光线追踪模块进行讲述:

话又说回来了:我们做的所有的着色工作,无论是计算直接光还是间接光,都是在解上面这个渲染方程。自发光非常好计算,很多文章展示出的渲染方程也会剔除掉前一自发光项重点展示后面的积分项。后边的部分就是在半球上做积分,需要知道半球上所有入射光线的方向和值。因此,解渲染方程的重点就在于扒清楚入射光线这个未知的问题。而对于某个着色点而言,入射光线有两个来源,

  • 直接光源
  • 间接光源

来自于它俩的入射光线又需要分别采取不同的计算方法,因此渲染方程的计算又被划分成了计算直接光照和计算间接光照两个部分。 

3 计算直接光照

直接光照就是场景中点光源、平行光源、面光源等这种理想光源(直接光源)发出光线到物体表面产生的光照,例如游戏场景中的蜡烛(当然小光源的话为了节省性能会预先烘焙出来)、灯管、太阳等。

啊,在计算直接光照时,光源的方向、位置、强度等信息都知道了,入射光线也很容易获得,因此其实就是一个分别计算再求和的过程,计算过程参考:【技术美术图形部分】PBR直接光部分:Disney原则的BRDF和次表面散射模型

如果仅仅只有直接光照,BRDF的效果跟传统光照模型区别并不大,因此光照计算的重点在于间接光照的计算。

4 全局光照中的间接光照

间接光照来源于非光源的光照明,例如“红色房子里的球也会被印上红色”,逐渐意识到,计算间接光照实际上就是在计算环境光照。和直接光照类似,硬要分类的话,间接光照也会被分为

  • 处理环境光照:IBL、AO
  • 间接光(环境光)漫反射:Lightmap、SH等
  • 间接光(环境光)镜面反射:SSR? EPIC的处理方案

 对于间接光照,我们也是基于BRDF方程计算的:

在讲具体计算前,我们还需要了解下面的几个部分。

4.1 光线弹射次数

在讨论间接光时总会出现一次弹射和多次弹射的说法,

一次弹射 One-bounce

间接光射出的光线打到物体后直接进入人眼。整体路径会是:

物体A(发出间接光)--> 物体B(接收间接光并反射)--> 人眼

多次弹射 Multi-bounce

即打到物体后不是直接到人眼,而是再在场景中多次反射:

物体A(发出间接光)--> 物体xN(N次反射)--> 人眼

弹射N次意味着计算量指数级增长,,计算量爆炸!!!

4.2 光线追踪与光栅化

光栅化渲染管线 Raster Pipeline

是一种object-order redering,即从图元(primitive)出发,寻找被图元影响的像素的渲染方法,按RTR4可将整个管线流程划分成应用阶段(应CPU),几何阶段、光栅化阶段和像素处理阶段(GPU),至于每个阶段功能就不细说了。

光栅化,光线只弹射一次,即光线的路径是:光源 --> 物体 --> 照相机

光线追踪渲染管线 Ray Tracing Pipeline

是一种image-order rendering,即发出一根光线,光线与物体表面求交于某一像素,再计算当前像素影响的所有对象的渲染方法。

实时光追,考虑光线弹射多次,光源 --> 物体1 --> 物体2... --> 照相机

4.3 实时光线追踪 RTRT

首先声明一点,我并不保证自己叙述的完全正确,仅仅代表我个人的理解。

就从UE4推出的光追技术出发吧:UE4种光追包含两种技术,

  • 混合的Ray Tracer:光追功能与光栅化效果相结合,混合渲染
  • Path Tracer:渲染场景的参考图像

前者是针对阴影、AO、反射、半透明和全局光照生成带有光线追踪的实时效果,其实就是在光栅化之后用光追来绘制反射、阴影等,我感觉相当于打了一个个“补丁”,很多很多补丁叠加在一起,慢慢地接近全局光照的效果。例如ShadowMap,就是通过从光源角度生成贴图的方式保存物体的遮挡信息,以绘制阴影。

更详细的了解参见2020年浅墨大佬写的文章:实时光线追踪技术:业界发展近况与未来挑战

4.4 离线渲染(静态光追?)

图形学中大于30FPS(1帧30ms)的渲染被称为是实现渲染,10FPS以下(1帧渲个一整天的那种)就是离线渲染了。

工业界的离线渲染,看到的另一种说法叫做静态光追,是用于影视、动画、广告等的渲染技术。离线渲染注重视觉效果,追求高画面质量,以准确的模拟出光的物理现象为主,所以渲染时会增强算法鲁棒性,在求解渲染方程的过程中尽可能的寻找高贡献路径。 

路径追踪:蒙特卡洛积分

由于根本不需要考虑渲染时长问题,离线渲染计算间接光照方法简单粗暴——蒙特卡洛积分,其实蒙特卡洛积分+光线追踪=路径追踪。关于蒙特卡洛积分具体如何参与解渲染方程的,可以看看之前我写的101的路径追踪部分的文章:GAMES101作业7-路径追踪实现过程&代码框架超全解读

总而言之,暴力计算意味着存储量大、计算复杂度高,实时全局光照肯定是无法实现的。(对离线渲染用到的技术没有太多的了解,只知道它的特点,至于是采用了什么技术导致的我也不是很清楚,只能阐述至此了~)

5 环境光技术概述

除了了解上述的几个概念,还需要知道环境光照涉及到的一些概念。

5.1 区分2种环境光概念*

一路学习下来,我发现描述环境光有两种方式,

  • Ambient Light
  • Environment Light

他俩有啥区别?这里略微有些咬文嚼字了,,但我觉得对我来说就是个模糊点,先看看下面这个截图,讲得蛮好:

po个我的理解,不一定对:一个场景中的所有物体都可以看作环境光(因为光源到处弹射嘛),这就是Environment Light了,计算的话一般就是IBL(简单的传统光照模型因为是局部光照,甚至是直接给一个常数颜色),有的文章还会把IBL归为计算直接光照的范畴。

而硬要区分的话,Ambient Light可以跟Ambient Occlusion一起看,意味着Ambient Light环境光确实是个间接光范畴,一些物体自己身上突起造成的自遮挡阴影、两个挨得很近的物体之间的阴影都算作间接光的环境照明带来的效果。

感觉真正在学习时不必拘泥于这些概念性的东西,这里就当作碎碎念一笔带过吧~

5.2 Phong模型的环境光项

讲IBL前,先看看传统光照模型中对环境光的考虑。

还记得学习传统光照模型的时候,Phong=环境光+漫反射+高光,这里的环境光就是Environment Light,计算时,

L_{a}=k_{a}\cdot I_{a}

这里假设了环境光是一个常数颜色,场景中各个方向的入射光线强度和颜色都趋于相等,仅仅是为了保证当光线没照到的时候物体也有一个“保底色”,这个保底色就是环境光项。

5.3 基于图像照明 IBL

实际项目中一定不会只给个常数项颜色的,一定会有Skybox,Skybox发出的光就成了环境光了。IBL做法就是拿一张Cubemap环境贴图作为光照的来源,来模拟间接光的漫反射和镜面反射光,想起来之前写的Cubemap应用,就串起来啦,

【Unity Shader】Unity中如何创建Cubemap?

【Unity Shader】用Cubemap实现天空盒和环境映射

浅浅复习一下:IBL思路就是提前计算每个法线方向对应的光照并储存在一张CubeMap中,需要用的直接采样就行,也就是说,Cubemap实际上是假的,是针对具体位置bake出来的。对于应用了Skybox的场景中的任何物体:

  • 位置信息:忽略!始终认为点就在Skybox中心
  • 法线信息:考虑!

IBL的不足

  • 无法处理自遮挡产生的阴影效果:没有位置信息!
  • 需要额外储存CubeMap,用的时候还要纹理采样;如果是个超大场景,必须针对点储存对应的Cubemap,这储存量。。。

于是,我们又开始了打补丁之旅——AO技术、球谐光照。 

5.4 环境光遮蔽 AO

对于阴影,直接光照有对应的补丁ShadowMap,间接光产生的阴影对应的补丁就是AO了!

静物 AO Map

正常项目中,为了节省贴图采样次数,一张AO Map一般会放在其他贴图的某一通道,例如Unity Standard shader中AO被认为保存在金属度贴图orBaseColor贴图的a通道中。

关于AO:

【技术美术美术部分】AO贴图的烘焙及应用

 【技术美术图形部分】AO理论及优化 AO贴图如何参与渲染

预计算一张AO Map适用于静物,计算完直接光漫反射+直接光镜面反射+间接光漫反射+间接光镜面反射,四部分后,叠加AO值就行(让颜色变暗)!

动物 实时AO 

对于动物,无法预处理了,只能实时计算AO——SSAO,天哪,实时AO的坑到现在我都没填上,我有罪。。。

6 球谐函数

由于球谐光照涉及到的球谐函数是之前从未总结过的知识,所以这里又单独拿出一小节来讲它。

IBL方法将球面积分转化成了对Cubemap进行纹理采样,想要继续改进就要用到球谐函数了。球谐光照的思路是用一组球谐基函数近似表示光源函数,把复杂的积分转化为加法(事实上蒙特卡洛积分也是SH的前提),以简化计算。

6.1 概念

浅叙述一下基函数:一个基函数是一个函数空间(Function Space)种特定基底的元素,函数空间中每个连续函数都能表示为基函数的线性组合。就好像Y=AX+B中{A,B}可以作为所有一次函数的系数、欧拉空间中(0,0,1)(0,1,0)(1,0,0)可以是所有向量的基底、傅里叶变换就是sinnx和cosnx作为基函数,拿着这两个基函数足以表达所有的周期信号...

而球谐函数恰好是一个三维球面坐标系下的一组球面基函数。

关于球谐函数的学习,我参考了这篇文章:球谐函数介绍(Spherical Harmonics),以下截图均截图自这篇文章,为了之后自己复习的时候方便查看,就直接截图啦。

文章里介绍球谐函数非常通透!先从二维出发:

这组5项组成的小基类,就足以表示任何二维平面的函数,例如:

这太酷了!

意味着只要规定一组可靠的基函数,用一个个系数就足以表达一个二维平面的函数。

球谐基函数

再到三维情况下,球谐函数也有三维空间下的基函数:

3D样子长这样: 

WiKi上也有列出每一阶的基函数:Table of spherical harmonics - Wikipedia

OK,现在我想要描述三维空间中的某一个形状,如果手里没有系数,他只能是初始形态——球,随着系数越多,就越接近最初想要表达的形状:

6.2 特点

呃,暂且把球谐函数当作一个球里的三角函数吧(更容易理解),想真正用它来简化光照计算,知道它的两个特点就OK:

正交完备

球谐函数两两正交,自己和自己积分是1,和其他的是0,总之一句话:只要两个球谐函数长得不一样,相乘结果就是0

旋转不变

基函数不仅正交,且归一化,意味着具有旋转不变性。

7 间接光照漫反射

终于进入正题了,下面结合具体做法讲讲怎么计算间接光照漫反射。

解方程积分绝对不现实,我了解到的有两种计算方法。

7.1 Lightmap

当光源和场景都不变时,拿到静态场景+静态光源,先离线计算每个点的Irradiance,保存到一张图也就是lightmap,用的时候采样就行!

同时它也有缺点,,

7.2 球谐光照

正常思路做法是基于传入的Cubemap预处理成一张贴图(模糊处理?感觉利用Mipmap就行欸),再将这个帖图投影到球谐光照的基函数上储存。前面提到了我们需要用球谐函数给IBL打补丁,那么球谐函数到底怎么参与到光照计算中?怎么简化光照方程?以下大部分公式均截图自【论文复现】Spherical Harmonic Lighting:The Gritty Details,为了方便自己以后复习的时候查看选择了直接截图,侵删。

简化漫反射积分项

把漫反射积分项,

分成两部分:

进行球谐展开,

一般而言求解间接光漫反射只需要3阶球谐基函数就行,我们指定上述的求和上限为i=9,

带入到最初的漫反射公式,把球谐系数Li和ti提出来,

由于Lj和ti都是基于同一组球谐基函数的两组不同的系数,因此提出来之后剩下的项其实就是球谐基函数,又因为球谐函数的正交完备性,直接约掉积分项里面i≠j的项,而且剩下的i=j时积分项相乘为1,那么积分项直接“消失了(变为1)”,于是光照方程成了,

计算系数

进一步分析球谐系数,这里就要开始计算了,

我们的Li与着色点p位置是无关的,直接对天空盒积分就行。

对于ti,

我们需要获取法线方向,如果不利用球谐函数的旋转性,需要每个n方向都预计算一组ti,那么利用旋转性:【论文复现】An Efficient Representation for Irradiance Environment Maps,这个过程相对比较复杂,直接看这篇参考文章即可,这里只粘出最后的简化光照公式结果,

现在,我们只需要,

  • 计算L项(预计算)
  • 计算t项(常数项与球谐基函数相乘)
  • 计算Y项(已知着色点法线n计算)

8 间接光照镜面反射

8.1 后项公式:预计算LUT

按金属度把公式后面一项划分出来了,

需要预计算出nA和fA的值,过程省略,总之最后直接化简成了只需要预计算出一张二维纹理,

根据cosθ(n和v的点积)和粗糙度r,就能预计算出nA和fA的值

加速计算

可以通过蒙特卡洛积分加速计算,

重要性采样也可以加速计算,

最后预计算的结果往往会被保存在如下这样一张二维纹理的RG通道中,

用的时候,根据n·v和粗糙度r采样这张图就行,这种图被称为LUT(Look up texture),就类似于信息对应表的感觉。

上述仅仅是我对过程的精要概括,详细内容还是参考:

【知识补充】基于图像的光照 IBL - 知乎 (zhihu.com)

8.2 前项公式:根据粗糙度采样Cubemap

后项搞定了——采样传入的一张预计算贴图即可。前一项公式:

由于镜面反射跟视角关系很大,不能像漫反射那样直接采样Cubemap,怎么做呢?

反射向量的重要性采样很大程度跟粗糙度有关,需要根据Cubemap生成它的Mipmap作为一张预滤波环境纹理(Pre-Filtered Environment Map),以这种方式保存粗糙度和入射光强度的对应关系。

啊,这样的话,直接根据粗糙度找到对应mip层的Cubemap,采样就完事!

Unity的方案

其实上面的方法跟UE的split sum计算的思路是一样的,都是拆分成近似的两个方程。Unity的话,在计算后一项时并没有预计算LUT,而是用了某种方法去拟合LUT的数据。篇幅有限,我会在后面一篇的具体实现过程中再体现。

8.3 低频信号和高频信号

这条其实算是插播,因为8小节涉及到的球谐函数处理镜面反射其实不算是个好的办法,物体漫反射算是低频信号,因为不需要统计方向,因此储存信息的代价很小,我们可以选择lightmap处理或者用球谐基函数去拟合光照方程。

但镜面反射是一个高频信号,入射方向取决于观察方向,因此如果继续用球谐函数求解镜面反射代价会很大(需要非常高阶的基函数),因此我感觉项目中用的更多的可能是SSR、反射探针之类的技术去实现间接光镜面反射?不确定。


我的天,属于这两天挤时间硬核补课了,接下来两天将完善一下相关的代码,再整理一篇Unity中实现PBR的过程博客。

关于PBR理论,今天就到这。

参考

如何在Unity中造一个PBR Shader轮子 - 知乎 (zhihu.com)

PBR中直接光照的实现 - 知乎 (zhihu.com)

重新理解PBR(2)——漫反射全局光照 - 知乎 (zhihu.com)

【论文复现】Spherical Harmonic Lighting:The Gritty Details - 知乎 (zhihu.com)

从零开始在Unity中写一个PBR着色器 - 简书 (jianshu.com)

猜你喜欢

转载自blog.csdn.net/qq_41835314/article/details/129639283