unity5自带的standard有没有unity 镜面反射shader效果

Unity 5 中的全局光照技术详解(建议收藏)
招聘信息:
本文整理自Unity全球官方网站,原文:(文章较长,请耐心阅读)简介全局光照,简称GI,是一个用来模拟光的互动和反弹等复杂行为的算法,要精确的仿真全局光照非常有挑战性,付出的代价也高,正因为如此,现代游戏会先一定程度的预先处理这些计算,而非游戏执行时实时运算。同一场景里:没有照明(左),只有直接光源(中),和有间接光源的全局光照(右)的表现,注意颜色如何在不同的表面进行光的”反弹”,产生更真实的结果。在本文中,我们会描述全局光照如何在Unity里运作,带领你通过不同的照明技术解释如何在项目里设定照明,并思考如何透过各种工具帮场景打光。选择一个照明专案广义的来说,Unity的全局光照是”实时”或是”预先计算好”的,在某些情况下两种方法可以结合使用,照出更逼真的场景。本节我们会针对两种技术的差异优势和使用时机做个简单的描述。实时照明(REALTIME LIGHTING)预设情况下,Unity的灯源(直接光源, 投射灯, 点光源)都是实时的,代表这些灯源会把光线照射到场景并以每一帧的频率更新,由于光源是可以在场景内移动的对象,场景灯光的更新是实时的,你可以在游戏窗口和场景窗口看到改变。实时照明的影响:注意到因为没有反射光源的关系阴影是全黑的,只有投射光锥体范围内的对象表面才有光源影响。实时照明是场景里照亮物体最基本的方法,用来照亮角色和会动的对象,可惜的是,Unity实时照明里的光线不会反射,因此我们才导入了全局光照系统,启用了预先计算的技术,都是为了表现一个更逼真的场景。烘焙全局光照(BAKEDGI LIGHTING)当烘焙一张光照贴图(Lightmap)时,场景内的静态对象会基于光的影响算出一张贴图成果,并迭在场景对象之上建立照明效果:左:一个简单的光照贴图场景成果,右:由Unity产生的一张光照贴图(阴影和光源信息都被纳入计算)这些”光照贴图”可以包含场景内投射到物体表面的直接光源,以及在不同物体间反射的”间接光源”,这样的光照贴图可以透过物体材质上的着色器(Shader)描述像是颜色的表面信息(Albedo)和凹凸(Normals)信息。烘焙光照所产生出来的贴图,是无法在游戏运作的时候变更运算的,因此被定义为静态(Static),虽然仍可在这层贴图上继续迭加光源计算,但两者已无法交互运算,通常我们采用这光照法来让低阶的手机能顺利执行,解决光在游戏中运行的效能问题。预计算全局光照(PRECOMPUTED REALTIME GI LIGHTING)虽然传统的静态光照贴图无法在游戏执行时改变场景光照条件,但预先计算的实时全局光照系统能帮我们实时运算复杂的场景光源互动,透过这种方法,就能建立昏暗的环境带有丰富的全局光照反射,并实时反映光源的改变。好比做个日晷,阴影的位置和颜色会随着光源移动改变,这在原本的烘焙光照系统是无法达成的。一个用GI呈现的日晷案例为了在合理的帧率实现这些效果,我们需要在实时运算之前先将一堆垄长的数字数据做”预计算”,预计算负责计算游戏过程中光的复杂行为,它可以在时间空档时进行计算,我们称作一个”脱机”运算。如何运作?最常见的需求是我们希望间接光源能够列入场景光照贴图的计算,幸好,原理上这些间接光源都是从直接光源慢慢转变过来的颜色,只有少部分特定情况有比较大幅度的颜色改变,这样的Unity的全局光照预计算,利用间接光源漫反射(diffuse)特性对运算有利。通常好的阴影是透过即时光源所计算出来的,而非烘焙到光照贴图,假定我们并不需要太复杂的细节取样,可以大大降低全局光照所产生的数据大小。透过预计算来简化整个流程,有效的降低了原本要在游戏中实时计算的全局光照运算数量,如果你要常在游戏中改变光源颜色,旋转光源或是调整光的强度,甚至对场景表面做变更,这点就很重要。Unity从表面上采样底层贴图,并从广义定义颜色的值到一个大型的群组,或是”丛集”,这会产生一个低解析的仿真静态几何,以方便我们用来计算光照。左:场景显示设置为”Albedo”时,可以清楚看到由Unity预计算所产生的纹理;右:如同游戏的场景一样,即时光照计算完结果后套用到场景。基本上,当在计算全局光照时,我们会针对静态场景周围做”光迹追踪”运算,这是非常耗效能的,因此无法苛求要实时运算,相反的,Unity把光迹追踪用在计算这些表面的丛集关系 - 在预计算”光传输”的阶段,然后把世界串成一个网络结构,我们在关键性的游戏过程就不再需要耗费效能的光迹追踪法。我们有效的创造出一个简化的算法可以在游戏过程中变化输入结构,这代表我们可以改变光源或是表面颜色,并很快的看到场景内全局光照的影响,算出的结果产出光照贴图透过GPU着色,并和其他照明或是表面混合,最后输出到屏幕上。收益和成本(BENEFITS AND COSTS)虽然可以同时使用烘焙方式的GI和预计算的GI,但要注意是同时仿真两个系统,效能负担也会两次运算的总和,不只是因为要储存两套光照贴图在显卡内存,同时着色器也得付出两次的处理成本。最终要选择哪个方法还是要取决于你项目的性质和预期的硬件考虑,例如在手机平台上,效能和内存限制较高,烘焙的GI法就会比较适合,如果是在有显卡的计算机上或是游戏机上执行,那可能使用预计算实时全局光照,或两个同时使用就比较可行。决定采用哪一种方法可以针对你的目标平台评估,记得如果项目要同时符合几个不同的硬件需求,往往都是以效能最高的平台为考虑。预先计算的过程(THE PRECOMPUTE PROCESS)在Unity里,预计算是在背景执行,不管是自动流程或是手动启用,计算期间你都可以继续编辑你的游戏对象而不受影响,预计算的时候会在右下角出现一个进度条,不同的算法会有不同的运算阶段,进度条上方也会显示阶段名称与进度。从上面的例子可以看出,11个工作已经进展到第5个,”丛集”还有108件工作要执行完才会到第6个会话,数值状态栏表如下:启用一个预计算(STARTING A PRECOMPUTE)只有静态对象会被纳入GI预计算,要让预计算启动首先必须最少要有一个静态对象,不管是单独设定对象或是从层级选单用Shift + 选择多个对象后一次修改。从属性面板,将对象的Static勾选起来,这会将该物体所有跟静态对象相关的旗标打开,包含导航旗标或是批处理旗标,这或许不是你想要的,针对预计算只要把”Lighting Static”这个旗标打勾即可。更细部的控制,只要点选属性接口Static右边的下拉式选单即可,此外,从Window里的Lighting接口也能指定设定静态对象。如果你的场景设为自动(Lighting->Scene->Auto),Unity的预计算就会自动启动,否则就需要用下列的流程手动执行。自动/手动预计算(AUTO/MANUALPRECOMPUTE)假如Lighting接口底下Auto这个选项是被勾选的(Lighting->Scene->Auto),那么预计算就会自动在背景不停的改变场景产出的静态几何。但如果这个勾选没勾,你将需要点击在Auto旁边的”Build”按钮手动启动预计算,这会用同样的方式进行预计算,让你比较好控制计算的开始时间。手动启动预计算会对场景所有的照明与各方面进行重新评估并重新计算,如果你希望有选择性的计算,可以从”Build”旁边的下拉选单来选择。使用烘焙GI或是预计算GI(ENABLINGBAKED GI OR PRECOMPUTED REALTIME GI)预设情况下,两种计算法在Unity里都是启用状态(Lighting->Scene),因为如此,你可以针对单独的光源设定要采用哪种计算法(Inspector->Light->Baking)在一个场景同时采用两种方法可能会对效能造成负担,最好的做法在同一个时间只用一种方法,要关闭任何一种方法可以从GI的接口(Lighting->Scene),把不要用的方法取消打勾即可,只有有打勾的算法会计算,任何相关光的设定都会被覆盖。预灯光设定(PRE-LIGHTSETTINGS)Unity里每盏灯光默认的烘焙模式都是”Realtime”,这代表这些灯光仍然会照亮你的场景,Unity的预计算GI系统会处理间接光。但如果默认的烘焙模式是”Baked”,那么这些灯光将会透过Unity的烘焙GI系统处理直接光源和间接光源,产生出来的光照贴图一旦贴到场景上在执行期间是不能改变的。一个设定烘焙模式为”Realtime”的点光源选择烘焙模式为”Mixed”的话,场景内的静态对象会被烘焙GI拿去做计算,然而,不像”Baked”模式,混合模式的灯光仍会继续运算即时光源到非静态对象上,这对于你想把静态环境烘成光照贴图,但同时又希望同样一盏灯能为会动的角色计算阴影很有帮助。GI快取(GI CACHE)无论是烘焙还是预计算系统,Unity会”缓存”场景的光照数据到”GI快取”,并会在计算时尝试重复运用这些数据来节省时间,你对场景的改变会影响这个数据重复利用的多寡。如果你要清除这个快取可以从(Preference->GICache->Clear Cache)来清除,清除后代表所有数据都必须重新运算,因次会花费一些时间,在某些情况下你也许需要降低档案空间大小(例如要把项目转到另外一台计算机)是有帮助的。场景设定(SCENESETUP)选择着色路径(CHOOSING A RENDERING PATH)Unity支持许多著色技术或”路径”,在启动一个项目时,必须要订出出一个路线,Unity预设是”Forward Rendering”。在”ForwardRendering”里,每个对象著色是根据每个影响对象的光,透过”Pass”来著色,所以有可能一个对象被重复著色了好几次,取决于有几盏灯在作用范围里。这种方法的优点是快速,也代表硬件需求低,此外,这种正向著色提供了广泛的自定义”着色模型”,可以快速处理透明度,也支持像是多重采样柔边(MSAA)的硬件功能,等等有些在其他路径上是无法实现的功能,对于图形质量有很大的影响。然而他的缺点是要为每盏灯光付出相对应的成本,也就是说,对象被越多盏灯光影响,花费的运算成本就越高,有些类型的游戏必需要大量的光源,就会令人望之却步,反观如果你能管理好你的灯光数量,那这个路径会是一个非常快速的解决方案。“Deferred”路径,是延迟了光的遮蔽与混合信息直到第一次接收到的表面的位置 法线 以及材质数据著色到一个”几何缓冲器”(G-buffer)作为一个屏幕空间的贴图,最后合成这些结果,这种方法优点是照明的著色成本是和像素数量成正比,而非灯光数量,因此你不用再管控场景灯光数量,某些游戏类型将会是一个关键优势。“Deferred”路径呈现可预见的效能特点,但通常需要较强大的硬件,对于手机平台支持度也较低。关于”Deferred”,”Forward”或是其他路径的更多信息,可以参阅。选择一个色彩空间(CHOOSING A COLOR SPACE)除了要选好著色路径之外,一开始选择一个”色彩空间(Color Space)”也是很重要的事情,色彩空间决定采用哪种算法来计算照明或材质加载时的颜色混合,这会对游戏的画面真实感有很大的影响,但大多数情况下,太超过的色彩空间设定可能会被目标平台的硬件强制限制。推荐比较接近真实的色彩空间是Linear,你可以从PlayerSetting里面找到”Color Space”来设定(Edit->ProjectSetting->Player)设定Linear的优点是会让场景内的提供给着色器的颜色也会因为光强度增加变亮,如果换成”Gamma”色彩空间,亮度马上会转为以白色做为参考,这将不利于图像的质量。采用Linear和Gamma颜色空间的图像对照表,可以注意到切换成Gamma时颜色快速的换成以白色为光照强度为基准。Linear另一个好处是着色器能在没有Gamma补偿的情况下对贴图进行取样,这有助于确保颜色质量在经过著色管道还能保持一致性,能提高色彩和计算的精度,最后屏幕的输出结果更为真实。可惜的是Linear颜色空间有些手机平台甚至有些游戏机不支持,应该说PC或是一些新手机硬件和次世代游戏机才会支持Linear颜色空间,在这种情况下,就得用Gamma方法替代了。确认你的目标平台是哪一种之后才选择适合的颜色空间是很重要的,想要了解更多关于颜色空间的信息,可以参阅。高动态范围(HDR)如同颜色空间,相机的”Dynamic range”动态范围是需要指定的,从本质上来说,这是用来定义相机截取的最亮与最暗的颜色范围,要启用HDR可以从相机组件里把HDR项目打勾即可,要注意的是,某些手机硬件是不支持HDR的,在著色路径为ForwardRendering并启用MSAA柔边时也不支援HDR,HDR和Linear颜色空间一起搭配是最好的,在处理非常明亮的颜色的时候还能保有准确性。在默认的情况下,Unity是使用低动态范围(LDR),颜色会以每个频道8位储存三原色(红 绿 蓝),8位确切的意思是用8个0或1的数字组合成的值,一共会有256种组合,三个频道256 x256 x 256可以组合出1600万种色采来表达从黑色到白色不同层度的灰阶。在现实环境中,颜色远远超出1600万个色阶,颜色和亮度的排列甚至远远超越了我们人眼所能辨识的,同样的,Unity能够处理超出这些范围的颜色并输出高于LDR设备支持的颜色,用来提供给像是计算机画面的高质量结果,尽管现今输出装置仍有很多限制,这些多出来的数值仍然可有很多的应用。启用HDR之后,会储存更大精度的颜色资料(使用浮点运算表示),能处理更多更亮的颜色范围。HDR能让我们在同一画面下维持两个巨大差异的亮度,例如场景的阴影区域和户外的亮光,我们也可以在环境场景建立一个”光晕”或发光特效,透过这些特效或可视的光影效果能增加画面的真实感,但也要小心处理这些效果以防止他们曝光过度。色调映射(TONEMAPPING)以摄影来比喻的话,如果我们使用不同的曝光度来拍摄我们的场景,我们可以先观察到哪些颜色会因为曝光过度而遗失,浅色调在很亮的区域可能被白色给覆盖过去,暗色调可能会被黑色给覆盖,这像是计算机图学的”色调映像”,当颜色在显示设备(比如计算机屏幕)表现范围之外时,算法会将颜色修正为装置合理的颜色并重现在屏幕上,当相机启用HDR时,建议相机加入Tonemapping这只程序(Assets->ImportPackage->Effects),这只程序可以帮你转换控制超出范围的颜色成为合理的颜色。更多关于色调映像的数据可以查看。环境光(AmbientLighting)场景中一个照亮整体环境非常重要的就是”环境光”,可以说是影响场景光源最全面的一个要素,环境光很多情况都很有用,也取决于你所选的风格,比如卡通风格的阴影不清楚或灯光是手绘风格,环境光也很适合用在当你不想单独调整场景内的灯光但又想要增加整体场景亮度的时候。在没有使用Unity 5全局光照的功能时,环境光不会算出准确的物理遮挡,但如果开启了任何一种GI的情况下,从Skybox照下来的环境光就能被算出遮挡,结果会更加真实。在同一个场景之下,左边是没有任何光源的场景,右边则开启了环境光,可以注意到调整环境光的强度天空盒的亮度并没有改变。透过将对象设定为静态对象来启用全局光照的场景,可以注意到光线在不同的表面有遮挡的效果。使用环境光的好处是耗用效能很低,因此在手机平台上只要灯光数量控制得宜也能得到很好的表现,你可以从Lighting窗口(Window->Lighting)找到EnvironmentLighting设定区域(Lighting->Scene),改变Ambient Source来改变环境光来源。Ambient Source的默认值是”Skybox”,上图的天空盒是Unity 5系统产生的一个默认天空盒,带有一个蓝色调的环境光,以及一些用该半球体上纯色与渐变色的设定。要注意的是改变Lighting里面的AmbientSource并不会让Skybox的颜色改变,但会影响到场景内环境光照射。反射源(REFLECTIONSOURCE)默认的情况下,场景中的对象会使用Unity内建的标准着色器(StandardShader)来著色,标准着色器是一个基于物理的着色器(PBS),它会透过模仿真实世界的物理特性像是反射或能量传递等来模拟真实世界里光在材质上的表现。当使用标准着色器时,每一个材质都会具有一定程度的镜面反射(specularity)和金属反射(metalness)属性,在没有强大的硬件来处理即时光迹追踪反射的情况下,我们得仰赖预先计算著色反射,我们使用了一个由六张描述天空的图片所组成的方体贴图(Cubemap)或从Unity 5用来从定点搜集环境信息的反射探头(Reflection Probe)产生所需的贴图,然后在和其他光和地表信息混合运算来仿真如同我们真实世界看到的反射效果。在预设的情况下,调高Specular和Metalness会更清楚的反射天空盒,反射的来源可以从设定来调整。场景内的对象在默认的情况下会反射天空盒的内容,但你可以从Lighting接口里找到ReflectionSource属性来改变来源,指定一个新的方体贴图,或指定一个反射探头来定义。反射探头(REFLECTIONPROBES)天空盒的信息不可能包含所有的场景对象,在许多情况下,对象从天空搜集反射信息时可能会被遮蔽,像是室内对象或是在类似桥或是隧道等建筑物里的对象,为了要准确反射这些对象,我们必须用反射探头针对这些对象取样,这种探头从他们的位置对周围取样并把结果写到方体贴图,可以让周围经过的物体得到环境的反射影像。你可以透过GameObject->Light->Reflection Probe来新增一个反射探头,反射探头的位置会决定方体贴图取样的内容,以及反射所看起来的样子,一般来说,基于效能考虑反射探头越少越好,请记住,反射探头并非用来让物理得到精确结果,而是让游戏世界有更好的反射,大多数情况下几个安排妥当的反射探头就很足够了。左图:场景只有预设的反射设定 & 右图:场景加入了反射探头后的结果在反射探头的属性面板我们可以设定Type为实时(Realtime),烘焙(Baked)或自定义(Custom),需要明白的是,实时反射的设定对效能极为不利,每多一个实时反射探头就会多出额外六次的著色运算,因此摆设实时的反射探头应该要有明确的需求,例如反射会闪动的霓虹灯,否则一般来说建议设成烘焙的就够了,效能也会好很多。需要注意的是,只有标记为”ReflectionProbe Static”的对象才会被反射探头取样,从属性接口上静态对象(Static)的下拉选单打勾即可,相反的,实时的反射探头会对所有可见的物体取样,除非你在屏蔽(Mask)选单指定剔除它。照亮一个场景(LIGHTINGA SCENE)我们已经介绍了一些在Unity里针对场景照明开始工作之前所需要考虑的条件,希望你对目标平台该用哪些设定已经有了一个方向(手机平台采用烘焙GI和Gamma颜色空间,PC或游戏机采用实时GI和Linear颜色空间),接下来让我们来看看有哪些协助制定光源的工具。定向光源(DIRECTIONALLIGHTS)“定向光”非常适合用来模拟阳光,它的特性就像是个太阳,定向光能从无限远的距离投射光源到场景,从定向光发出来的光线是互相平行的,也不会像其他种光源会分岔,结果就是不管对象離定向光源多远,投射出来的阴影看起来都一样,这其实对户外场景的照明很有利。定向光没有真正的光源坐标,放置在场景任何地点都不会影响光的效果,只有旋转会影响定向光的照射结果。其他有光源坐标的灯光类型,例如投射灯(Spotlights),角色阴影会因为接近或远离光源而改变,这也许在照亮室内环境时会是个问题,一般来说,避免角色太接近隐形的光源,我们会建立一个亮点来假装光源。使用定向光不用考虑距离,不管多远它都会影响场景所有的表面(除非被剔除),当使用延迟(Deferred)著色路径时会造成一些效能损耗,要注意的是,使用这个著色路径时,光的效能代价和他影响的像素数目是成正比的,但虽然需要消耗效能,起码结果较为统一,因此比较容易调整平衡。在预设情况下,新的场景都会附带一盏定向光,在Unity 5里还会与天空盒系统关联(Lighting->Scene->Skybox),你也可以删除预设的定向光并创建一个新的光源,然后从Sun这个属性重新指定(Lighting->Scene->Sun)旋转预设的定向光会导致天空盒也跟着更新,如果光的角度和地面平行就可以做出日落的效果,把光源转到天空导致变黑就能做出夜晚的效果,从上往下照就会模拟日间的效果。如果天空盒有指定为环境光源(Ambient Source),那么天空盒的颜色就会影响环境里面的对象。点光源(POINT LIGHTS)点光源可以想象是在3D空间里一个对着所有方向发射光线的点,很适合用来制作像是灯泡, 武器发光或是从物体发射出来的爆炸效果,点光源的亮度从中心最强一直到范围属性(Range)设定的距离递减到0为止,光的强度从光源到距离成反比,这是所谓的”平方反比定律”,类似光在现实世界的行为。点光源从它的位置对四面八方射出光线,球形的小图示代表光的”范围”,光线到达此范围是会”衰减”到0,但如果有间接光源或反射光则会继续投射。点光源开启阴影运算是很耗效能的,因此必须谨慎使用,点光源的阴影为了要给六个不同的世界方向会运算六次,在比较差的硬件开启此功能会造成较大的效能负担。当在场景中加入点光源时要注意,目前它们不支持阴影的间接反射,这代表由点光源产生的光线,只要在距离内有可能会穿过对象反射到另外一面,这可能会导致墙壁或地板”漏光”,因此放置点光源要格外注意,然而如果是采用Backed GI的话,就不会有这类的问题产生。聚光灯(SPOTLIGHTS)聚光灯投射一个锥体在他的Z轴前方,这个锥体的宽度由投射角度(Spot Angle)属性控制着,光线会从源头到设定的范围慢慢衰减到0,同时越靠近锥体边缘也会衰减,把投射角度的值加大会让锥体宽度加大,同时也让边缘淡化的力度变大,这现象学名叫做”半影”。聚光灯有许多用途,他们可以用来模拟路灯, 壁灯,或许多创意用法,例如模拟手电筒,因为投射区域能精确的控制,因此很适合用来模拟打在角色身上的光或是模拟舞台灯光效果等等。光线会因为离源头越远而递减,可以注意到光也会因为越靠近锥体边缘而变弱,我们称之为半影区,这会因为锥体角度变大而更明显。和点光源一样,使用预计算GI时,聚光灯不支持间接光阴影,这表示灯光会穿过几何影响到另外一面,因此放置投射灯要特别注意。区域光(AREA LIGHTS)区域光可以当作是摄影用的柔光灯,在Unity里面他们被定义为单面往Z轴发射光线的矩形,目前只能和烘焙GI一起使用,区域光会均匀的照亮作用区域,虽然区域光没有范围属性可以调整,但是光的强度也是会随着距离光源越远而递减。区域光照亮表面并在区间产生漫反射与柔和的阴影区域光用在建立柔和的照明效果非常有用,光线在任何方向穿过光的表面时会产生不同方向的折射 - 造成在对象上产生漫反射,常见的用途是拿来当作天花板壁灯或是背光灯,为了实现这功能,我们必须从每个光照贴图像素上发射一定数量的光线,背对着区域光以确定光有能见度,这代表区域光的计算是消耗很大的,而且会延长烘焙的时间,但如果运用得宜可以增加场景光的深度,那么消耗就很值得,值得注意的是区域光只能用在烘焙,因此不影响游戏效能。发光材质(EMISSIVE MATERIALS)虽然区域光不支持实时GI,Unity提供另外一个柔和的灯光效果叫做发光材质(Emissive Materials),和区域光一样,发光材质可以让物体表面发光,他们可以反射场景内像是颜色或是光强度等等能在游戏内改变的光源,自发光(Emission)是一个在标准着色器(Standard Shader)内的属性,允许静态对象成为一个发光体,预设情况下是0,代表指定了这个材质并不会有任何的自发光反应,HDR颜色选择器能指定发光颜色,强度能在0-1的范围调整,来建立类似区域光的效果。发光材质并没有范围属性,但从材质发出的光会以二的次方速度递减,自发光材质只会作用在有标记为”Static”或”LightmapStatic”卷标的对象,同样的,如果发光材质附加在非静态对象或像是角色的动态对象则不会有任何作用。然而,材质设定只要emission数值大于0,即使他们不接收场景光源在画面上也会有发光的效果,这种效果也可以透过将emission属性底下的”Global Illumination”改为”None”,像这样的自发光材质很适合来模拟霓虹灯等类似的光源。使用Unity标准着色器并附加自发光材质的一个范例,注意从Unity Logo的自发光也会计算阴影,在这个案例让球体有了阴影。发光材质只会影响场景内的静态对象,如果你想要影响像是角色的动态对象,就必须采用光探头系统(Light Probes),在游戏周期改变发光材质的值会更新光探头取样,并直接在结果上看到变化。光探头(LIGHT PROBES)静态对象只被Unity全局光照GI系统计算,为了使动态对象能够和静态场景接收到的光影信息互动,我们需要纪录这些光的信息并做成可以在执行期间快速存取的格式。我们在场景放置许多取样点来截取各个方向搜集来的信息,颜色信息会被编成能在游戏中快速被取出的一组数值(或系数),这些取样点我们称为”光探头”。使用了光探头的场景,注意图中光探头放置位置在光线容易变化的地方,例如阴影或是颜色转换的地方。光探头允许移动对象接受由全局光照GI所计算出来复杂的反射光源,对象在著色网格的时候会判断附近光探头的位置并且把光的信息一并融合计算,这是透过找寻由光探头所产生的一个四面体,然后决定哪个四面体的落入对象的轴向,这样就能让场景内的动态对象正确地接受光信息,如果没有放置光探头,动态对象就无法接受全局光照的信息,造成动态对象比场景还要暗。预设的情况下,场景是没有任何光探头的,你必须从GameObjects->Light->LightProbe Group自行建立光探头群组。假如全局光照里的Auto是打勾的(Lighting->Scene->Auto),当光源或是静态对象更新时,光探头信息也会实时更新,没打勾的话必须点Build运算才会更新。
微信扫一扫
订阅每日移动开发及APP推广热点资讯公众号:CocoaChina
您还没有登录!请或
点击量10375点击量8993点击量5541点击量4759点击量4522点击量4201点击量3457点击量3403点击量3391
&2016 Chukong Technologies,Inc.
京公网安备89  本文主要介绍Untiy5以后的GI,PBS,以及光源探头,反射探头的用法以及在着色器代码中如何发挥作用,GI是如何影响渲染的,主要分成三个部分,最开始说明PBS需要的材质与相应概念,二是Unity&里相应GI的操作,三是对应着色器代码的理解。如果没有特殊声明,所有操作与代码都是针对Unity5.3.
PBS材质与概念
  简单来说,PBS的优点不同的照明下获得一致的外观,更容易实现,更直观的参数。
PBS材质概念:
  1.albedo&反照率&
  反照率贴图定义漫反射的基本颜色,与原来的漫反射贴图相比,不包含定向光与AO,在这我们应该由环境自己的定向光与AO来影响,Unity里,我们用GI得到相应烘培或是实时的方向光与AO。
  2.Smoothness/Microsurface&表面细节&材料细节&光滑度
  光滑度:描述物体微表面的一个参数,可以用来定义法线分布函数,这样,粗糙的表面呈现宽,淡的镜面反射,光滑呈现集中和明亮的镜面反射。
  3.Metal/reflectivity&金属性/反射&
  金属与绝缘体应该使用不同的反射设置,导体的反射率&60-90%,绝缘体0-20%,高反射光分子不容易到达内部和散射,这样金属的高表现出来比较淡。
  如下全局设置:
  对于固定的材料,反射率趋于稳定,这样一般来说,一个模型的metal相对变化较少,如泥,水,木头这些他们的反射率相关并不大,只有金属与绝缘体会有相对比较大的反差,而模型表面的粗糙度应该用上面的图光滑性来表示,这个相对变化会比较大些,如下,水和泥土有相似的金属性(反射率),但是光滑度相差大,所以相差比较明显。
  上图来自pbr-theory:&
  Unity中事项,Metallic&贴图,金属性质占用R通道,光滑性占用A通道,GB被忽略。  
  物体的本来颜色用albedo表示,光滑性用Smoothness表示,材质特性用Metal表示,其中Unity&二种PBS标准着色,Standard其中金属性越高,本身镜面颜色占比越高,灯光的颜色占比越低,而高光可以设置自己的镜面反射。
  PS: pbr-practice& 
BRDF&光照模型&概念
  1&能量守恒:一个对象不能反射比他接收的光多。
  粗糙的材料有更多比较淡的亮点,而光滑的材料有更集中,更明亮的亮点,也可以这么理解,漫反射越多,镜面反射相对越少,镜面反射越多,漫反射越少。
  2&菲涅尔效应:边缘的反射更亮。
  更具体的可以看:
  3&微表面模型模型:
  普通的着色模型假定着色的区域是一个平滑的表面,表面有一个法线,而微表面则认为,着色区域是一个无数比入射光线覆盖范围更小的微小表面组成的粗糙区域,这个微小表面是光滑的镜面反射。表面细微细节对扩散的影响,表面越粗糙,反射光越发散或模糊。
GI结合BRDF渲染
  GI全局照明指的是全局照明会模拟光线在场景中的多次反射,所有的东西都是一个潜在的光源,任何可见的模型不是辐射光线,就是反射光线,其中GI(渲染间接光源,本身一般也用BRDF渲染)&配合BRDF生成带灯光直射,环境内模型互想影响的逼真场景。 &
  天空盒是GI的组成部分,反射天空盒可以改变场景所有模型接受反射量。
  GI结合PBS,如下,摄像机的镜头是一样的材质,在不同的环境下:
  更详细的可以点进这个链接:&&
  全局光照的特点在于能够捕捉间接光照,所以5以后,除开原来的direct&light的效果,增加indirect&light的效果,简单来说,就是除开光源之后,然后模型本身做为光源,幅射到别的模型上,层层递归后的效果。现不管是预计算实时GI还是烘培GI都只是针对静态模型。预计算Gi的实时光源与烘陪对应的烘培光源里的强度与反射强度都会影响幅射图与方向图的内容。需要注意,预计算Gi针对的是实时方向光,而烘培GI针对的是烘培方向光。
  Skybox&:天空盒,参考材质Skybox/Cubemap,如果全局光源与全局反射探头都选择Skybox模式,则会把Skybox当做一个Cubemap,场景所有模型,静态与非静态的一部分光源从这个Cubemap反射上得到。
  Sun:Skybox选择一个方向光,以这个方向光的方向做方向,这个方向光如颜色与强度不影响全局本身,如果没有设置方向光,选择强度最大的那个方向光源。&
  Ambient&Source:全局光源,如果设置天空盒,但是天空盒本身没有设置,自动选择下面的全局颜色设置,全局反射探头以Ambient&Source当做光源。
  Ambient&Intensity:全局光源强度,越高越亮,为0时,光源不起作用。
  Ambient&GI:当预计算GI与烘培GI二个都选择后,这个可以选择是用实时还是烘培。&
  Reflection&Source:Unity默认放入的全局反射探头,选择Skybox会以Ambient&Source里提供的光源颜色做反射,同时也可以自己提供cubemap当做反射源。
  Resolution:反射探头解析度,应该是对应RTT的cubemap六张纹理的分辨率。
  Compression:是否压缩。
  Reflection&Intensity:Reflection&Source针对所有模型反射强度,值越大,相应的模型面上显示越清晰的Reflection&Source。
  Reflection&Bounces:当设置多个Reflection&Probe时,互相反射对方信息的次数,如二面镜子。
  Precomputed&Realtime&GI
  预计算实时GI,针对实时静态物体之间的幅射光,故相应的幅射图与方向图都是低分辨率下的。动态物体可以使用光照探头来得到相应反射光源信息,注意动态模型与光探头的距离。
  Realtime&Resolution:预计算实时GI,把场景分成许多格,得到每个格的幅射信息。那么这个值越高,计算量将以平方增加,最终值还将和General&GI里的光源参数里的Resolution相乘。
  CPU&Usage:生成相应GI的数据时,在游戏运行时分配多少CPU计算能力。&
  Baked&GI
  烘培GI,因此能得到更精确的模型之间的反射光信息,但是不能运行时更改相应的光源信息,如颜色,方向,预计算实时GI没有这个问题。
  Baked&Resolution:一般来说,是Realtime&Resolution&10+,因为相应的幅射图与方向图精确度要高很多。
  Baked&Padding:网上说是光照贴图中分隔的距离,还需要验证。
  Compressed:是否压缩
  Ambient&Occlusion:值越高,遮挡地区得到的光比差越大。
  Final&Gather:用FG技术来产生烘培数据,这种技术时间会长一些。参考
  Ray&Count:Final&Gather所用的光线追踪光线数目。
  Atlas&Size:图集里贴图的大小,越低实际占用越精确也就是越小,但是贴图产生越多,应该选择一个合适的大小。&  
  General&GI
  预计算实时GI还是烘培如何生成.
  Direction&Mode:幅射图/带方向光/加镜面,具体看Shader分析,其中预计算实时GI与烘培GI在这生成的相应幅射图,方向图等有所不同,后面会提到。
  Indirect&Intensity:间接光的强度。
  Default&Parameters:生成相应贴图所需要的信息。
  其中全局预计算GI,烘培GI,全局反射探头相应的改动需要重新烘培,如果选择自动,相关改动会自动在后台烘培。
  上面这些说实话,写这么操作没啥用,自己对着每项实践一篇,什么都清楚了。&
Light&Probe:&
  对于GI来说,不管是预计算GI与烘培GI,都不会对非静态模型计算间接反射,光探头的加入,可以使非静态模型得到周围静态模型的幅射光,主要技术原理使用一种球谐光照的技术,注意light probe一般不会对静态模型有影响,你看到的影响,只是因为非静态模型的颜色变化大造成的反差。
  相关球谐光照的技术原理可以参见,本人也看不懂,只能说看了后有点印象是怎么回事:
  一种2D傅立叶级数的球形推广,可以把光照函数展开成SH基函数的叠加,类似傅立叶变换能把任何函数展开成正弦波的叠加,对光照图来说一般只用2&bands&=&4&RGB&textures,通过丢失高频细节来压缩存储。  
Reflection&Probe:
  定义一个Cubemap用来影响周围模型的镜面反射,给镜面高光模型使用。
  一般来说,我们想得到一个实时场景的Cubemap,只需要在一个点,用摄像机对着前后,左右,上下,各拍摄一次,形成6个面,组合成Cubemap.
  Type:烘培,用户,自动。其中,烘培就是用户来控制生成一个当前的场景cubemap,用户就是用户自己提供一个cubemap,实时就是不断更新这个cubemap以映射最新的场景。
  当选择实时,Refresh&mode:On&&awake启动时,每桢,用户脚本控制,当每桢时,如下选择。
  Time&slicing:一桢先生成6个面,后面8桢每桢生成一个mipmap,一共9桢。
  Individual&face:6面6桢,加后面8桢每个mipmap,一共14桢。
  No&time&slicing:一桢内把mipmap与cubemap全部生成。
  Importance:当多个反射探头影响一个模型时,这个参数影响这个反射探头的比重。
  Intensity:间接光强度,强度影响镜面反射,镜面反射越亮。
  Box&Projection:从着色代码来看,应该根据当前反射探头的源点与AABB影响是模型原来法线。
  Size:大小,范围内的模型使用这个。
  Probe&Origin:原点。
  Cubemap&capture&sttings:
  Resolution:cubempa材质的大小。
  HDR:高光。
  Shadow&Distance:阴影距离,数值越少,阴影越近。
  下面就是RTT对应摄像机的属性。
GI间接光源算法:辐射度算法
  辐射度算法就是:把场景细分到很细很细的面片(如1个像素那么大的三角形),分别计算它们接受和发出的光能,然后逐次递归,直到每个面片的光能数据不再变化(或者到一定的阀值)为止.因此,计算量很大(要计算很多次),而且难以并行(因为递归),参考
  Unity中GI选择non-direction模式生成的辐射度,使用一张图,储存每个位置收到的间接光照,其中假定都只是扩散,没有镜面反射。&
GI&Directional&LightMap算法:
  把半球面的入射辐射度用某种方法进行采样,保存起来在运行时根据法线图中的法线方向来进行一次合成,由于带方向信息,也可以支持高光计算了,可以参考:
  Unity中GI选择direction模型,会在上面辐射图添加一张图,用来存储接收到的光的方向选择specular,烘培GI与预计算GI使用不同的方式,烘培GI在上面二张图各扩大一倍,原来的保存直接光的影响,新增加的位置用来保存间接光的影响,其中预计算GI新增加一张贴图,三张图分别保存辐射光照,光源方向,法线,需要结合实时方向光源。
  探讨Unity5中全局光照(Enlighten)&
  Unity&5.0新功能教学:
  Unity&5&中的全局光照技术详解&
  Unity5&官网文档GI三种模式具体区别&
Unity&GI&BRDF&Shader
  主要参考对照如下Shader文件:
  UnityStandardCore.cginc:前向渲染base,顶点着色入口vertForwardBase,片断着色入口fragForwardBase,这个是着色器的主要Pass,全局方向光,GI信息合并都在这个pass中。
  UnityStandardBRDF.cginc:BRDF的具体实现.
  UnityGloballllumination.cginc:提取GI信息,包含烘陪GI与预计算GI。
  预计算Gi针对的是实时方向光,而烘培GI针对的是烘培方向光,所以当说预计算GI的light时,指的是实时方向光,而烘培GI的light说的是烘培方向光。
  相应主要代码我都已经加上注释,相信还是比较容易看懂的。
1 ---------UnityStandardCore
2 //顶点着色器入口
3 VertexOutputForwardBase vertForwardBase (VertexInput v)
VertexOutputForwardB
UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);
//世界坐标下位置
float4 posWorld = mul(_Object2World, v.vertex);
#if UNITY_SPECCUBE_BOX_PROJECTION
o.posWorld = posWorld.
//屏幕空间位置
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.tex = TexCoords(v);
o.eyeVec = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);
float3 normalWorld = UnityObjectToWorldNormal(v.normal);
#ifdef _TANGENT_TO_WORLD
float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);
o.tangentToWorldAndParallax[0].xyz = tangentToWorld[0];
o.tangentToWorldAndParallax[1].xyz = tangentToWorld[1];
o.tangentToWorldAndParallax[2].xyz = tangentToWorld[2];
o.tangentToWorldAndParallax[0].xyz = 0;
o.tangentToWorldAndParallax[1].xyz = 0;
o.tangentToWorldAndParallax[2].xyz = normalW
//We need this for shadow receving
TRANSFER_SHADOW(o);
// Static lightmaps
#ifndef LIGHTMAP_OFF
//开启烘培GI后
o.ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.
o.ambientOrLightmapUV.zw = 0;
// Sample light probe for Dynamic objects only (no static or dynamic lightmaps)
//光源探头对动态模型的影响,rgb(颜色)根据SH系数还原光源探头与不重要的点光源上的颜色信息
#elif UNITY_SHOULD_SAMPLE_SH
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL
o.ambientOrLightmapUV.rgb = 0;
#elif (SHADER_TARGET & 30)
o.ambientOrLightmapUV.rgb = ShadeSH9(half4(normalWorld, 1.0));
// Optimization: L2 per-vertex, L0..L1 per-pixel
o.ambientOrLightmapUV.rgb = ShadeSH3Order(half4(normalWorld, 1.0));
// Add approximated illumination from non-important point lights
#ifdef VERTEXLIGHT_ON
o.ambientOrLightmapUV.rgb += Shade4PointLights (
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
unity_4LightAtten0, posWorld, normalWorld);
//开启预计算GI后
#ifdef DYNAMICLIGHTMAP_ON
o.ambientOrLightmapUV.zw = v.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.
#ifdef _PARALLAXMAP
TANGENT_SPACE_ROTATION;
half3 viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));
o.tangentToWorldAndParallax[0].w = viewDirForParallax.x;
o.tangentToWorldAndParallax[1].w = viewDirForParallax.y;
o.tangentToWorldAndParallax[2].w = viewDirForParallax.z;
UNITY_TRANSFER_FOG(o,o.pos);
72 //片断着色器入口
73 half4 fragForwardBase (VertexOutputForwardBase i) : SV_Target
FRAGMENT_SETUP(s)
UnityLight mainLight = MainLight (s.normalWorld);
half atten = SHADOW_ATTENUATION(i);
half occlusion = Occlusion(i.tex.xy);
//提取GI里的信息到UnityGI中。
UnityGI gi = FragmentGI (
s.posWorld, occlusion, i.ambientOrLightmapUV, atten, s.oneMinusRoughness, s.normalWorld, s.eyeVec, mainLight);
// 如果是预计算GI或动态模型,gi.light表示主光源,焙烘取
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
// GI生成类型是spceular,才会计算
c.rgb += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);
c.rgb += Emission(i.tex.xy);
UNITY_APPLY_FOG(i.fogCoord, c.rgb);
return OutputForward (c, s.alpha);
93 //填充UnityGIInput,用来得到UnityGI信息。
94 inline UnityGI FragmentGI (
float3 posWorld,
half occlusion, half4 i_ambientOrLightmapUV, half atten, half oneMinusRoughness, half3 normalWorld, half3 eyeVec,
UnityLight light)
d.worldPos = posW
d.worldViewDir = -eyeV
//如果有GI信息。
#if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
d.ambient = 0;
d.lightmapUV = i_ambientOrLightmapUV;
//一般来说,非静态模型,得到环境光
d.ambient = i_ambientOrLightmapUV.
d.lightmapUV = 0;
//全局反射探头的AABB
d.boxMax[0] = unity_SpecCube0_BoxM
d.boxMin[0] = unity_SpecCube0_BoxM
d.probePosition[0] = unity_SpecCube0_ProbeP
d.probeHDR[0] = unity_SpecCube0_HDR;
//用户定义的反射探头AABB
d.boxMax[1] = unity_SpecCube1_BoxM
d.boxMin[1] = unity_SpecCube1_BoxM
d.probePosition[1] = unity_SpecCube1_ProbeP
d.probeHDR[1] = unity_SpecCube1_HDR;
return UnityGlobalIllumination (
d, occlusion, oneMinusRoughness, normalWorld);
130 --------UnityGloballllumination.cginc
131 //FragmentGI 跳转到这
132 inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half oneMinusRoughness, half3 normalWorld, bool reflections)
UnityGI o_
UNITY_INITIALIZE_OUTPUT(UnityGI, o_gi);
// Explicitly reset all members of UnityGI
ResetUnityGI(o_gi);
//动态模型使用 SH得到的漫反射信息。
#if UNITY_SHOULD_SAMPLE_SH
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL
half3 sh = ShadeSH9(half4(normalWorld, 1.0));
#elif (SHADER_TARGET &= 30)
half3 sh = data.ambient + ShadeSH12Order(half4(normalWorld, 1.0));
half3 sh = data.
o_gi.indirect.diffuse +=
//如果没有烘培GI,需要当前全局方向光源的信息
#if !defined(LIGHTMAP_ON)
o_gi.light = data.
//atten阴影信息,值越小阴影越重
o_gi.light.color *= data.
// Baked lightmaps
fixed4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
half3 bakedColor = DecodeLightmap(bakedColorTex);
//没有方向贴图
#ifdef DIRLIGHTMAP_OFF
//设置漫反射
o_gi.indirect.diffuse = bakedC
#ifdef SHADOWS_SCREEN
o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex);
#endif // SHADOWS_SCREEN
//方向与漫反射
#elif DIRLIGHTMAP_COMBINED
fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
//更精准的漫反射
调整过后的half Lambert
o_gi.indirect.diffuse = DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);
#ifdef SHADOWS_SCREEN
o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex);
#endif // SHADOWS_SCREEN
//漫反射,方向,高光
#elif DIRLIGHTMAP_SEPARATE
// Left halves of both intensity and direction lightmap right halves - indirect.
// Direct 调整o_gi.light
fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (bakedColor, bakedDirTex, normalWorld, false, 0, o_gi.light);
// Indirect 漫反射,镜面都是保存在unity_Lightmap中,竖直中间分开
//调整o_gi.light2
half2 uvIndirect = data.lightmapUV.xy + half2(0.5, 0);
bakedColor = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, uvIndirect));
bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, uvIndirect);
o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (bakedColor, bakedDirTex, normalWorld, false, 0, o_gi.light2);
//预计算GI
#ifdef DYNAMICLIGHTMAP_ON
// Dynamic lightmaps unity_DynamicLightmap unity_DynamicDirectionality unity_DynamicNormal
fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw);
//间接漫反射
half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);
#ifdef DIRLIGHTMAP_OFF
o_gi.indirect.diffuse += realtimeC
#elif DIRLIGHTMAP_COMBINED
half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
//调整漫反射
o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld);
#elif DIRLIGHTMAP_SEPARATE
half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
half4 realtimeNormalTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicNormal, unity_DynamicLightmap, data.lightmapUV.zw);
//调整o_gi.light3
o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (realtimeColor, realtimeDirTex, normalWorld, true, realtimeNormalTex, o_gi.light3);
//gi里的信息全放入indirect的diffuse中
o_gi.indirect.diffuse *=
//有反射探头,设置镜面光源信息。
if (reflections)
half3 worldNormal = reflect(-data.worldViewDir, normalWorld);
#if UNITY_SPECCUBE_BOX_PROJECTION
half3 worldNormal0 = BoxProjectedCubemapDirection (worldNormal, data.worldPos, data.probePosition[0], data.boxMin[0], data.boxMax[0]);
half3 worldNormal0 = worldN
half3 env0 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], worldNormal0, 1-oneMinusRoughness);
#if UNITY_SPECCUBE_BLENDING
const float kBlendFactor = 0.99999;
float blendLerp = data.boxMin[0].w;
UNITY_BRANCH
if (blendLerp & kBlendFactor)
#if UNITY_SPECCUBE_BOX_PROJECTION
half3 worldNormal1 = BoxProjectedCubemapDirection (worldNormal, data.worldPos, data.probePosition[1], data.boxMin[1], data.boxMax[1]);
half3 worldNormal1 = worldN
half3 env1 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE(unity_SpecCube1), data.probeHDR[1], worldNormal1, 1-oneMinusRoughness);
o_gi.indirect.specular = lerp(env1, env0, blendLerp);
o_gi.indirect.specular = env0;
o_gi.indirect.specular = env0;
//反射探头的信息存入到gi的indirect镜面中
o_gi.indirect.specular *=
262 --------UnityStandardBRDF.cginc
263 // Main Physically Based BRDF
264 // Derived from Disney work and based on Torrance-Sparrow micro-facet model
BRDF = kD / pi + kS * (D * V * F) / 4
I = BRDF * NdotL
269 // * NDF (depending on UNITY_BRDF_GGX):
a) Normalized BlinnPhong
272 // * Smith for Visiblity term
273 // * Schlick approximation for Fresnel
274 half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
half3 normal, half3 viewDir,
UnityLight light, UnityIndirect gi)
half roughness = 1-oneMinusR
//nh,能射入眼睛的光线的角度 也叫半线
half3 halfDir = Unity_SafeNormalize (light.dir + viewDir);
half nl = light.
//nh,半线与法线夹角,夹角越少,射入眼睛的光越大
half nh = BlinnTerm (normal, halfDir);
//射线与法线夹角
half nv = DotClamped (normal, viewDir);
//光线与法线夹角
half lv = DotClamped (light.dir, viewDir);
//半线与光线夹角
half lh = DotClamped (light.dir, halfDir);
291 #if UNITY_BRDF_GGX
//遮挡函数
half V = SmithGGXVisibilityTerm (nl, nv, roughness);
//法线分布函数 1/Pi
half D = GGXTerm (nh, roughness);
//遮挡函数
half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
//法线分布函数 1/Pi
half D = NDFBlinnPhongNormalizedTerm (nh, RoughnessToSpecPower (roughness));
301 #endif
half nlPow5 = Pow5 (1-nl);
half nvPow5 = Pow5 (1-nv);
half Fd90 = 0.5 + 2 * lh * lh *
//disney Diffuse 菲涅尔 边角有更亮的光
half disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5);
// HACK: theoretically we should divide by Pi diffuseTerm and not multiply specularTerm!
// BUT 1) that will make shader look significantly darker than Legacy ones
// and 2) on engine side "Non-important" lights have to be divided by Pi to in cases when they are injected into ambient SH
// NOTE: multiplication by Pi is part of single constant together with 1/4 now
//镜面系数
half specularTerm = max(0, (V * D * nl) * unity_LightGammaCorrectionConsts_PIDiv4);// Torrance-Sparrow model, Fresnel is applied later (for optimization reasons)
half diffuseTerm = disneyDiffuse *
//Gi镜面,可以看到oneMinusReflectivity越高,grazingTerm越低,specColor越高(本身镜面颜色)
half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));
//GI non-direction与direction的BRDF如下情况
//light.color
实时方向光直接信息
实时方向光直接信息
//gi.diffuse
直接与间接光源信息
间接光源信息
SH(光源探头)间接光源信息
//gi.specular
half3 color =
diffColor * (gi.diffuse + light.color * diffuseTerm)
+ specularTerm * light.color * FresnelTerm (specColor, lh)
+ gi.specular * FresnelLerp (specColor, grazingTerm, nv);
return half4(color, 1);
  整个代码并不多,但是对于前面所说所有东东,在这都有一个完整的解释,这份代码来告诉我们,那些金属性,光滑度,GI中幅射图,方向图,还有光源探头,反射探头所起的作用。
BRDF&光照模型:
  如下是现有光照模型没有考虑的问题:
  光照现象,漫反射并不是各个方面平均发散. & &----微表面模型(NDF).&&
  菲涅尔定理(Fresnel) & & & & & & & & & & & & & & &&&----光源在边角处有更明亮的反光。
  能量守恒,反射的光不能超过入射的光. & & & & &----遮挡因素,越光滑镜面越集中越亮&
  普通的着色模型假定着色的区域是一个平滑的表面,表面有一个法线,而微表面则认为,着色区域是一个无数比入射光线覆盖范围更小的微小表面组成的粗糙区域,这个微小表面是光滑的镜面反射,因为着色区域并不能一个法向量来表示表面的方向,转面代替用一概率分布函数(NDF)来表示。一般来说,分别用如下字母表示:
  D&用来表示法线分布。
  F&用来菲涅尔影响,光源在边角处有更明亮的反光。
  G/V&用来表示凹凸表面间的遮挡因素(Unity用V来表示)&
  如下是Unity相对应BRDF的处理
  先要说明每个引擎对BRDF处理各不同,在这只介绍Untiy的实现:
  D&采用GGX与BlinnPhong二种法线分布函数,BlinnPhong比较简单,效率高。
  F&采用简化的Disney&Fresnel方式求得菲涅尔影响。
  G/V&采用GGX与Beckmann二种技术,可以看到,光滑度是个关键参数。
  针对Unity5.3简化过的Disney&Fresnel,简单分析下.
  Nl:法线与灯光的夹角,夹角越大,这个值越小。
  Nv:法线与视线的夹角,夹角越大,这个值越小。
  Lh:灯光与视线的半线与法线的夹角,其中灯光与视线的半线就是灯光与视线的平均线,简单来说,这个线与法线重合,这条由灯光发出来的射线才能进入我们的眼镜。
  假定&fd90不变,nl与nl的角度越大,那么nlPow5与nvPow5的值越大,最终结果越大,这也是菲涅尔想表达的,光源在边角处有更明亮的反光。
  在Unity中,可以看到,D与V影响镜面反射,F影响漫反射,特别说明,只有Unity是这样处理。
  其中,可以看到反射率(也就是金属性)影响的是GI的镜面反射,也就是反射探头。
  其余的部分挑的说明下:
  顶点着色器中,填充VertexOutputForwardBase&信息,其中ambientOrLightmapUV(half4)&如果包含烘培GI,xy填充相应烘培GI的UV坐标,如果包含预计算GI,zw填充为预计算GI的UV坐标。如果是非静态模型,不包含GI信息,相应light&probe提供的光源信息放入rgb中。&
  片断着色器中,每个像素要得到对应像素上的Unity&GI信息,相应的,Unity&GI中的属性light并不是表示光源,而是当前像素受如主光源,幅射,镜面对当前像素的影响,每个像素对应的light都有差别,千万不要看到写的是个light,就把它当做光照,这样所有理解都不对了。
  其中如果只有烘培GI,当前像素的Unity&GI中参数light不提供信息,indirect里的漫反射包含烘培光源的光照。而预计算GI中当前像素的light本身就是全局光源,indirect只包含物体之间的漫反射信息,而非静态模型中当前像素只有实时光源等直接光照信息,其周围的静态模型的反射光只有通过光探头得到SH信息。
  FragmentGI里常见结构:
  1 UnityLight:
  包含当前像素中光源颜色,方向,法线与光源方向点积
  2 UnityIndirect:
  包含当前像素中diffuse漫反射,specular镜面信息
  3 UnityGI:特别注意,里面的light是UnityLight类型,并不表示光源,而是用来表示当前像素受光源影响的量。
  Gi.light&如果烘陪GI信息,则使用当前主光源填充UnityLight,如果有烘陪GI信息,则填充为空。
  Gi.light2&当烘培GI启用高光后,才会调用。
  Gi.light3&预计算GI启用高光后,才会调用。
  UnityGI在根据函数UnityGlobalIllumination被填充,我们可以分析得到动态模型,使用SH得到漫反射信息。如果没有烘培GI,我们需要实时全局光源,故gi.light是像素所受全局方向光。烘培GI,GI光照信息保存在gl.light2中,gi漫反射直接取光照图里的diffuse.预计算GI,GI光照信息保存在gl.light3中,gi漫反射添加光照图里的diffuse.而反射探头用来添加反射的信息到GI里的镜面信息中了,其中Occlusion&控制gi的diffuse与specular系数。(n*=occlusion)&   
  其中UNITY_SHOULD_SAMPLE_SH&当前渲染的动态模型,使用SH得到间接的漫反射信息,其中UNITY_SHOULD_SAMPLE_SH如下定义。#define&UNITY_SHOULD_SAMPLE_SH&(&defined&(LIGHTMAP_OFF)&&&&defined(DYNAMICLIGHTMAP_OFF)&)&
  预计算GI与烘培GI
  烘培GI如上代码中分析得到,选择non-direction,只有一张图,保存直接与间接光照所有信息,选择direction后,会保存方向,选择specular后,上面二张图,长度扩大一倍,保存镜面有关信息。
  其中预计算GI,选择non-direction,只有一张图,不保存直接光照,只保存间接光照,选择direction,保存方向,选择specular后,不同于烘培GI,会新增一张纹理保存镜面相关信息(从代码来上看,可能是组织过的法线)&
  反射探头:
  unity_SpecCube0_ 全局反射探头&unity_SpecCube1_当前模型受影响的反射探头。
  FragmnetGI:得到全局反射探头与用户定义的反射探头位置,AABB,IsHDR.
  UnityGlobalIllumination:片断像素中得到反射探头影响镜面值,可以看到,SpecCube0与SpecCube1通过深度影响gi间接光源上的镜面颜色。
  BoxProjectedCubemapDirection:影响worldNormal(反视线进过法线后的反射)
  Unity_GlossyEnvironment:可以看到&,光滑值影响LOD值,越光滑越清晰。 UNITY_SAMPLE_TEXCUBE&从cubemap取值。&
  光源探头:
  vertForwardBase:VertexOutputForwardBase的ambientOrLightmapUV设置颜色,从这可以看到,光源探头实际不影响静态的物体,你如果看到有影响,只是因为周围的非静态模型颜色反差造成给你的影响。
  UnityGlobalIllumination:灯光探头的值赋到gi间接光源上的漫反射上。
  :今天看UE4中的环境反射文档,我去,Unity里的做法完全是参照UE4的,通过上面的着色器代码,我们可以完全理解下面这个链接里所说的。
阅读(...) 评论()}

我要回帖

更多关于 unity 反射探头 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信