本文对unity2018版本的默认渲染管线的环境光照实现做一个简单的梳理。帮助读者对其中的设计要素有一个整体的正确了解。这对于正确使用渲染相关的各种功能和解决实际工程中的问题是十分重要的。

首先需要简单回顾一下PBR相关的重要知识点:

  • specular来自于物体表面的反射
  • diffuse来自于光在物体表面“附近”的散射
  • diffuse散射分布非常“散”,一般简化为在半球上均匀分布
  • 金属度会影响反射和散射的光的比例
  • 用粗糙度控制反射光在不同方向上的分布情况
  • 反射和散射的比例还跟入射光的角度有光(菲涅尔系数)




Unity SLua原理 unity specular_环境光


diffuse和specular

Unity SLua原理 unity specular_插值_02

不同粗糙度表面的specular


由于diffuse和specular产生的机制和实际效果不同,因此在渲染算法的实现中往往分别处理,unity默认管线中也不例外。

unity中的环境光照

环境光照可以分为两种类型,在unity中称为Environment Lighting和Environment Reflections,分别对应PBR中的diffuse和specular分量的光照。

为了避免歧义,下文中我将用“diffuse环境光“和“环境反射”来指代这两种环境光照。

全局环境光照

在Lighting面板中可以设置当前场景的“diffuse环境光“和“环境反射”如何计算。diffuse环境光有三种类型,都很好理解,具体不展开。

需要注意的是,不管使用哪种类型,都会被转换成一个3阶的球谐光来表示。这是因为diffuse环境光非常低频,使用球谐函数,无论是内存占用,插值,还是shader中的计算,都是非常优化的。


Unity SLua原理 unity specular_插值_03


环境反射有两种类型,使用场景中的天空盒或者单独指定一个cubemap。注意这里会经prefilter生成一张专门的cubemap,通过参数可以指定这张cubemap的大小以及是否压缩。后文还会对此进行解释。


Unity SLua原理 unity specular_插值_04


简单总结一下就是diffuse环境光使用球谐光,环境光照使用prefilterd cubemap

为了方便验证效果,如下图将diffuse环境光设成上蓝下红的天地光,环境反射用默认的cubemap


Unity SLua原理 unity specular_环境光_05


不同的金属度和光滑度下的材质效果如下图(固有色为白色)。可以看到,金属度影响环境光照中diffuse环境光和环境反射的比例。

光滑度会影响环境反射的锐利度,或者说是清晰度。这在金属度高时比较明显。diffuse环境光本身不受光滑度的影响,但是即使金属度为0,

光滑度高时也会看到一些环境反射的效果。这是因为物理世界中,光在非金属(绝缘体)物质界面处都会有一定比例被反射,而金属表面反射会更强。


Unity SLua原理 unity specular_环境光_06


物体上的diffuse环境光

以上是一个场景中的整体环境光照的设置。具体到场景中的每个物体如何接受光照,还需要在场景中摆放光照探针和反射探针,并且在物体的Mesh Renderer组件中进行具体设置。

首先,静态物体和动态物体的光照方式是不同的。静态物体会被自动勾选Lightmap Static,这时物体的diffuse环境光仅来自于lightmap,而不会使用球谐光。


Unity SLua原理 unity specular_Unity SLua原理_07


而动态物体没有lightmap,其diffuse环境光来自于球谐光。具体如何使用球谐光,在mesh renderer组件中有四种模式:

  • off。不使用场景中的light probe group,这时会使用场景中的diffuse环境光
  • Blend Probes。最常用的方式,根据场景中附近的light probe(也是球谐光),在物体位置处插值得到该物体所用的球谐光作为diffuse环境光照
  • Use Proxy Volume。使用一个三维网格来采样场景中的light probe(采样结果保存到3D纹理中,再在shader中采样3D纹理)。这样可以突破一个物体只一个球谐光的限制,对于大型物体光照信息更丰富。但是这个功能在移动平台上不支持。
  • Custom Provided。自己指定球谐光信息。


Unity SLua原理 unity specular_插值_08


下图演示了小球使用Blend Probes方式在场景中的light probe中插值出的效果。(三个光源均为静态光)


Unity SLua原理 unity specular_环境光_09

四面体的顶点上是四个light probe

Unity SLua原理 unity specular_插值_10


顺便提一下,光照探针的摆放是很有讲究的。光照环境变化大的地方(比如墙壁的内外侧)需要保证足够数量的光照探针,否则会产生漏光的问题。


Unity SLua原理 unity specular_使用场景_11

light probe摆放不充足导致的漏光效果

Unity SLua原理 unity specular_环境光_12

墙壁附近补充了light probe的效果

物体上的环境反射

diffuse环境光区分静态物体和动态物体,统一使用环境反射,不区分静态物体和动态物体。mesh renderer上可以设置物体使用环境反射的计算方式,同样有四种模式:

  • Off。不使用场景中的reflection probe,这时会使用场景设置中的Environment Reflections
  • Blend Probes。在reflection probe覆盖的范围内插值,如果当前位置没有reflection probe覆盖,则会跳变到场景的Environment Reflections。适用于室内场景
  • Blend Probes And Skybox。在reflection probe覆盖的范围内以及和场景的Environment Reflections间差值。适用于室外场景
  • Simple。使用最近的(以及权重最高的)reflection probe,不插值。


Unity SLua原理 unity specular_插值_13


需要注意的是,Blend Probes是否会生效还受到Graphics选项中的Tier设置的影响(毕竟插值需要多一次cubemap采样)


Unity SLua原理 unity specular_Unity SLua原理_14


还有一个使用细节,场景中的物体想要烘焙到reflection probe上需要勾选Reflecton Probe Static。


Unity SLua原理 unity specular_Unity SLua原理_15


关于prefiltered cubemap

reflection probe可以在场景某处烘焙一张cubemap,也可以如下图这样指定一张cubemap。


Unity SLua原理 unity specular_环境光_16


不过,不专注渲染的同学可能不清楚,用作环境反射的cubemap和普通的cubemap是不同的。为了模拟不同粗糙度表面的环境光照,同时兼顾性能开销,在引擎中往往会将光照环境进行预积分。不同粗糙度的表面需要的积分函数的“形状”不同。如下图,越光滑的表面积分函数越“细长”,反之越“圆”。(即reflection lobe)


Unity SLua原理 unity specular_使用场景_17

不同和光滑表面的reflection lobe

于是我们选取多个不同形状的积分函数进行预积分,得到的结果都可以保存在cubemap贴图中。恰好积分函数越“圆”的结果频率越低(也就是越模糊),正好适合保存在低级别的mipmap中。

我们对比一下一个cubemap和将其用于reflection probe,经bake(会进行预积分计算)之后的结果。可以看到prefiltered cubemap明显会更模糊。


Unity SLua原理 unity specular_环境光_18

普通cubemap和用于环境反射的prefiltered cubemap

最后用一张图来总结一下本文的主要内容


Unity SLua原理 unity specular_Unity SLua原理_19