学习笔记25

之前rendering 16看的第一部分,还差最后一点,是关于透明物体的,那一点说的很玄乎,根本没有看懂。

而且他的操作由于版本的差异,导致我这里根本没找到相关的操作按钮。

这里给我们自己的材质使用这个light map之前,还是得检查一下这个是否打开了,需要用到一个变体关键词。

逻辑就是:我们在外面可以设置是否开启烘焙等选项,然后开启的情况会对shader的关键词生成有影响,从而我们在#if判断的时候

会根据外部是否开启来决定是否进行相关的计算。

另外这里注意他说的VERTEXLIGHT和LIGHTMAP之间的互斥。

扫描二维码关注公众号,回复: 14915910 查看本文章

既然要采样lightmap。那么就必然有用到uv,这里是第二套UV。

通过第二套UV来进行采样,另外采样之前,还需要做一个转换:

这个就是类似我们之前的纹理坐标的变换,但是这里他说原因的时候,说什么texture unwrap有点不明白什么东西。。。

然后就是采样工作,这个采样结果是作为间接光照的。

这里采样结果还需要一个decode。

(之前我们有一个疑问,就是什么时候decode,什么时候encode,从这里来看,他是和图片存储有关系的,如果说一个图片要求存储某一些格式,我们就要encode了,

而有一些我们获取的纹理,他是encoded,所以我们需要对他进行一个decode。)

这里就是关于我们目前对lightmap的使用仍然存在问题,简单使用没啥问题。

他始终认为我们的额物体时solid white和opaque的,所以当不透明的时候,或者其他颜色的时候会出现一些问题。

这个当不透明度改成0的时候,就更加明显了。

这里关于为啥treat as opaque就是如上所说的了,主要是他在烘焙的时候,如果看到你是半透明,他会去参考_Color的alpha通道,

但是我们这里没有,所以就只好认为是opaque的了。

所以解决这个很简单,就是替换一下。

对于这个cutout的阴影,是完全类似的情况,也是需要我们进行一个改名字。

这里在烘焙的时候,注意烘焙的结果是间接光,我们考虑烘焙效果的时候,关注场景中的间接光。

所以这里关于颜色,你想间接光他要物体本身的颜色干啥,不就是那些color bleeding等等的效果么。

但是这里colorbleeding的永远是白色,也就是说这个 lightmap在处理我们所有的物体的映射出去的光的时候,都考虑

成了白色。

这个就不是改颜色那么简单的事情了,因为lightmap本身考虑的颜色是物体身上包括了albedo,自发光,金属度等等的情况,

这些情况都决定着最后color bleeding的结果是如何的。

所以说这里想要让lightmap能够给我们一个正确的效果,我们需要把这些相关的东西都告诉他。

之前的那些包括裁剪和透明啊,我们也是去告诉它的,不同的只是,之前是约定好叫什么名字,然后他会去取出相应的值。然后根据值来生成间接光的效果。

具体怎么把相关的东西告诉lightmap生成的时候。需要一个新的额pass。

准备一些需要用到的变量和函数等。

因为这些函数起作用时,在之前,我们都是写好了很多的条件,需要依赖于这些条件判断。也就是需要关键词才能生效。

接下来就是vertex了。

其实理解这里的核心就好了。

其实核心就是红框的那句,也就是说:首先我们这个pass还是场景中的物体走一遍。

但是我们输出的结果并不是想要输出摄像机视角下透视投影看到的效果。

而是我们想要搞一个texture unwrap in the lightmap。

这里就开始骚操作了。

首先,不管最开始世界空间中的物体如何如何,不管他们的位置又是如何。

这些其实和最终渲染的结果并没有直接的关系。

最终渲染的结果是取决于传递给fragment的位置和着色所需要的属性信息。

前者决定了输出的图的内容布局,后者决定了具体的内容细节。

所以说我们输入的是场景,我们完全可以用它渲染出这张texture unwrap in the lightmap。

具体如何操作呢?其实最重要的就是搞明白输出的位置是什么。

我们场景中每一个物体对应的点,在这张texture 中都会有一个对应。这个也就是我们对lightmap采样的前提。

那么其实我们如果想要这个lightmap,那就只需要把这每一个点搞出来,这每一个点又是和场景中的点对应的。

所以思路就来了,就是把场景中的点,变换到lightmap中的点,然后作为位置传递给fragment。

这样我们就实现了:shading间接光,然后存到lightmap。

当然这里需要一些细节,就是要把二维的这里要变成三维的坐标,二维是因为顶点对应到lightmap上的点都是2维坐标。

这里把z都写成0即可。

这里由于考虑平台相关的问题,所以搞了一个三目运算符。

对于fragment,这里需要输出两个,一个albedo 一个emission。pass被跑两次。关于每次怎么输出,Unity帮我们准备

了相关的判断接口:

从他的定义可以看出,他这里对albedo和emission进行了一个判断,除此之外,还进行了一些encode或者pow,clamp

这种简单的变换运算。

所以说,我们需要把我们计算好的几个量传递进来。所以接下来的任务就是计算那几个传递进来的参数。

这里就是简简单单的调用就行了。

当然这里还有一些自发光的问题,就是烘焙的时候,如果物体时自发光,他要去走自发光计算的pass,然后输出到lightmap上

才会有结果,

而这件事情,需要材质哪里处理一下,才能保证,自发光物体必然走一趟自发光计算pass。

这里在实现的时候出了一些问题,主要是关于坐标变换的问题。

我本来有点理解错误了,以为是把顶点的采样坐标直接作为裁剪空间坐标传递到fragment中的。

但是实际并非如此,作者写的是,把这个采样坐标作为object坐标,然后进行MVP变换。再传递到fragment中。

但是这么搞也是没有效果的,而且这里在调试的时候,我直接在fragment中写了一个return 红色,居然也是没有任何效果的。

这就说明,fragment整的跟没进来是的。原因待会说。

然后查找资料发现了下面的写法:

Unity5中的MetaPass - Esfog - 博客园 (cnblogs.com)

其中和我们不同的点就发现主要在坐标变换上。

这里直接使用了一个封装好的函数。参数是顶点坐标和两个uv,还有俩内置的东西。

找到他的实现代码

我们这里没有定义这个所谓的EDITOR_VISUALIZATION,然后他这里有两个分支,上边链接中有讲,

其实x是代表的静态GI,也就是我们的选择。y分量代表动态GI。

所以这里两个uv其实就表示一套是静态的,一套是动态的。

所以这里我们会进入第一个分支,然后里面的操作其实和我们是一样的,唯一不同的是,最后要乘以VP矩阵,而不是MVP

上边说返回fragment直接写一个红色都没有任何的反应,其实这里和我们多乘了一个M矩阵有关系。

本以为这个M矩阵输出调试一下应该是零矩阵,所以上边乘了之后啥都没了,所以都黑了,这也能解释MVP乘了也不行

但是实际调试了一下M矩阵,居然不是。

这块就先记着这么干吧,都太奇怪了。

回头学学renderdoc调试

这里对于粗糙的金属,其实这种自身颜色反射出去的效果更加明显,所以在standard中又对这一点做了一点补充,

对于我们之前实现的效果,这里就又提出了一个问题,就是关于烘焙灯光之后的表面细节问题。

主要是因为在烘焙的时候它只关心表面的顶点信息,然后就得到光照结果,所以我们法线对细节的表现就失去了。

所以我们想要重获细节,我们需要把法线信息给考虑到。

这差距不是一星半点。

这里的开启Directional的方法确实有一些改进,但是并不是很大。(上图中右边是改进后的)

主要原因就是这里的lightmap采样频率不够,如果说这个采样频率足够高的话,

(另外上边没有考虑直接光,烘焙的都是间接光,所以法线细节体现不是很明显也是正常的。)

开启这个directional之后,他会多生产一个图。

之前我们的lightmap只是记录场景中某个位置接收到的光的强度,然后把这个作为间接光的颜色亮度直接就用了。

而现在呢,多生成一个图,和上边的lightmap是匹配的,上边记录强度,这里再补充一个大致的方向信息。因为毕竟是

间接光,没有一个确切的方向。

然后有了光的大致方向,再对着色点进行采样环境光的时候,可以进行一个类似diffuse的shading计算,这样就把法线考虑在内了。

当然这里还是会因为光的方向是大致的,大致也就意味着模糊意味着filtering,意味着平均,所以效果稍有提升,但不是很强。

所以这里说,当有一个主宰的光的时候,结果就会好一些。

老样子,还是先考虑相关的编译指令。

这里就妙了,就是关于texture变量,他是包含了数据和采样状态的,后者可以包括一些filter模式等。

一般情况,一个纹理两者兼备。但是我们有时候可以为了节省来考虑只保留其一。

这里由于directional和intensity的采样方式是完全一样的,所以unity就在生成前者的时候没帮我们生成采样状态。

这时候我们就需要和intensity使用同一个采样状态了。那么这里使用到的函数就变换了。

因为没有自己的采样状态,就得明确指出使用的是谁的采样状态,所以这里换了个宏,多传递了一个参数。

得到之后,这里有一些解码操作,还有一些其他的unit-length的问题,而这一切Unity的函数都帮我们解决了,直接调用即可。

之前傻了,还没有使用就直接拿来比,当然没啥效果。

这里应用这个法线之后,其实还是可以看到很大的优化的,但是相对直射光还是有一些的区别。

效果越来越好,一个个问题逐个击破。现在又来问题了。

就是关于动态的物体,他是完全不受bake光影响的,所以说,当场景中出现bake光,同时又有这个动态的物体的时候,

这个动态物体就有点格格不入。上边给了一个极端情况,就是只有bake光的时候,动态物体直接是黑的。

这里为了给动态光加上环境光,这里就使用probe,光照探针。

之前计算环境光是拿当前物体周围的global environment data来算,现在换成使用探针算,而探针中就存储了周围环境的光信息。

可以把他理解成吸光的,他把周围的环境光给吸走,然后它再去照亮动态的物体。

这个探针就是使用球谐函数搞得。

这里有一些关于probe的特点。

比如这里说的每个probe会分区,然后他们之间会根据位置来插值球谐函数,还有他们会把动态物体treat as a point。

————————————————————————————————————————————————————————————————————————————————————————————————————

Rendering

关于之前的烘焙效果的间接光有很多的limitation。

关于full realtime和full baked都有自己的一些限制。

这里主要就说了间接光是bake光所拥有的,实时光不具有的。而她又能带来很好的效果,所以考虑一个combine。

这一点确实是可以实行的。

只修改上边的一个设置还不够,我们还需要对灯光本身进行一个设置。

把灯光的mode修改成mixed才行。

这里纠正一个之前的认知错误,之前认为bake的是所有的间接光,这可就大错特错了。

因为我们的场景唯一一个灯光的模式是bake的,很明显,这个平行光是烘焙的。

而之所以认为是bake的所有间接光,是因为在shader 中计算的时候,把采样lightmap的结果赋值给了indirect.diffuse

其实只看这一点就好像是只烘焙了间接光,但是完全没有考虑当前pass中直接光计算部分结果是0.

因为光被设置成了bake,所以关于直接光计算的时候他就不被考虑了。

所以就是烘焙了所有的光照。

但是当我们把光的模式修改成mixed就可以只bake间接光,而直接光部分就实时计算。

所以针对间接光部分,才有了上边lightmap变暗的效果。

这时候问题又来了,阴影的fade效果失效了,我们可以减小shadow distance来更明显的观察到这一点。

主要的原因就是对于这个阴影计算的一些东西,unity搞了一个更新。

之前我们阴影的操作是一个三部曲,先搞一个坐标变量,然后坐标变换,最后采样阴影图。

现在前俩宏都改变了。变成了上边的形式,

这里说了一个值得注意的:就是更新之后,只有平行光的屏幕空间阴影坐标,被放进了interpolator中,

还有就是,lightmap的坐标被用到一个shadowmask中。这里需要提供一个lightmap的坐标。

另外这里有一个bug,让我们必须初始化一下Interpolator才行。

现在我们观察这个阴影,还是没有fade效果。

主要是因为当前处于mixed模式,同时具有实时灯光和烘焙的lightmap效果。

没写,我们需要自己写,好在我们在延迟渲染的时候,已经写过了相关的东西,所以这里直接赋值过来略改一下即可。

最后这里只需要在上边那个宏定义的时候才需要我们手动做这个fade,所以加上一个判断。

这里由于bake indirect 的代价昂贵,所以又引入了一个新的mode,就是shadowmask,他也同时具备烘焙的间接光

和实时的直接光。

前者做的:对于所有的物体会按照实时走一遍,无论静态和动态。然后再对烘焙好的lightmap做一个采样,把间接光部分补充

上去。这部分只有间接光,且只对静态物体补充。

对于shadowmask模式的,他会再lightmap里面存储indirect光,和shadow attenuation。

其实说白了就是传统的lightmap,因为存储光就必然又shadow attenuation信息。不考虑直接光存在的lightmap,也是

有一些阴影的。这里说的就是这个阴影。其实就是单纯的一个lightmap图,记录间接光的值。

然后就是shadowmask,是一张图,这个就类似我们之前的屏幕空间shadowmap,(一个非0即1的图)

不考虑间接光的时候,正常计算光照,然后再采样shadow图做一个衰减,这里的lightmap就相当于前者正常计算的光照。

后者是衰减。

上边想法有很多错的,都是放屁。下面总结梳理一下:

什么是烘焙间接光?什么是烘焙阴影?物体怎么接受烘焙阴影?

一个只考虑了间接光的场景,想象一个球和一个地板。如果只是考虑间接光,其实他俩基本上颜色差不太多,球下面可能稍微暗一点点。

这时候把整个场景记录在map中,就是烘焙的间接光。

而烘焙阴影这个东西,是需要考虑直接光的。其实说到阴影,游戏考虑的都是直接阴影。也就是直接光产生的阴影。

而烘焙阴影考虑一种带有bake光的场景。这时候他会把物体照亮,由于球挡住了地板接受光,所以地板产生了阴影,

这个就是我们说的烘焙阴影。这个有个重点是需要是bake光,只有bake光才会在生成lightmap的时候考虑。

这里刚开始的时候,有一个疑问,就是那种烘焙的阴影,不都已经在生成lightmap的时候生成好了么。直接采样

就能得到阴影的结果,为何还需要一个类似shadowmask作为遮罩,非01的记录阴影区域。

这个误区关键点在于没有考虑到动态物体接受烘焙阴影。

首先静态物体接受烘焙阴影,确实可以不用关心什么非01的阴影区域,因为都烘焙好了的。这就是那种灯光是bake的场景

渲染的方式。静态物体所有的颜色都是来自于采样。

但是一旦考虑到动态物体就没法搞了。烘焙贴图里面根本就没有它相关的信息。没法采样。

而如果又想要让他接收到烘焙的阴影。那么只能使用之前类似的阴影处理方式,那就是准备一个shadowmask

表示那些区域内是烘焙阴影区域。这个由于是哪些为烘焙阴影区域,所以这个图完全是可以预先生成的。

预先生成之后,渲染时,对其进行采样即可。为了统一,静态物体考虑接收到的烘焙阴影时,也采用shadowmask计算。

而不是去lightmap采样。

Lighting Mode:Baked Indirect - Unity 手册

现在回到文章中来,在shadowmask模式下,

这句,间接光和阴影衰减被存储到lightmaps里面。

首先间接光这个无疑,这里说的衰减阴影是什么,其实就是烘焙阴影,就是bake灯产生的阴影烘焙在了lightmaps里面。

然后shadowmask模式下,会为了烘焙阴影的形成来多搞一个贴图,就是shadowmask。

然后下面这个,他说的有些不严谨,确切来说这个shadowmask和动态物体没有关系,所以说被照亮的所有动态物体都会出现在shadowmask中。

这个效果上,无论是静态还是动态都少了烘焙阴影,首先静态物体本不该少,因为他的烘焙阴影信息可以从lightmap中获取

但并没有这么做,就是我们之前说过为了保证统一,所以和动态物体一样,通过shadowmask采样来完成阴影效果。

而采样shadowmask的工作我们至此并没有做,所以完全没有这个烘焙阴影的效果。

这里其实应该没有在lightmap里面保存烘焙阴影,其一是因为根本不需要采样这个烘焙阴影,其二如果保存了那么会对

间接光的采样造成影响。

所以接下来我们就需要采样来补充上这个烘焙阴影效果。

首先这个先采样获得bakedAtten,其实这个就是非0即1的,类似屏幕空间shadowmap,这种值作为烘焙阴影的衰减。

然后结合attenuation,这个是实时阴影的衰减。还有一个是关于边缘阴影fade的变量,这三个进行一个综合考虑得到

最终的衰减值。

注意上边的文档截图,单纯的shadowmask,静态物体是没有实时阴影的,只有烘焙阴影,注意和之前的那个bake indirect区分

而distance shadowmap就会有了。

对于动态物体会投射实时的阴影,所以这里就:

这里说的是静态物体同时受到动态物体的实时阴影和烘焙阴影。

而这里又引出来了一个问题,就是烘焙阴影的边界问题。

补充:动态物体根本没有LightMap_On这个宏,所以他们不会对lightmap进行采样。

在延迟渲染中考虑shadowmask时,其实只需要多存一张shadowmask图就好了,其他的和之前阴影完全类似。

这里后面的判断主要是看平台行不行,因为输出的gbuffer是使用渲染目标存储的,而渲染目标的个数是有限的。

这里使用的时候,这里和一个selector进行了dot,这里主要和前面说过一个mask图全是红色,因为把数据存储到了R通道

相关,这里也就是取出来shadowmask下某一灯光的shadowmask。然后作为attenuation。

然后这个关于这个fade这里,首先球的shadowFade是根据当前pass的光源类型求的,按照老的算法,

我们是对shadowAtten进行一个修改,这个是当前灯光的直接阴影衰减。

其实在这里就是把间接阴影衰减拉进来了,然后换了一个函数来做最终的计算。

猜你喜欢

转载自blog.csdn.net/yinianbaifaI/article/details/127702725
今日推荐