实际引擎中的全局光照,并不是真正实时的,分为两种,第二种是比较常用的,就是lightmap,烘焙好间接光照,然后进行一个采样。
预计算实时GI,他这个也是通过预计算然后再使用算好的数据,这个预计算结果不一定是保存在纹理中。
这两个东西,是可以同时使用的,也可以单独用一个。
烘焙系统视觉效果上作用区别不大。
就是他们都是烘焙,结果基本上是一样的,只是烘焙的过程,采用的算法等等的可能不同,就比如渐进式烘焙,这个他是先烘焙你摄像机看到的部分,然后在其他,enlighten就有点不一样,但是对于我们来说
区别真的不大。
这里第一个就是只烘焙间接光,第二个就是阴影的烘焙相关的。
灯光mode为烘焙,那么这个灯只对静态物体起作用,如果是mixed或者是realtime的,那就对静态和动态都起作用。
首先上边三种模式看第一个单词mixed,也就是这仨都是针对mixed mode的光的。为啥呢?
因为bake的光,他是直接和间接,阴影都进行烘焙,没得商量,所以这里的选项根本不会对bake光源有任何的控制。
然后实时光照,根本不鸟这个bake GI。如果实时光照匹配这个BakeGI的话,根本没有间接光。
然后这三个模式既然是针对mixed的,mixed是一个实时和烘焙的混合。
对于baked indirect和shadowmask这俩,都是直接光是实时的,间接光是烘焙的。
对于subtractive,静态物体,两个光都是烘焙的,非静态物体,直接光是实时的,间接光是烘焙的。
关于他们的阴影,baked indirect和 subtractive没啥特殊的,实时光就实时阴影,间接光就烘焙阴影。而shadowmask就不一样了,它是有俩的,另一个是distance shadowmask
第三个就是间接光和直接光都进行一个烘焙。
打开unity,调整光方向,让他从右边口射入。
关于场景里面的三个球,他们都是自发光的。
打开烘焙界面。
然后这里有几个设置,上边标记的那俩就是预计算实时GI和烘焙GI。
烘焙之前,我们需要勾选这里的static。
打开实时GI关闭烘焙GI的效果。
这个是关闭了实时GI的效果。
这个效果上和刚才是类似的。直接光照是可以实时修改的。
直接光和间接光都进行一个烘焙,需要把光照的模式修改再进行烘焙。
烘焙完成之后,再修改直接光的方向等就不会有任何反应了。
这时候直接看上去和subtractive没啥区别。
如果我们这个把灯关掉,直接光是会消失的。这个是shadowmask的作用。
意思是:这个他会把直接光和间接光烘焙到场景中,但是不同于subtractive的是,他会把直接光和间接光分开进行烘焙,
这里可以看到。
如果修改灯光强度,那么这里的光照效果会变暗,变亮,阴影的亮暗会受影响,但是如果我们修改光的方向,阴影并不会发生移动。就是因为他是烘焙死的,位置不会变的。
如果我们切换成distance shadow mask。
他会把直接光变成一个实时光,阴影是实时的。
当我们把距离拉远(上边project setting里面有一个shadow distance控制距离分界),会变成烘焙的阴影。
这里有相关的设置。
============================================================================================================
首先创建一个shader。.
搭建一个场景,这里本身柱子和平面都是unity的standard材质,所以应该有阴影的,这里看不到是因为距离远,因为刚刚的设置:
修改一下:
三个对象都勾选成static。
这里烘焙之后,由于我们使用的shader就是一个unlit的shader,他里面根本没有什么投射阴影,接受阴影等代码,所以就是上边这样的效果。
要想让他融入这个全局光照,我们需要给他添加很多东西。
这一点如何实现,我们可以参考unity自带的shader里面怎么写的。
这个打开的话,是表面着色器,我们拽到unity里面获取一下像素片元着色器的代码,然后再看。
这个着色器是实现了GI的,所以我们就参考它的代码,
找到像素着色器,会有这么一个函数调用,这个函数其实就是实现GI的核心函数。
找到实现。
这里就是嵌套调用了一下。
由于我们这里要自己写GI,那么这些函数我们待会也要自己写,所以把他们复制到一个头文件中,待会自己写,自己调用就好了。
创建头文件的方式,就是随便创建一个文件,shader就可以,然后打开in explorer,然后修改后缀即可。
参考人家的
写好之后,include到自己的shader中,注意放的位置和include的写法。
这里如果文件移动了。shader啥的没有刷新,可以右键reimport一下,刷新一下。
=======================================================================================
GI了解补充
基于 Unity 引擎的游戏开发进阶之 全局光照 - 知乎 (zhihu.com)
光照探针,那个是针对非静态物体的。他会把一个物体的发光给吸收到小球里面,然后小球来照亮周围的物体。注意这里的发光物体必须是静态的。
它可以有很多的小球,这个小球根据和物体的距离不同,小球里面存储的光照是不同的。
然后该物体周围的物体会吸收三个小球的光。选择最近的三个小球进行吸收。
为了得到更好的效果,我们这里可以对探针以及物体做一些处理。
对于探针,球的数量要多一些,然后根据距离进行一个分布。由近到远。因为极端情况就三个小球,周围的所有的物体吸光都是吸相同的三个小球,也就是吸到的亮度是一样的,这样效果就不合理了
远的近的应该被不同亮度照亮的。所以要多一些球,这样距离远的物体就吸距离远的球,距离远的球他的亮度会低一些,这样符合正常的效果。
对于物体,要尽可能的小一些,极端情况,一个巨大的大地板,然后它吸哪三个小球,是由他的中心点决定的,中心点距离哪三个球最近,那么整个板子就被相同的光照亮,这也是不合理的,因为地板很远的一部分
和距离发光物体近的一部分,应该具有不同的亮度。所以把物体弄的小一些,无论哪一部分距离发光物体的距离都是差不多的,然后被照亮的强度相同,没有太大的问题。
实时GI,这个实时指的并不是完全的实时,主要是指的可以实时改变灯光的亮度,颜色。然后效果上也会由实时的改动。
他针对的是静态物体。其实他这个提前计算的是:全局光照的那些光线路径,而具体的照明结果是实时计算的。
Bake GI,针对静态物体,就是直接把所有的光照结果直接烘焙到贴图上,也就是烘焙之后,这些光都是板上钉钉的,不会随着光的属性修改而实时改变的。
相关操作:
创建探针。然后直接烘焙就行了。
如果光源为baked,那么lighting里面是否开启real time GI都无影响。因为一切照亮都是从烘焙到贴图中得到的。同时非静态物体不会被照亮。
如果光源为mixed,会照亮非静态物体。
lighting中选择baked GI,直接光照是实时改变的,包括位置和颜色等,间接光照不能由任何的改动。
lighting中选择 real time GI,直接光照是可以实时改变的,没有间接光照。其实lighting中选择了实时GI,再光源处就只有realtime一个选项。
如果光源为real time,会照亮非静态物体,
lighting中选择baked GI,直接光照没问题,没有间接光照。
lighting中选择realtimeGI,直接光照没问题,间接光照颜色亮度可以实时修改。
所以有用的搭配:
Baked 搭配 baked GI。
Mixed 搭配 baked GI。
Realtime 搭配 realtime GI。
也就是这个老哥说的三种情况。
基于 Unity 引擎的游戏开发进阶之 全局光照 - 知乎 (zhihu.com)
对于光源模式的三种:realtime,baked,mixed。
这里说的就更清楚了,mixed其实就是把间接光,利用烘焙来生成,然后直接光,就和普通情况一样,完全实时的。
而实时光,就是每一帧计算一次,实时光不会进行具体颜色的烘焙,会进行路径的烘焙,所以选择实时光的时候,弄烘焙GI是没有间接光效果的,就类似它没有烘焙你却想要显示它的烘焙结果,当然显示不出来了。
而选择实时GI的时候,就是每一帧计算一次,根据已经烘焙好的路径进行实时计算,实时计算颜色强度等。
写给美术看的Unity全局光照技术(理论篇) - 知乎 (zhihu.com)
Unity学习——预计算实时GI(全局光照) - 知乎 (zhihu.com)
关于这个勾选static的,其实只需要勾选lightmap static就够了,外面static的勾选相当于一个总开关,它勾选了,里面这些都勾选上。
===========================================================================================
弄好了cginc之后,我们就要把一些代码拷贝到我们的cginc中。
弄完之后改名字。改成我们自己的,不会重定义冲突。
然后一一来看:
这个inout其实就是相当于传引用。会对gi参数进行修改。
第一个是直接光照,那么里面只需要光的方向和颜色就行了,第三个舍弃了。不做存储了。
然后间接光照,
玩这个UnityGI,我们需要加上头文件,注意光照相关的就加上边这俩头文件就行了,他俩还会链接到其他的头文件,所以基本上光照相关的都会包含进来。
写上我们自己的gi。
还有一个参数,这个主要就是物体的表面属性了。
然后还有一个GIInput
UnityLight就是刚刚的直接光照结构体。 atten是衰减。
lightMapUV,GI中的间接光照,无论是bake GI还是realtime GI都是会采样纹理贴图的。这个变量就是采样贴图的纹理坐标。
看后面注释,xy分类是bake GI的,zw是realtime GI的。
剩下的都是和探针相关的,先不涉及。
到现在为止,三波输入都搞定了。
====================================================================================================================
对于上边还缺了一个lightmapUV的变量写入,这个是一个重头戏。
他这个变量只有在bake GI或者realtime GI的情况下才会使用。
所以这里我们要进行一个判断。根据什么判断?也就是根据是否开启bake GI或者realtime GI
上边的意思就是,我们先#pragma multi_...之后,他就会弄出来下面那些宏供我们判断。
可以搞这么一个小测试。
对这俩进行开关的时候,输出的颜色就黑白变化了。
判断条件说了,那么这个lightmapUV到底从哪搞来,其实就是一个模型的二套UV。第一套UV都是模型表面那些采样。第二套UV是专门和烘焙相关的。所以从哪来?就是从appdata中获取。
appdata中的语义写成TEXCOORD1即可。这就会传入第二套UV。
第二套UV,DCC软件可以生成,DCC软件不生成,在模型导入引擎的时候,一般也会默认勾选帮我们生成第二套UV。
注意了,这里我们还有一种方式,就是appdata得到了uv之后,直接传到v2f,然后到了像素里面再对lightmapUV进行赋值,这样中途v2f可把float4改成float2。
但是这样就大错特错了,因为顶点shader里面执行这个计算要比像素shader里面执行这个计算高效!
在模型的面板中,我们是可以看到这个相关的信息的。
到这里基本上把参数准备就完成了。
==============================================================================================================================
首先解决一个报错,之前giInput.light写错了,他需要的是一个直接光的结构体,我们把之前写过的gi给放在上边,然后这里直接把gi的light传进去就行了。
有时候他会要求这种初始化,因为下面他作为输出参数使用,所以初始化一下。
当我们计算完成gi之后,继续观察unity里面shader GI咋写的,他又干了啥:
用了一下上边的这个函数。依旧是复制自己头文件里面。
里面调用了UnityLambertLight:
这个就是最基本的兰伯特模型。
在lightingLambert还有一个判断宏,定义如下:
现在效果是一个黑色,因为我们没有对o里面的法线进行初始化,导致Lambert计算的时候是0.
把灯光和模式都设置一下,就可以看到效果,直接光已经ok了,但是间接光还是有些问题的。
=================================================================================================================
效果不对,我们就利用中间的结果进行一点点的输出来看问题在哪:
直接把间接光照给输出出来。
这里的结果是正确的,正确的烘焙效果,带有阴影。
注意这里的什么是正确的,其实就想象间接照明他的效果,这里虽然是间接,他也不会是均匀的,因为直接光照射其他物体是有方向偏重的,所以其他物体反射到球身上也是有方向偏重的。
另外这里直接光是没有阴影的,因为直接光的部分,就是一个纯Lambert,我们没有做投射阴影的东西。
那只能是这个lightLambert的问题了。
这里有问题,这个变量根本没有初始化。这里初始化为1即可。
最终效果。
直接光加上间接光。
目前还未对间接光计算的黑盒做一个剖析,所以下面:
其实核心就是这个函数了。
这里套了两层,其实可以简化成这一句。
置空。
其实这里除了几个if之外,这里的data.light是我们场景中的主平行光。就是把它的颜色考虑一个衰减,同时把间接光考虑一下AO。
第一个if,里面判断的宏是:
shadows_screen是硬件上支持与否的一个判断。后面是静态烘焙是否打开。
其实这个是distance shadermask情况下才有的,这里我们的球的shader并不能投射实时阴影,因为投射阴影这个要在shader里面写出来。我们并没有写。
所以没法使用这个效果,它过渡也看不出来。
这个也暂时跳过。
这里首先进行一个采样贴图,采样的是烘焙的光照贴图,采样的map是看不到实现的,unity没有开放出来。
然后烘焙贴图的采样结果都是一个RGBM 的格式,是一个编码之后的,我们需要解码。
编码的目的是存储一些超过1的数值。
我们甚至可以直接把这一部分拿出来,这样就不必走gi那些,简化了shader。
但多数还是用Unity内部的那个,并且在里面根据需要做一些修改。
这一坨和directional mode有关系的,表示定向光烘焙模式,开启后表示生成的烘焙贴图会多生成一组烘焙贴图。
shadermask会生成三组,subtractive是两组,其中的一组是定向光,作用就是在shader表面呈现光照方向的效果,比如法线贴图。
无论开启与否,下面就是把刚刚采样到的颜色给到间接光的diffuse。
这个realtime GI类似的,也是采样解码。
==============================================================================================
上节,再说distance shadowmask的时候有一个阴影混合过渡,我们当时没有写阴影,所以没有测试,这里补充上。
直接从之前的shader中复制即可。
然后就有投影了,还差个接受阴影。
这里就加上attenuation那一句,这个atten有两种含义,一种是光照衰减,一种是实时阴影的值。
这里我们要做过渡效果,其实就是对这个值进行一个处理,那么就是传递到giInput,然后到
这里会对giInput传递过来的atten进行一个修改。实现过渡效果。
然后采样阴影还差点东西,但是不用上边的方式,上边方式atten得到的只是采样后的阴影,我们想要的是采样后的阴影加上光照的衰减。
这个可以参考unity内部的shader:
这里补充上这两句就可以实现我们想要的。实现了阴影之间的过渡。包括投射的包括接受的,从实时到烘焙的过渡。
可以看一下上边两句具体干了啥:
这里就相当于一个宏,干了俩宏的事情。
其实就是把之前的光照衰减和实时阴影的采样进行了一个合并,就是一块写了。
这一句也是两个的结合。
=========================================================================================================================
准备这样一个场景,然后把灯的模式改成baked。然后地板是static,球是非static。
这样烘焙之后,因为baked模式,所以一切灯都是烘焙的,同时只有静态物体接受灯光。那么结果就是球根本没收到灯光。
如果想要让这个球收到灯光。
我们想要让这个动态物体获得光照的影响,那就可以用探针。
可以创建一个空对象,然后直接add component,找到探针。
我们调大这个空物体的scale,其实就有一个包围盒被放大。
然后这里好像包围盒上就直接有探针。
绿色就偏多一些。
上边能这么干,是因为使用的是内置的standard shader,他都弄好了,使用我们自己的shader就不行了,怎么才能行呢?
(附上我们自己的材质,就会是黑色的,因为场景内的光都是baked的,而且动态物体,所以没有东西能照亮它)
还是参考unity内部的shader。
有这么一段。
粘贴到我们自己里面。
这里首先需要此对象没有开启静态烘焙,也就是这个物体时动态的??然后俩条件,讲了半天没听明白。
然后这里写了一句o.sh,我们需要在我们自己的o里面也定义一个sh;
再往下,一个条件判断后,它进行了一个函数的调用:
里面的参数就是4盏灯,float4变量表示同时记录四盏灯的x,4个y,4个z,这样合起来一共四个灯。(逐顶点照明最多支持四盏灯)
然后传入四个颜色,四个衰减,世界坐标和法线。
修改一下。入乡随俗。
当计算完这个sh之后,怎么使用它?就是在frag里面:
给giInput做一个赋值即可。
然后解决报错就出现了光照探针的效果。
这里还测试了一个什么逐顶点光,就是上边写的那一坨。没看懂,
大佬文章,从PBR到GI。