unity shader 入门(全局光照,Gamma空间,HDR,PBS)

文章介绍了全局光照在Unity中的重要性,包括实时光照、预计算光照和Enlighten解决方案。伽马校正用于线性空间渲染,以避免非线性计算导致的误差。HDR则提供了更高的亮度动态范围,允许更真实的光照效果和屏幕后处理。Unity中的HDR支持和伽马校正策略确保了色彩的正确呈现。
摘要由CSDN通过智能技术生成

什么是全局光照

在前面的内容中,我们可以发现全局光照对得到真实的渲染结果有着举足轻重的作用。全局光照,指的就是模拟光线是如何在场景中传播的,它不仅会考虑那些直接光照的结果,还会计算光线被不同的物体表面反射而产生的间接光照。在使用基于物理的着色技术时,当渲染表面上一点时,我们需要计算该点的半球范围内所有会反射到观察方向的入射光线的光照结果,这些入射光线中就包含了直接光照和间接光照。
通常来讲,这些间接光照的计算是非常耗时间的,通常不会用在实时渲染中。一个传统的方法是使用光线追踪,来追踪场景中每一条重要的光线的传播路径。使用光线追踪能得到非常出色的画面效果,因此,被大量应用在电影制作中。但是,这种方法往往需要大量时间才能得到一帧,并不能满足实时的要求。
Unity采用了Enlighten解决方案来让全局光照能够在各种平台上有不错的性能表现。事实上,Enlighten也已经被集成在虚幻引擎(Unreal Engine)中,它已经在很多3A大作中展现了自身强大的渲染能力。总体来讲,Unity使用了实时+预计算的方法来模拟场景中的光照。其中,实时光照用于计算那些直接光源对场景的影响,当物体移动时,光照也会随之发生变化。但正如我们之前所说,实时光照无法模拟光线被多次反射的效果。为了得到更加真实的渲染效果,Unity又引入了预计算光照的方法,使得全局光照甚至在一些高端的移动设备上也可以达到实时的要求。
预计算光照包含了我们常见的光照烘焙,也就是指我们把光源对场景中静态物体的光照效果提前烘焙到一张光照纹理中,然后把这张光照纹理直接贴在这些物体的表面,来得到光照效果。这些光照纹理不仅存储了直接光照的结果,还包含了那些由物体反射得到的间接光照。但是,这些光照纹理无法在游戏运行时不断更新,也就是说,它们是静态的。不过这种方法的确为移动平台的复杂光照模拟提供了一个有效途径。以上提到的这些技术很多都已非常熟悉,并可能已经在实际工作中大量使用了它们。
由于静态的光照烘焙无法在光照条件改变时更新物体的光照效果,因此,Unity使用了预计算实时全局光照(Precomputed Realtime GI)为我们提供了一个解决途径,来动态地为场景实时更新复杂的光照结果。正如我们之前看到的,使用这种技术我们可以让场景中的物体包含丰富的全局光照效果,例如多次反射等,并且这些计算都是实时的,可以随着光源和物体的移动而发生变化。这是使用之前的实时光照或烘焙光照所无法实现的。
那么,这些是如何实现的呢?它们实际上都利用了一个事实——一旦物体和光源的位置被固定了,这些物体对光线的反弹路径以及漫反射光照(我们假设漫反射光照在各个方向的分布是相同的)也是固定的,也就是说是和摄像机无关的。因此,我们可以使用预计算方法来把这些物体之间的关系提前计算出来,而在实时运行时,只要光源的位置(光源的颜色是可以实时变化的)不变,即便改变了光源颜色和强度、物体材质属性(指的是漫反射和自发光相关的属性),这些信息就一直有效,不需要实时更新。在预计算阶段,Enlighten会在由所有静态物体组成的场景上,进行简化的“光线追踪”过程。在这个过程中Enlighten会自动把场景分割成很多个子系统,它并不是为了得到精确的光照效果,而是为了得到场景中物体之间的关系。需要注意的是,这些预计算都是在静态物体上进行的,因此,为了利用上述的预计算方法,我们至少需要把场景中的一个物体标识为Static(至少需要把Lightmap Static勾选上)。一个例外是物体的高光反射,这是和摄像机的位置相关的,Unity的解决方案是使用反射探针,正如我们之前看到的那样。对于动态移动的物体来说,我们可以使用光照探针来模拟它的光照环境。因此,在实时运行时,Unity会利用预计算得到的信息来计算光照信息,并把它们存储在额外的光照纹理、光照探针或Cubemap中,再和物体材质进行必要的光照计算,得到最后的渲染效果。
Unity全新的全局光照解决方案可以大大提高一些基于PC/游戏机等平台的大型游戏的画面质量,但如果要在移动平台上使用仍需要非常小心它的性能。一些低端手机是不适合使用这种比较复杂的基于物理的渲染,不过,Unity会在后续的版本中持续更新和优化。而且随着手机硬件的发展,未来在移动平台上大量使用PBS也已经不再是遥不可及的梦想了。更多关于Unity中全局光照的内容,可以在Unity官方手册的全局光照(http://docs.unity3d.com/Manual/GIIntro.html)一文中找到更多内容。

什么是伽马校正

要想渲染出更符合真实光照环境的场景就需要使用线性空间。而Unity默认的空间是伽马空间,在伽马空间下进行渲染会导致很多非线性空间下的计算,从而引入了一些误差。而要把伽马空间转换到线性空间,就需要进行伽马校正(Gamma Correction)
伽马校正中的伽马一词来源伽马曲线。通常,伽马曲线的表达式如下:在这里插入图片描述

其中指数部分的发音就是伽马。最开始的时候,人们使用伽马曲线来对拍摄的图像进行伽马编码(gamma encoding)。事情的起因可以从在真实环境中拍摄一张图片说起。摄像机的原理可以简化为,把进入到镜头内的光线亮度编码成图像(例如一张JEPG)中的像素。如果采集到的亮度是0,像素就是0亮度是1,像素就是1亮度是0.5,像素就是0.5。如果我们只用8位空间来存储像素的每个通道的话,这意味着0~1区间可以对应256种不同的亮度值。但是,后来人们发现,人眼有一个有趣的特性,就是对光的灵敏度在不同亮度上是不一样的。在正常的光照条件下,人眼对较暗区域的变化更加敏感
Youtube上有一个名为Color is Broken的非常有趣的视频,在这个视频中,作者用了一个非常生动的例子来说明这个现象。当一个屋子的光照由一盏灯增加到两盏灯的时候,人眼对这种亮度变化的感知性要远远大于从101盏灯增加到102盏灯的变化,尽管从物理上来说这两种变化基本是相同的。那么,这和之前讲的拍照有什么关系呢?如果使用8位空间来存储每个通道的话,我们仍然把0.5亮度编码成值为0.5的像素,那么暗部和亮部区域我们都使用了128种颜色来表示,但实际上,对亮部区域使用这么多颜色是种存储浪费。一种更好的方法是,我们应该把把更多的空间来存储更多的暗部区域,这样存储空间就可以被充分利用起来了。摄影设备如果使用了8位空间来存储照片的话,会使用大约为0.45的编码伽马来对输入的亮度进行编码,得到一张编码后的图像。因此,图像中0.5像素值对应的亮度其实并不是0.5,而大约为0.22。这是因为:
在这里插入图片描述

如上所见,对拍摄图像使用的伽马编码使得我们可以充分利用图像的存储空间。但当把图片放到显示器里显示时,我们应该对图像再进行一次解码操作,使得屏幕输出的亮度和捕捉到的亮度是符合线性的。这时,人们发现了一个奇妙的巧合——CRT显示器本身几乎已经自动做了这个解码操作!这又从何说起呢?在早期,CRT(Cathode Ray Tube,阴极射线管)几乎是唯一的显示设备。这类设备的显示机制是,使用一个电压轰击它屏幕上的一种图层,这个图层就可以发亮,我们就可以看到图像了。但CRT显示器有一个特性,它的输入电压和显示出来的亮度关系不是线性的,也就是说,如果我们把输入电压调高两倍,屏幕亮度并没有提高两倍。我们把显示器的这个伽马曲线称为显示伽马(diplay gamma)。非常巧合的是,CRT的显示伽马值大约就是编码伽马的倒数。CRT显示器的这种特性,正好补偿了图像捕捉设备的伽马曲线,人们想,“天呐,太棒了,我们不需要做任何调整就可以让拍摄的图像在电脑上看起来和原来的一样了!”虽然现在CRT设备很少见了,并且后来出现的显示设备有着不同的伽马响应曲线,但是,人们仍在硬件上做了调整来提供兼容性 在这里插入图片描述

随后,微软联合爱普生、惠普提供了sRGB颜色空间标准,推荐显示器的显示伽马值为2.2,并配合0.45的编码伽马就可以保证最后伽马曲线之间可以相互抵消(因为2.2×0.45≈1)。绝大多数的摄像机、PC和打印机都使用了上述的sRGB标准。
这和渲染有什么关系?答案是关系很大。事实上,由于游戏界长期以来都忽视了伽马校正的问题,造成了我们渲染出来的游戏总是暗沉沉的,总是和真实世界不像。由于编码伽马和显示伽马的存在,我们一不小心就可能在非线性空间下进行计算,或是使得输出的图像是非线性的。
对于输出来说,如果我们直接输出渲染结果而不进行任何处理,在经过显示器的显示伽马处理后,会导致图像整体偏暗,出现失真的状况。
伽马的存在还会对混合造成影响。实际上,渲染中非线性输入最有可能的来源就是纹理。为了充分利用存储空间,大多数图像文件都进行了提前的校正,即已经使用了一个编码伽马对像素值编码。但这意味着它们是非线性的,如果我们在shader中直接使用纹理采样值就会造成在非线性空间的计算,使得结果和真实世界的结果不一致。我们在使用多级渐远纹理(mipmaps)时也需要注意。如果纹理存储在非线性空间中,那么在计算多级渐远纹理时就会在非线性空间里计算。由于多级渐远纹理的计算是种线性计算——即采样的过程,需要对某个方形区域内的像素取平均值,这样就会得到错误的结果。正确的做法是,我们要把非线性的纹理转换到线性空间后再计算多级渐远纹理。
如上所说,伽马的存在使得我们很容易得到非线性空间下的渲染结果。在游戏渲染中,我们应该保证所有的输入都被转换到了线性空间下,并在线性空间下进行各种光照计算,最后在输出前通过一个编码伽马进行伽马校正后再输出到颜色缓冲中。Untiy的颜色空间设置就可以满足我们的需求。当我们选择伽马空间时,实际上就是“放任模式”,不会对Shader的输入进行任何处理,即使输入可能是非线性的;也不会对输出像素进行任何处理,这意味着输出的像素会经过显示器的显示伽马转换后得到非预期的亮度,通常表现为整个场景会比较昏暗。当选择线性空间时,Unity会把输入纹理设置为sRGB模式,在这种模式下,硬件在对纹理进行采样时会自动将其转换到线性空间中;并且,GPU会在Shader写入颜色缓冲前自动进行伽马校正或是保持线性在后面进行伽马校正,这取决于当前的渲染配置。如果我们开启了HDR的话,渲染就会使用一个浮点精度的缓冲。这些缓冲有足够的精度不需要我们进行任何伽马校正,此时所有的混合和屏幕后处理都是在线性空间下进行的。当渲染完成要写入显示设备的后备缓冲区(back buffer)时,再进行一次最后的伽马校正。如果我们没有使用HDR,那么Unity就会把缓冲设置成sRGB格式,这种格式的缓冲就像一个普通的纹理一样,在写入缓冲前需要进行伽马校正,在读取缓冲时需要再进行一次解码操作。如果此时开启了混合(像我们之前的那样),在每次混合时,硬件会首先把之前颜色缓冲中存储的颜色值转换回线性空间中,然后再与当前的颜色进行混合,完成后再进行伽马校正,最后把校正后的混合结果写入颜色缓冲中。这里需要注意,透明通道是不会参与伽马校正的。
然而,Unity的线性空间并不是所有平台都支持的,例如,移动平台就无法使用线性空间。此时,我们就需要自己在shader中进行伽马校正。对非线性输入纹理的校正代码通常如下: 在这里插入图片描述

在最后输出前,对输出像素值的校正代码通常如下面这样: 在这里插入图片描述

但是,手工对输出像素进行伽马校正会在使用混合时出现问题。这是因为,校正会导致写入颜色缓冲内的颜色是非线性的,这样混合就发生在非线性空间中。一种解决方法是,在中间计算时不要对输出颜色值进行伽马校正,但在最后需要进行一个屏幕后处理操作来对最后的输出进行伽马校正,也就是说我们需要保证伽马校正发生在渲染的最后一步中,但这可能会造成一定的性能损耗。

什么是HDR

在使用基于物理的渲染时,我们经常会听到一个名词就是HDR。HDR是High Dynamic Range的缩写,即高动态范围,与之相对的是低动态范围(Low Dynamic Range, LDR)。那么这个动态范围是指什么呢?通俗来讲,动态范围指的就是最高的和最低的亮度值之间的比值。在真实世界中,一个场景中最亮和最暗区域的范围可以非常大,例如,太阳发出的光可能要比场景中某个影子上的点的亮度要高出几万倍,这些范围远远超过图像或显示器能够显示的范围。通常在显示设备使用的颜色缓冲中每个通道的精度为8位,意味着我们只能用这256种不同的亮度来表示真实世界中所有的亮度,因此,在这个过程中一定会存在一定的精度损失。早期的拍摄设备利用人眼的特点,使用了伽马曲线来对捕捉到的图像进行编码,尽可能充分地利用这些有限的存储空间,这点我们已经前面解释过了。然而,HDR的出现给我们带来了新的希望,HDR使用远远高于8位的精度(如32位)来记录亮度信息,使得我们可以表示超过0~1内的亮度值,从而可以更加精确地反映真实的光照环境。尽管我们最后还是需要把信息转换到显示设备使用的LDR内,但中间的计算却可以让我们得到更加真实可信的效果。Nvidia曾总结过使用HDR进行渲染的动机:让亮的物体可以真地非常亮,暗的物体可以真地非常暗,同时又可以看到两者之间的细节。
使用HDR来存储的图像被称为高动态范围图像(HDRI),例如,我们在前面中就是使用了一张HDRI图像来作为场景的Skybox。这样的Skybox可以更加真实地反映物体周围的环境,从而得到更加真实的反射效果。不仅如此,HDR对与光照叠加也有非常重要的作用。如果我们的场景中有很多光源或是光源强度很大,那么一个物体在经过多次光照渲染叠加后最终得到的光照亮度很可能会超过1。如果没有使用HDR,这些超过1的部分全部会截取到1,使得场景丢失了很多亮部区域的细节。但如果开启了HDR,我们就可以保留这些超过范围的光照结果,尽管最后我们仍然需要把它们转换到LDR进行显示,但我们可以使用色调映射(tonemapping)技术来控制这个转换的过程,从而允许我们最大限度地保留需要的亮度细节。
HDR的使用可以允许我们在屏幕后处理中拥有更多的控制权。例如,我们常常同时使用HDR和Bloom效果。我们前面解释了Bloom特效的实现原理,Bloom效果需要检测屏幕中亮度大于某个阈值的像素,把它们提取出来后进行模糊,再叠加到原图像中。但是,如果不使用HDR的话,我们只能使用小于1的阈值来提取需要的像素,但很多时候我们实际上是需要提取那些非常亮的区域,例如车窗上对太阳的强烈反光。由于没有使用HDR,这些值实际上很可能和街上一些颜色偏白的区域几乎一样,造成不希望的区域也会出现泛光的效果。如果我们使用HDR,这些就都可以解决了,我们只需要使用超过1的阈值来只提取那些非常亮的区域即可。
总体来说,使用HDR可以让我们不会丢失高亮度区域的颜色值,提供了更真实的光照效果,并为一些屏幕后处理提供了更多的控制能力。但HDR也有自身的缺点,首先由于使用了浮点缓冲来存储高精度图像,不仅需要更大的显存空间,渲染速度会变慢,除此之外,一些硬件并不支持HDR。而且一旦使用了HDR,我们无法在利用硬件的抗锯齿功能。事实上,在Unity中如果我们同时打开了硬件的抗锯齿(在Edit → Project Settings → Quality → Anti Aliasing中打开)和摄像机的HDR,Unity会发出警告来提示我们由于开启了抗锯齿,因此,无法使用HDR缓冲。尽管如此,我们可以使用基于屏幕后处理的抗锯齿操作来弥补这一点。
在Unity中使用HDR也非常简单,我们可以在Camera组件面板中打开HDR选项即可。此时,场景就会被渲染到一个HDR的图像缓冲中,这个缓冲的精度范围可以远远超过0~1。最后,我们可以再使用一个色调映射的屏幕后处理脚本来把HDR图像转换到LDR图像进行显示。我们可以在Unity官方手册中的高动态范围渲染一节(http://docs.unity3d.com/Manual/HDR.html)。

PBS适合什么样的游戏

在把PBS引入当前的游戏项目之前,我们需要权衡一下它的优缺点。需要再次提醒的是,PBS并不意味着游戏画面需要追求和照片一样真实的效果。事实上,很多游戏都不需要刻意去追求与照片一样的真实感,玩家眼中的真实感大多也并不是如此。PBS的优点在于,我们只需要一个万能的shader就可以渲染相当一大部分类型的材质,而不是使用传统的做法为每种材质写一个特定的shader。同时,PBS可以保证在各种光照条件下,材质都可以自然地和光源进行交互,而不需要我们反复地调整材质参数。
然而,在使用PBS时我们也需要考虑到它带来的代价。如上面提到的,PBS往往需要更复杂的光照配合,例如大量使用光照探针和反射探针等。而且PBS也需要开启HDR以及一些必不可少的屏幕特效,例如抗锯齿、Bloom和色调映射,如果这些屏幕特效对当前游戏来说需要消耗过多的性能,那么PBS就不适合当前的游戏,我们应该使用传统的shader来渲染游戏。使用PBS对美工人员来说同样是个挑战。美术资源的制作过程和使用传统的shader有很大不同,普通的法线纹理+高光反射纹理的组合不再适用,我们需要创建更细腻复杂的纹理集,包括金属值纹理、高光反射纹理、粗糙度纹理、遮挡纹理,有些还需要使用额外的细节纹理来给材质添加更多的细节表面。除了使用图片扫描的传统辅助方法外,这些纹理的制作通常还需要更专业的工具来绘制,例如Allegorithmic Substance Painter和Quixel Suite。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值