您的位置:首页 > 移动开发 > Unity3D

【猫猫的Unity Shader之旅】之光照系统初探

2017-09-10 20:22 357 查看
   两年没写博客了,现在大家普遍都用上Unity5以上的版本了,所以猫猫准备使用最新的Unity版本继续自己的Shader之旅。有人问猫猫这么长时间都干嘛去了,我只能说,当然是把妹去了…

  Unity的光照系统很复杂,一方面是Unity对渲染封装的比较严实,另一方面是官方给出的资料比较少。Unity似乎不想让我们了解渲染的更多细节,而是希望我们只要会用相对简单的Surface Shader进行“填空”就好了。然而我们必须认识到,如果只会Surface Shader,那么我们只能停留在萌新的阶段,想要“脱白”,就得勇敢滴打破Unity为了我们提供的这种“便利”。

渲染路径

  说到Unity的光照系统,渲染路径一定是一个绕不开的话题,这个概念也是一个当之无愧的“新人杀手”,第一次看到这个词总会感到一头雾水。要弄明白什么是渲染路径,就要先对光照在渲染过程中的作用方式有个简单的认识。我们都知道,光照是由场景中的Light组件最先定义的,也就是说,光照信息最初诞生的位置是在CPU,而我们的Shader是运行在GPU的,这就涉及到GPU怎么去使用这些光照信息,比如在使用前向渲染路径(Forward Rendering Path)的时候,引擎就会对逐光源调用Shader的不同Pass的方式来实现光照,我们只需要知道在哪个Pass中处理哪种光源就可以了。如果是顶点照明渲染路径(VertexLit Render Path),我们只需要一个Pass就可以了,所有的光照都在这一个里面处理。如果是延迟渲染路径(Deferred Rendering Path),就是一种更复杂的方式了。

  综上所述,渲染路径其实是一种GPU级别的概念,如果熟悉OpenGL这类比较底层的图形接口就会发现并没有定义渲染路径这种东西,所以说渲染路径是一种相对高级的概念。对于Unity来说,渲染路径最终是由引擎底层和我们所写的Shader来共同实现,不幸的是,因为引擎已经先实现了一部分,我们写Shader也必须按照它的规矩来(〒︿〒)

  本文主要探讨前向渲染路径下的光照。

前向渲染路径

  前向渲染路径的原理就是将每个光源进行分配,分配到不同的Pass(通过Tags的LightMode定义为ForwardBase还是ForwardAdd)里进行光照计算。首先,Unity会依据光源的重要程度(它自己的一套规则)将光源分成三种:逐像素处理的光源(简称A光源)、逐顶点处理光源(简称B光源)、使用SH(球谐函数)光源(简称C光源),这里说的逐顶点还是逐像素只是一个建议性质的称呼,和我们最终在Vertex Shader还是在Fragment Shader中实现光照计算并没有关系,所以为了防止混淆我们就直接用简称了。

  Unity具体分配光源的规则是这样的:

  1. 场景中最亮的平行光一定是A光源。

  2. Quality Setting中的Pixel Light Count遵循多不退少不补,实际数量可以超出(有多个光源设置为Important)也可以不足(场景中没那么多非“Not Important”光源)。

  3. 如果光源被设置成Important或者Not Important,就遵循他们的意愿,即设置为Important的一定是A光源,设置为Not Important一定是B光源或者C光源。

  4. 设置为Auto的光源会根据它们的光照强度和离物体远近等动态分配,此时会尊重Pixel Light Count的设置,终于给点面子啦(╯’ - ‘)╯ ┻━┻

  5. B光源最大数量4个,超出的一律按C光源处理。

  以上是猫猫自己根据官方文档给出的规则的理解,大家可以在这里参考官方文档。

  搞清楚了光源的类型,下一步就是决定光源在哪里处理。规则如下:

  1.Tags的LightMode设置成ForwardBase的Pass处理场景中最亮的平行光和所有B、C光源。该Pass只会渲染一次,我们需要在这一次渲染中全部处理这些光源,当然也可以选择不理它们(╯’ - ‘)╯ ┻━┻

  2.Tags的LightMode设置成ForwardAdd的Pass处理场景中其他A光源,每个Pass处理一个光源,引擎会循环使用Pass处理。

  写到这里事情就比较明朗了,如果我们要在前向渲染路径下处理所有光源,需要两种Pass,一种是LightMode标记为ForwardBase的Pass,通过一次渲染处理场景中最亮的平行光和B、C类光源,另一种是LightMode设置为ForwardAdd的Pass,通过多次渲染每次处理一个A光源,然后通过Blend对这两种渲染得到的结果进行混合,一般是Blend One One。那么我们在Pass中如何访问这些光源的信息呢,答案是通过内置变量。

  不同的Pass除了要设置不同的LightMode之外,还需要在CGPROGRAM和ENDCG之间分别加入#pragma multi_compile_fwdbase和#pragma multi_compile_fwdadd,因为只有这样才能才能得到正确的光源信息。在ForwardBase的Pass中,我们需要访问最亮的平行光的颜色,可以使用_LightColor0,方向可以用_WorldSpaceLightPos0,而对于B类光源,可以unity_LightColor、unity_4LightAtten0等变量访问到对应的光源信息,具体可以参考这里。对于C类光源,则需要使用UnityCG.cginc里面定义的ShadeSH9等方法获取。

  在ForwardAdd的Pass中,我们需要处理所有的A光源。由于这些光源的类型不确定,我们需要通过_WorldSpaceLightPos0.w来区分,如果值为0,表示光源为平行光,此时_WorldSpaceLightPos0.xyz表示光源的方向,如果值为1,表示为类型(点光源、聚光灯)等,此时_WorldSpaceLightPos0.xyz表示光源位置。

结束语

  可以看到用Vertex&Fragment Shader处理光照非常麻烦,所以如果要写的Shader需要处理光照,最好的方式还是使用Surface Shader,这是Unity好的地方,让我们能够用一种很简单的方式让我们能够处理复杂的东西,但是对于学习来说,过度的包装又让我们感到迷茫,看不清本质。如果希望弄清楚这背后的知识,去学习一下底层的图形接口应该会觉得更纯粹一些。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unity 光照