转自:https://zhuanlan.zhihu.com/p/68025039
间接光
间接光的实现与ibl(基于图像的渲染)和SH(球谐光照)这两个名词分不开。基于图像的渲染已经是很大的一个体系了,在这里特指基于环境贴图cubemap对表面进行渲染。球谐光照实际上就是将周围的环境光采样成几个系数,然后渲染的时候用这几个系数来对光照进行还原,这种过程可以看做是对周围环境光的简化。这两者在后面的实验中都会被用到。
间接光部分使用的也是和直接光相同的BRDF方程,不同之处在于BRDF加一加就好,而这里真的要解积分。。。总的公式如下:
间接光漫反射
拆分出的间接光漫反射公式如下:
后面那个积分看起来很吓人,想看解法的话《 Real-time Rendering》上有,我这里只谈实现。在Shader里每帧做积分显然是不现实的,普遍的做法是把采样得到的cubemap预处理成一张贴图,就像下图这样:
预处理过程如果想手写的话可以看OpenGL的实现,而作为一个成熟的引擎Unity已经帮我们把cubemap处理好存起来了。Unity里有这么一组变量:
// SH lighting environment
half4 unity_SHAr;
half4 unity_SHAg;
half4 unity_SHAb;
half4 unity_SHBr;
half4 unity_SHBg;
half4 unity_SHBb;
half4 unity_SHC;
这里存的是积分后用球谐函数编码的全局光照。即Unity做的事情为:先将环境贴图cubemap积分成模糊的全局光照贴图,再将全局光照贴图投影到球谐光照的基函数上存储,这里的七个参数即为存储的基函数的系数。Unity用的基函数叫三阶的伴随勒让德多项式,式子的参数如下所示:
unity_SHA的三个系数存储的是l=1时的参数,unity_SHB存储的是l=2时的第1,2,4个参数,unity_SHC单独存储(m=2,l=2)时的最后一个参数。(l=0,m=0)和(l=2,m=0)的系数代表的光照数据影响太小被Unity舍弃掉了。
公式挺吓人,但实际上每个参数表示的都是球面上某一部分的光照,如图:
具体的计算代码见UnityCG.cginc,在这里直接调用其中的ShadeSH9函数。此函数传入归一化的法线,返回的即为重建的积过分的环境光照信息。
half3 ambient_contrib = ShadeSH9(float4(i.normal, 1));
float3 ambient = 0.03 * Albedo;
float3 iblDiffuse = max(half3(0, 0, 0), ambient.rgb + ambient_contrib);
float kdLast = 1;
float3 iblDiffuseResult = iblDiffuse * kdLast * Albedo;
第二行往后的代码都很容易理解。ambient是环境光影响不大,随便设个很暗的值就行。这样得到的iblDiffuse就是方程中积分部分的值,乘上kd乘上Albedo即为间接光漫反射的结果,此处的kd和上面的kd不一样需要重新计算,先设成1。注意这里和直接光部分一样没有对颜色除PI。