zoukankan      html  css  js  c++  java
  • Unity 5着色器系统代码介绍(下)

    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则是消耗最低的版本。

    如你所见,这是个大函数,因此跳过一些与优化相关的细节,依次逐块的进行讲解,首先从这个非常有用的注释开始:

    [C#] 纯文本查看 复制代码
    ?
    // Main Physically Based BRDF
    // Derived from Disney work and based on Torrance-Sparrow micro-facet model
    //
    //   BRDF = kD / pi + kS * (D * V * F) / 4
    //   I = BRDF * NdotL
    //
    // * NDF (depending on UNITY_BRDF_GGX):
    //  a) Normalized BlinnPhong
    //  b) GGX
    // * Smith for Visiblity term
    // * Schlick approximation for Fresnel


    注释给出了使用的公式,以及引用与作用。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); // Torrance-Sparrow model, Fresnel is applied later (for optimization reasons)
       //HACK (see file for more comments)
       specularTerm = max(0, specularTerm * nl);
       half diffuseTerm = disneyDiffuse * nl;
     
       // surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(realRoughness^2+1)
       half realRoughness = roughness*roughness;       // need to square perceptual roughness
       half surfaceReduction = 1.0 / (realRoughness*realRoughness + 1.0);          // fade in [0.5;1]
     
       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采样


    这些知识应该已足以让你在修改标准着色器时,不会意外的将全局光照玩坏。
  • 相关阅读:
    《程序员你伤不起》读书总结
    03SpringBoot用JdbcTemplates访问Mysql
    02Spring Boot配置文件详解
    01构建第一个SpringBoot工程
    java基础-04泛型
    java集合-HashSet源码解析
    java集合-HashMap源码解析
    java基础-03基本语法
    java基础-02数据类型
    java基础-01基本概念
  • 原文地址:https://www.cnblogs.com/nafio/p/9137084.html
Copyright © 2011-2022 走看看