Unity Shader球谐光照解析
发表于2018-04-23
昨天翻 Unit Europe 2017 发现一篇很有意思的演讲,作者因为游戏的特殊性,彻底摒弃了 Unity 原本的烘培系统,通过只烘培 Light Probe 的形式自己读取数据建立全局光照,文章提到了 GDC 的一篇讲 Light Probe 插值的演讲,过去翻了翻,果然,LightProbe里存储的是烘培后用球谐函数编码的全局光照,正好趁机会把球谐光照这块梳理下:
理论出自02年SIG的一篇论文,后面有人做了详细的笔记,国内也有博主翻译了一部分,不过并不详尽,因为大部分底层计算工作 Unity 已经帮我们做好了并提供了接口,因此并不需要我们自己实现,这里主要讲一下我自己认为比较核心的几个点的理解:
1、蒙特卡洛积分:
将计算机尤其是GPU上非常难以计算的积分简化为了加法,这是球谐光照的前提
2、投影:
球谐光照的实质就是将复杂的光照信号投影到基函数上存储,然后在使用的时候再将基函数上的数据加起来重建光照信号
3、伴随勒让德多项式
想比如正弦信号,伴随勒让德多项式作为基函数不仅是正交的,而且是归一化的, 这意味着其具有旋转不变性,适用于动态物体
Unity 使用了三阶的伴随勒让德多项式作为基函数,因为Unity 主要用来存储全局光等低频信号,其在欧拉坐标系下如下所示:
以法线方向作为 x,y,z 其在球面上如图所示:
Unity通过烘培时的光线追踪计算出其光照原始信号,然后投影到基函数并存储其系数,我们在Shader中可通过 ShadeSH9 函数获取重建信号,ShadeSH9 实现在 UnityCG.cginc 文件中,具体代码如下:
// normal should be normalized, w=1.0 half3 SHEvalLinearL0L1 (half4 normal) { half3 x; // Linear (L1) + constant (L0) polynomial terms x.r = dot(unity_SHAr,normal); x.g = dot(unity_SHAg,normal); x.b = dot(unity_SHAb,normal); return x; } // normal should be normalized, w=1.0 half3 SHEvalLinearL2 (half4 normal) { half3 x1, x2; // 4 of the quadratic (L2) polynomials half4 vB = normal.xyzz * normal.yzzx; x1.r = dot(unity_SHBr,vB); x1.g = dot(unity_SHBg,vB); x1.b = dot(unity_SHBb,vB); // Final (5th) quadratic (L2) polynomial half vC = normal.x * normal.x - normal.y * normal.y; x2 = unity_SHC.rgb * vC; return x1 + x2; } // normal should be normalized, w=1.0 // output in active color space half3 ShadeSH9 (half4 normal) { // Linear + constant polynomial terms half3 res = SHEvalLinearL0L1(normal); // Quadratic polynomials res += SHEvalLinearL2(normal); if (IsGammaSpace()) res = LinearToGammaSpace(res); return res; }
三阶的基函数系数分别用了两个子函数来读取,其中
// SH lighting environment half4 unity_SHAr; half4 unity_SHAg; half4 unity_SHAb; half4 unity_SHBr; half4 unity_SHBg; half4 unity_SHBb; half4 unity_SHC;
是 UnityShaderVariables.cginc 中的内置变量,用来存放存储的系数,在实际使用中,我们直接调用 ShadeSH9,配合LightProbe 即可读取到 precompute bake 生成的自发光信息
来自:https://blog.csdn.net/notmz/article/details/78339913