| 
 http://forum.china.unity3d.com/thread-25738-1-10.html 
 
上一篇对着色器系统的工作原理做了介绍,现在我们将继续深入,将目光聚焦在标准着色器的光照函数。 
 
重新回到Standard.shader,这次在UnityStandardCoreForward.shader中,我们将选择另一个“不简单”的那个分支。它将我们引向UnityStandardCore.shader,而我们感兴趣的是fragForwardBaseInternal函数。 
 
[C#] 纯文本查看 复制代码 
half4
 fragForwardBaseInternal (VertexOutputForwardBase i)   
{ 
    FRAGMENT_SETUP(s) 
#if
 UNITY_OPTIMIZE_TEXCUBELOD 
    s.reflUVW      
 = i.reflUVW; 
#endif 
  
    UnityLight
 mainLight = MainLight (s.normalWorld); 
    half
 atten = SHADOW_ATTENUATION(i); 
  
  
    half
 occlusion = Occlusion(i.tex.xy); 
    UnityGI
 gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight); 
  
    half4
 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect); 
    c.rgb
 += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi); 
    c.rgb
 += Emission(i.tex.xy); 
  
    UNITY_APPLY_FOG(i.fogCoord,
 c.rgb); 
    return
OutputForward (c, s.alpha); 
} 
 
 | 
 
 
 
 
 
 
 
拿来做参考的简单版本: 
 
[C#] 纯文本查看 复制代码 
half3
 c = BRDF3_Indirect(s.diffColor, s.specColor, gi.indirect, PerVertexGrazingTerm(i, s), PerVertexFresnelTerm(i)); 
 
    c
 += BRDF3DirectSimple(s.diffColor, s.specColor, s.oneMinusRoughness, rl) * attenuatedLightColor; 
    c
 += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi); 
 
 | 
 
 
 
 
 
 
 
与上一节中的版本不同,最终的颜色由对UNITY_BRDF_PBS、UNITY_BRDF_GI和Emission的调用结果相加得出。 
 
Emission与简单版本中的相同。UNITY_BRDF_PBS和UNITY_BRDF_GI是包含文件中定义的函数别名。在下面这些包含文件中进行查找: 
 
[C#] 纯文本查看 复制代码 
#include
 "UnityCG.cginc" 
#include
 "UnityShaderVariables.cginc" 
#include
 "UnityInstancing.cginc" 
#include
 "UnityStandardConfig.cginc" 
#include
 "UnityStandardInput.cginc" 
#include
 "UnityPBSLighting.cginc" 
#include
 "UnityStandardUtils.cginc" 
#include
 "UnityStandardBRDF.cginc" 
  
#include
 "AutoLight.cginc" 
 
 | 
 
 
 
 
 
 
 
UnityStandardBRDF和UnityPBSLighting看起来最像,所以先查看它们。它们就在UnityPBSLighting.cginc中,不同的着色器目标会选择不同的函数。 
 
选择BRDF1_Unity_PBS,它就在UnityStandardBRDF.cginc中,它看起来是最逼真的可用BRDF,而BRDF3_Unity_PBS则是消耗最低的版本。 
 
如你所见,这是个大函数,因此跳过一些与优化相关的细节,依次逐块的进行讲解,首先从这个非常有用的注释开始: 
 
 
 
注释给出了使用的公式,以及引用与作用。NDF(法线分布函数)有多个选择,但这里仅介绍GGX,因为我觉得它更好。 
 
下面对注释中的公式进行简单的介绍: 
- 
kD: 漫反射率
 - 
pi: π常量
 - 
kS: 镜面反射率
 - 
D: 法线分布
 - 
V: 几何可见度系数
 - 
F: 菲涅尔反射率
 
  
 
 
自定义光照函数: 
[C#] 纯文本查看 复制代码 
half4
 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness, 
 
    half3
 normal, half3 viewDir, 
    UnityLight
 light, UnityIndirect gi) 
{ 
    half
 roughness = 1-oneMinusRoughness; 
 
 | 
 
 
 
 
 
 
 
将光滑度转换为粗糙度。 
 
[C#] 纯文本查看 复制代码 
half3
 halfDir = Unity_SafeNormalize (light.dir + viewDir); 
 
 | 
 
 
 
 
 
 
 
half向量。 
 
正确处理NdotV(查看文件中的注释): 
 
[C#] 纯文本查看 复制代码 
half
 nl = DotClamped(normal, light.dir); 
  
half
 nh = BlinnTerm (normal, halfDir); 
half
 nv = DotClamped(normal, viewDir); 
  
half
 lv = DotClamped (light.dir, viewDir); 
half
 lh = DotClamped (light.dir, halfDir); 
 
 | 
 
 
 
 
 
 
 
计算 V 和 D: 
 
[C#] 纯文本查看 复制代码 
half
 V = SmithJointGGXVisibilityTerm (nl, nv, roughness); 
   half
 D = GGXTerm (nh, roughness); 
 
 | 
 
 
 
 
 
 
 
根据Disney BRDF,计算漫反射项,以及镜面反射系数: 
 
[C#] 纯文本查看 复制代码 
half
 disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5); 
   half
 specularTerm = (V * D) * (UNITY_PI/4);  
    
   specularTerm
 = max(0, specularTerm * nl); 
   half
 diffuseTerm = disneyDiffuse * nl; 
  
    
   half
 realRoughness = roughness*roughness;        
   half
 surfaceReduction = 1.0 / (realRoughness*realRoughness + 1.0);           
  
   half
 grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity)); 
 
 | 
 
 
 
 
 
 
 
将所有的加总,包括全局光照贡献: 
 
[C#] 纯文本查看 复制代码 
  half3
 color =    diffColor * (gi.diffuse + light.color * diffuseTerm) 
                    +
 specularTerm * light.color * FresnelTerm (specColor, lh) 
                    +
 surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv); 
  
    return
half4(color, 1); 
} 
 
 | 
 
 
 
 
 
 
 
以上就是光照函数的全部。下面来深入介绍全局光照对最终结果的贡献。 
 
本节我们将介绍全局光照贡献的计算方式。过程有些麻烦,因为进行关键计算的代码隐匿在质量选择层的层层定义之后。 
 
所以让我们查看下所有与全局光照有关的函数和结构体,它们就位于我们前面三节提及的代码中。 
 
在UnityStandardCore.cginc中, fragForwardBaseInternal: 
 
[C#] 纯文本查看 复制代码 
UnityGI
 gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);  
 
half4
 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect); 
 
    c.rgb
 += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi); 
 
 | 
 
 
 
 
 
 
 
在我们的片段前向基本函数中,FragmentGI被用于计算全局光照数据:“gi”,它被传递给UNITY_BRDF_PBS 和UNITY_BRDF_GI (它们的定义分别对应着不同的质量级别)。 
 
在UnityStandardBRDF.cginc中, BRDF1_Unity_PBS: 
 
[C#] 纯文本查看 复制代码 
half4
 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness, 
 
    half3
 normal, half3 viewDir, 
    UnityLight
 light, UnityIndirect gi) 
{ 
[...] 
    half3
 color =    diffColor * (gi.diffuse + light.color * diffuseTerm) 
                    +
 specularTerm * light.color * FresnelTerm (specColor, lh) 
                    +
 surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv); 
    return
half4(color, 1); 
} 
 
 | 
 
 
 
 
 
 
 
这是UNITY_BRDF_PBS部分,它接受gi,用它来计算着色像素的颜色。 
 
以下两个定义至少需要定义一个: 
 
- 
LIGHTMAP_ON
 - 
DYNAMICLIGHTMAP_ON
 
  
 
 
还有一堆额外的定义,用来控制代码的跳转,或决定函数的选择: 
 
- 
DIRLIGHTMAP_SEPARATE
 - 
DIRLIGHTMAP_COMBINED
 - 
UNITY_BRDF_PBS_LIGHTMAP_INDIRECT
 - 
UNITY_BRDF_GI
 - 
UNITY_SHOULD_SAMPLE_SH
 - 
UNITY_SPECCUBE_BLENDING
 - 
UNITY_SPECCUBE_BOX_PROJECTION
 - 
_GLOSSYREFLECTIONS_OFF
 - 
UNITY_SPECCUBE_BOX_PROJECTION
 
  
 
 
全局光照数据的流转基本是这样的,从基本结构体流向与定义相关的函数: 
 
- 
结构体UnityGI (在UnityLightingCommon.cginc中) 保存着多个UnityLight,取决于光照贴图的类型
 - 
结构体UnityGIInput (在UnityLightingCommon.cginc中) 保存着计算GI所需的其他不同信息,被用于许多函数中
 - 
函数UNITY_BRDF_GI (在UnityPBSLighting.cginc中) 在fragForwardBaseInternal 中用于计算对BRDF的间接贡献(通过调用BRDF_Unity_Indirect)
 - 
函数BRDF_Unity_Indirect(在UnityPBSLighting.cginc中)将UNITY_BRDF_PBS_LIGHTMAP_INDIRECT 的结果与传入的colour相加
 - 
函数UNITY_BRDF_PBS_LIGHTMAP_INDIRECT (在UnityPBSLighting.cginc中) 被定义为BRDF2_Unity_PBS (但一条注释说也可以使用BRDF1_Unity_PBS ,以获得更佳质量)
 - 
函数BRDF2_Unity_PBS 或BRDF1_Unity_PBS,我们在前面一节中见过。这里用于计算间接贡献
 - 
函数FragmentGI (在UnityStandardCore.cginc中) 填充必要的数据,包括来自反射探针的数据,然后传递给UnityGlobalIllumination
 - 
函数UnityGlobalIllumination:(4个版本,不同的签名)传递数据给UnityGI_Base 和UnityGI_IndirectSpecular
 - 
函数UnityGI_Base(在UnityGlobalIllumination.cginc中)对光照贴图进行采样和解码,混合实时衰减和应用遮蔽
 - 
函数UnityGI_IndirectSpecular(在UnityGlobalIllumination.cginc中),计算反射,对盒型投影进行矫正(如果已激活),应用遮蔽
 
  
 
 
对于了解全貌同样有用的东西: 
 
- 
结构体UnityIndirect(在UnityLightingCommon.cginc中) 仅包含一个漫反射和一个镜面反射颜色
 - 
结构体UnityLight(在UnityLightingCommon.cginc中)保存光源的颜色、方向和NdotL
 - 
纹理立方体unity_SpecCube0 和unity_SpecCube1: 反射探针
 - 
结构体Unity_GlossyEnvironmentData: 保存粗糙度和反射UV
 - 
函数ResetUnityGI: 清空一个UnityGI结构体
 - 
函数ResetUnityLight: 清空一个UnityLight 结构体
 - 
函数ShadeSHPerPixel: 对每个像素进行Spherical Harmonics采样
 
  
 
 
这些知识应该已足以让你在修改标准着色器时,不会意外的将全局光照玩坏。 |