Unity中Shader的BRDF解析(四)

文章目录

  • 前言
  • [一、BRDF 中的 IBL](#一、BRDF 中的 IBL)
  • 二、解析一下其中的参数
    • [1、光照衰减系数 :surfaceReduction](#1、光照衰减系数 :surfaceReduction)
    • [2、GI镜面反射在不同角度下的强弱 :gi.specular * FresnelLerp (specColor, grazingTerm, nv);](#2、GI镜面反射在不同角度下的强弱 :gi.specular * FresnelLerp (specColor, grazingTerm, nv);)
    • [在BRDF中,IBL(Image Based Light)对最终效果有着重要的作用,可以模拟出反射 Cubemap 的效果,可以实现在不同环境中,不需要调节参数只需要修改 Cubemap 就达到模拟物理反射的效果。](#在BRDF中,IBL(Image Based Light)对最终效果有着重要的作用,可以模拟出反射 Cubemap 的效果,可以实现在不同环境中,不需要调节参数只需要修改 Cubemap 就达到模拟物理反射的效果。)
    • [BRDF2 和 BRDF3 只是对 BRDF1 性能上的妥协](#BRDF2 和 BRDF3 只是对 BRDF1 性能上的妥协)
  • 三、最终效果
    • [最终代码\](#最终代码)

前言

在上一篇文章中,我们解析了BRDF中的 镜面反射,这篇文章我们继续解析BRDF中的 IBL(Image Based Lighting 基于图像的光照)


一、BRDF 中的 IBL

//IBL(Image Based Lighting)基于图像的光照

//surfaceReduction : 衰减

//gi.specular : 间接光中的镜面反射

//FresnelLerp : 镜面反射在不同角度下的过度

half3 ibl = surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);


二、解析一下其中的参数

1、光照衰减系数 :surfaceReduction

// surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(roughness^2+1)

half surfaceReduction;

ifdef UNITY_COLORSPACE_GAMMA

surfaceReduction = 1.0-0.28roughness perceptualRoughness; // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1]

else

surfaceReduction = 1.0 / (roughness* roughness + 1.0); // fade \in [0.5;1]

endif

  • 当处于线性空间时,可以把光照衰减范围控制在 [0.5,1] 之间
  • 当处于Gamma空间时

为了节省性能,采用 r 在 [0,1]之间 近似的公式来简化计算:
1-0.28x^3^ as approximation for (1/(x^4^+1))^(1/2.2)^ on the domain [0;1]

Unity使用 1-0.28x^3^ 替代 (1/(x^4^+1))^(1/2.2)^

2、GI镜面反射在不同角度下的强弱 :gi.specular * FresnelLerp (specColor, grazingTerm, nv);

//GI中镜面反射的菲涅尔效果

//F0 : 视线 与 物体法线 夹角为 0° 的情况

//F90 : 视线 与 物体法线 夹角为 90° 的情况

inline half3 FresnelLerp1 (half3 F0, half3 F90, half cosA)

{

half t = Pow5 (1 - cosA); // ala Schlick interpoliation

return lerp (F0, F90, t);

}

  • F~0~ : 视线 与 物体法线 夹角为 0° 的情况
  • F~90~ : 视线 与 物体法线 夹角为 90° 的情况
  • NdotV : 视线方向单位向量 dot 法线单位向量 作为反射过度的重要参数

在BRDF中,IBL(Image Based Light)对最终效果有着重要的作用,可以模拟出反射 Cubemap 的效果,可以实现在不同环境中,不需要调节参数只需要修改 Cubemap 就达到模拟物理反射的效果。

BRDF2 和 BRDF3 只是对 BRDF1 性能上的妥协


三、最终效果

最终代码\

  • .cginc文件

    #ifndef MYPHYSICALLYBASERENDERING_INCLUDE
    #define MYPHYSICALLYBASERENDERING_INCLUDE

      //Standard的漫反射和镜面反射计算↓
    
      //F函数的计算:(菲涅尔效果)
      inline half3 FresnelTerm1 (half3 F0, half cosA)
      {
          half t = Pow5 (1 - cosA);   // ala Schlick interpoliation
          return F0 + (1-F0) * t;
      }
      //GI中镜面反射的菲涅尔效果
      //F0 : 视线 与 物体法线 夹角为 0° 的情况
      //F90 : 视线 与 物体法线 夹角为 90° 的情况
      inline half3 FresnelLerp1 (half3 F0, half3 F90, half cosA)
      {
          half t = Pow5 (1 - cosA);   // ala Schlick interpoliation
          return lerp (F0, F90, t);
      }
    
      //V函数的计算:
      // Ref: http://jcgt.org/published/0003/02/03/paper.pdf
      inline float SmithJointGGXVisibilityTerm1 (float NdotL, float NdotV, float roughness)
      {
          #if 0
          // Original formulation:
          //  lambda_v    = (-1 + sqrt(a2 * (1 - NdotL2) / NdotL2 + 1)) * 0.5f;
          //  lambda_l    = (-1 + sqrt(a2 * (1 - NdotV2) / NdotV2 + 1)) * 0.5f;
          //  G           = 1 / (1 + lambda_v + lambda_l);
    
          // Reorder code to be more optimal
          half a          = roughness;
          half a2         = a * a;
    
          half lambdaV    = NdotL * sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
          half lambdaL    = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);
    
          // Simplify visibility term: (2.0f * NdotL * NdotV) /  ((4.0f * NdotL * NdotV) * (lambda_v + lambda_l + 1e-5f));
          return 0.5f / (lambdaV + lambdaL + 1e-5f);  // This function is not intended to be running on Mobile,
          // therefore epsilon is smaller than can be represented by half
          #else
          //上面公式的一个近似实现(简化平方根,数学上不太精确,但是效果比较接近,性能好)
          // Approximation of the above formulation (simplify the sqrt, not mathematically correct but close enough)
          float a = roughness;
          float lambdaV = NdotL * (NdotV * (1 - a) + a);
          float lambdaL = NdotV * (NdotL * (1 - a) + a);
    
          #if defined(SHADER_API_SWITCH)
          return 0.5f / (lambdaV + lambdaL + UNITY_HALF_MIN);
          #else
          return 0.5f / (lambdaV + lambdaL + 1e-5f);
          #endif
    
          #endif
      }
      //D函数的计算:
      inline float GGXTerm1 (float NdotH, float roughness)
      {
          float a2 = roughness * roughness;
          float d = (NdotH * a2 - NdotH) * NdotH + 1.0f; // 2 mad
          return UNITY_INV_PI * a2 / (d * d + 1e-7f); // This function is not intended to be running on Mobile,
          // therefore epsilon is smaller than what can be represented by half
      }
    
      //为了保证分母不为0,而使用的一种安全的归一化
      inline float3 Unity_SafeNormalize1(float3 inVec)
      {
          //normalize(v) = rsqrt(dot(v,v)) * v;
          float dp3 = max(0.001f, dot(inVec, inVec));
          return inVec * rsqrt(dp3);
      }
      //迪士尼的漫反射计算
      half DisneyDiffuse1(half NdotV, half NdotL, half LdotH, half perceptualRoughness)
      {
          half fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
          // Two schlick fresnel term
          half lightScatter   = (1 + (fd90 - 1) * Pow5(1 - NdotL));
          half viewScatter    = (1 + (fd90 - 1) * Pow5(1 - NdotV));
    
          return lightScatter * viewScatter;
      }
      // 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
      half4 BRDF1_Unity_PBS1 (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
          float3 normal, float3 viewDir,
          UnityLight light, UnityIndirect gi)
      {
          //感性的粗糙的 = 1 - smoothness
          float perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness);
          //半角向量(一般用 H 表示): H = 光线向量 + 视线向量(此处的 光线向量 和 视线向量 为单位向量,根据向量相加的四边形法则得出半角向量)
          float3 halfDir = Unity_SafeNormalize1 (float3(light.dir) + viewDir);
          
      //法线 与 视线的点积在可见像素上不应该出现负值,但是他有可能发生在 投影 与 法线 映射 时
      //所以,可以通过某些方式来修正,但是会产生额外的指令运算
      //替代方案采用abs的形式,同样可以工作只是正确性少一些    
      // NdotV should not be negative for visible pixels, but it can happen due to perspective projection and normal mapping
      // In this case normal should be modified to become valid (i.e facing camera) and not cause weird artifacts.
      // but this operation adds few ALU and users may not want it. Alternative is to simply take the abs of NdotV (less correct but works too).
      // Following define allow to control this. Set it to 0 if ALU is critical on your platform.
      // This correction is interesting for GGX with SmithJoint visibility function because artifacts are more visible in this case due to highlight edge of rough surface
      // Edit: Disable this code by default for now as it is not compatible with two sided lighting used in SpeedTree.
      #define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0
    
      #if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV
          // The amount we shift the normal toward the view vector is defined by the dot product.
          half shiftAmount = dot(normal, viewDir);
          normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal;
          // A re-normalization should be applied here but as the shift is small we don't do it to save ALU.
          //normal = normalize(normal);
    
          float nv = saturate(dot(normal, viewDir)); // TODO: this saturate should no be necessary here
      #else
          half nv = abs(dot(normal, viewDir));    // This abs allow to limit artifact
      #endif
    
          //其他向量之间的点积
          float nl = saturate(dot(normal, light.dir));//法线 点积 光线
          float nh = saturate(dot(normal, halfDir));//法线 点积 半角
          half lv = saturate(dot(light.dir, viewDir));//光线 点积 视线
          half lh = saturate(dot(light.dir, halfDir));//光线 点积 半角
    
          // Diffuse term
          //迪士尼原则的漫反射
          half diffuseTerm = DisneyDiffuse1(nv, nl, lh, perceptualRoughness) * nl;
    
          // Specular term
          // HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm!
          // 理论上漫反射项中应该除以 PI,但是由于以下两个原因没有这样做
          // BUT 1) that will make shader look significantly darker than Legacy ones
          //原因一:这样会导致最终效果偏暗
          // and 2) on engine side "Non-important" lights have to be divided by Pi too in cases when they are injected into ambient SH
          //原因二:当引擎光照为 不重要光照 时,进行球谐光照计算,会再除以一个 PI。所以,在Unity计算迪士尼漫反射时,不除以PI
    
          //声明一个学术上的粗糙度 = perceptualRoughness * perceptualRoughness
          float roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
    
          //GGX模型拥有比较好的效果,默认使用这个模型(并且,UNITY_BRDF_GGX在定义时,默认为 1)
      #if UNITY_BRDF_GGX
          // GGX with roughtness to 0 would mean no specular at all, using max(roughness, 0.002) here to match HDrenderloop roughtness remapping.
          //使用max来限定 roughtness 最小等于0 的原因:当 roughtness 为0时,结果会直接为0,导致效果丢失
          roughness = max(roughness, 0.002);
          float V = SmithJointGGXVisibilityTerm1 (nl, nv, roughness);
          float D = GGXTerm1 (nh, roughness);
      #else
          // Legacy
          half V = SmithBeckmannVisibilityTerm1 (nl, nv, roughness);
          half D = NDFBlinnPhongNormalizedTerm1 (nh, PerceptualRoughnessToSpecPower(perceptualRoughness));
      #endif
    
          //镜面反射中的DV项的计算
          //最后乘以PI的原因是因为上面计算漫反射时,等式右边没有除以PI。
          //导致算出的结果,等效于分母中多乘了一个PI,所以需要在计算公式时,乘以一个PI,消除PI
          float specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
    
      //如果颜色空间为Gamma空间:    
      #   ifdef UNITY_COLORSPACE_GAMMA
              specularTerm = sqrt(max(1e-4h, specularTerm));
      #   endif
    
          // specularTerm * nl can be NaN on Metal in some cases, use max() to make sure it's a sane value
          specularTerm = max(0, specularTerm * nl);
    
      //材质上的镜面高光开关    
      #if defined(_SPECULARHIGHLIGHTS_OFF)
          specularTerm = 0.0;
      #endif
    
          // surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(roughness^2+1)
          half surfaceReduction;
      #   ifdef UNITY_COLORSPACE_GAMMA
          //Gamma空间
              surfaceReduction = 1.0-0.28*roughness*perceptualRoughness;      // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1]
      #   else
          //线性空间
              surfaceReduction = 1.0 / (roughness*roughness + 1.0);           // fade \in [0.5;1]
      #   endif
    
          // To provide true Lambert lighting, we need to be able to kill specular completely.
          // 当我们的 metallic = 1时,并且Albedo为纯黑色的情况,不希望有金属反射效果
          specularTerm *= any(specColor) ? 1.0 : 0.0;
    
          half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
    
          //漫反射颜色 = 贴图 * (gi漫反射 + 灯光颜色 * 迪士尼漫反射)
          half3 diffuse = diffColor * (gi.diffuse + light.color * diffuseTerm);
          
          //镜面反射 DFG / 4cos(θl)cos(θv)
          //speclarTerm : D G 函数
          //light.color : 光照颜色
          //FresnelTerm (specColor, lh) : F 函数
          half3 specular = specularTerm * light.color * FresnelTerm1 (specColor, lh);
          
          //IBL(Image Based Lighting)基于图像的光照
          //surfaceReduction : 衰减
          //gi.specular : 间接光中的镜面反射
          //FresnelLerp : 镜面反射在不同角度下的过度
          half3 ibl = surfaceReduction * gi.specular * FresnelLerp1 (specColor, grazingTerm, nv);
          
          half3 color = diffuse + specular + ibl;
          
          return half4(color, 1);
      }
    
      // Based on Minimalist CookTorrance BRDF
      // Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255
      //
      // * NDF (depending on UNITY_BRDF_GGX):
      //  a) BlinnPhong
      //  b) [Modified] GGX
      // * Modified Kelemen and Szirmay-​Kalos for Visibility term
      // * Fresnel approximated with 1/LdotH
      half4 BRDF2_Unity_PBS1 (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
          float3 normal, float3 viewDir,
          UnityLight light, UnityIndirect gi)
      {
          float3 halfDir = Unity_SafeNormalize (float3(light.dir) + viewDir);
    
          half nl = saturate(dot(normal, light.dir));
          float nh = saturate(dot(normal, halfDir));
          half nv = saturate(dot(normal, viewDir));
          float lh = saturate(dot(light.dir, halfDir));
    
          // Specular term
          half perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness);
          half roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
    
      #if UNITY_BRDF_GGX
    
          // GGX Distribution multiplied by combined approximation of Visibility and Fresnel
          // See "Optimizing PBR for Mobile" from Siggraph 2015 moving mobile graphics course
          // https://community.arm.com/events/1155
          float a = roughness;
          float a2 = a*a;
    
          float d = nh * nh * (a2 - 1.f) + 1.00001f;
      #ifdef UNITY_COLORSPACE_GAMMA
          // Tighter approximation for Gamma only rendering mode!
          // DVF = sqrt(DVF);
          // DVF = (a * sqrt(.25)) / (max(sqrt(0.1), lh)*sqrt(roughness + .5) * d);
          float specularTerm = a / (max(0.32f, lh) * (1.5f + roughness) * d);
      #else
          float specularTerm = a2 / (max(0.1f, lh*lh) * (roughness + 0.5f) * (d * d) * 4);
      #endif
    
          // on mobiles (where half actually means something) denominator have risk of overflow
          // clamp below was added specifically to "fix" that, but dx compiler (we convert bytecode to metal/gles)
          // sees that specularTerm have only non-negative terms, so it skips max(0,..) in clamp (leaving only min(100,...))
      #if defined (SHADER_API_MOBILE)
          specularTerm = specularTerm - 1e-4f;
      #endif
    
      #else
    
          // Legacy
          half specularPower = PerceptualRoughnessToSpecPower(perceptualRoughness);
          // Modified with approximate Visibility function that takes roughness into account
          // Original ((n+1)*N.H^n) / (8*Pi * L.H^3) didn't take into account roughness
          // and produced extremely bright specular at grazing angles
    
          half invV = lh * lh * smoothness + perceptualRoughness * perceptualRoughness; // approx ModifiedKelemenVisibilityTerm(lh, perceptualRoughness);
          half invF = lh;
    
          half specularTerm = ((specularPower + 1) * pow (nh, specularPower)) / (8 * invV * invF + 1e-4h);
    
      #ifdef UNITY_COLORSPACE_GAMMA
          specularTerm = sqrt(max(1e-4f, specularTerm));
      #endif
    
      #endif
    
      #if defined (SHADER_API_MOBILE)
          specularTerm = clamp(specularTerm, 0.0, 100.0); // Prevent FP16 overflow on mobiles
      #endif
      #if defined(_SPECULARHIGHLIGHTS_OFF)
          specularTerm = 0.0;
      #endif
    
          // surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(realRoughness^2+1)
    
          // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1]
          // 1-x^3*(0.6-0.08*x)   approximation for 1/(x^4+1)
      #ifdef UNITY_COLORSPACE_GAMMA
          half surfaceReduction = 0.28;
      #else
          half surfaceReduction = (0.6-0.08*perceptualRoughness);
      #endif
    
          surfaceReduction = 1.0 - roughness*perceptualRoughness*surfaceReduction;
    
          half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
          half3 color =   (diffColor + specularTerm * specColor) * light.color * nl
                          + gi.diffuse * diffColor
                          + surfaceReduction * gi.specular * FresnelLerpFast (specColor, grazingTerm, nv);
    
          return half4(color, 1);
      }
    
      sampler2D_float unity_NHxRoughness1;
      half3 BRDF3_Direct1(half3 diffColor, half3 specColor, half rlPow4, half smoothness)
      {
          half LUT_RANGE = 16.0; // must match range in NHxRoughness() function in GeneratedTextures.cpp
          // Lookup texture to save instructions
          half specular = tex2D(unity_NHxRoughness1, half2(rlPow4, SmoothnessToPerceptualRoughness(smoothness))).r * LUT_RANGE;
      #if defined(_SPECULARHIGHLIGHTS_OFF)
          specular = 0.0;
      #endif
    
          return diffColor + specular * specColor;
      }
    
      half3 BRDF3_Indirect1(half3 diffColor, half3 specColor, UnityIndirect indirect, half grazingTerm, half fresnelTerm)
      {
          half3 c = indirect.diffuse * diffColor;
          c += indirect.specular * lerp (specColor, grazingTerm, fresnelTerm);
          return c;
      }
    
      // Old school, not microfacet based Modified Normalized Blinn-Phong BRDF
      // Implementation uses Lookup texture for performance
      //
      // * Normalized BlinnPhong in RDF form
      // * Implicit Visibility term
      // * No Fresnel term
      //
      // TODO: specular is too weak in Linear rendering mode
      half4 BRDF3_Unity_PBS1 (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
          float3 normal, float3 viewDir,
          UnityLight light, UnityIndirect gi)
      {
          float3 reflDir = reflect (viewDir, normal);
    
          half nl = saturate(dot(normal, light.dir));
          half nv = saturate(dot(normal, viewDir));
    
          // Vectorize Pow4 to save instructions
          half2 rlPow4AndFresnelTerm = Pow4 (float2(dot(reflDir, light.dir), 1-nv));  // use R.L instead of N.H to save couple of instructions
          half rlPow4 = rlPow4AndFresnelTerm.x; // power exponent must match kHorizontalWarpExp in NHxRoughness() function in GeneratedTextures.cpp
          half fresnelTerm = rlPow4AndFresnelTerm.y;
    
          half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
    
          
          half3 color = BRDF3_Direct1(diffColor, specColor, rlPow4, smoothness);
          color *= light.color * nl;
          color += BRDF3_Indirect1(diffColor, specColor, gi, grazingTerm, fresnelTerm);
    
          
          return half4(color, 1);
      }
    
    
    
      // Default BRDF to use:
      //在 ProjectSetting->Graphics->TierSetting中设置
      //StandardShaderQuality = low(UNITY_PBS_USE_BRDF3)
      //StandardShaderQuality = Medium(UNITY_PBS_USE_BRDF2)
      //StandardShaderQuality = High(UNITY_PBS_USE_BRDF1)
    
      #if !defined (UNITY_BRDF_PBS1) // allow to explicitly override BRDF in custom shader
      // still add safe net for low shader models, otherwise we might end up with shaders failing to compile
      #if SHADER_TARGET < 30 || defined(SHADER_TARGET_SURFACE_ANALYSIS) // only need "something" for surface shader analysis pass; pick the cheap one
          #define UNITY_BRDF_PBS1 BRDF3_Unity_PBS1  //效果最差的BRDF
      #elif defined(UNITY_PBS_USE_BRDF3)
          #define UNITY_BRDF_PBS1 BRDF3_Unity_PBS1
      #elif defined(UNITY_PBS_USE_BRDF2)
          #define UNITY_BRDF_PBS1 BRDF2_Unity_PBS1
      #elif defined(UNITY_PBS_USE_BRDF1)
          #define UNITY_BRDF_PBS1 BRDF1_Unity_PBS1
      #else
          #error something broke in auto-choosing BRDF
      #endif
      #endif
    
      inline half OneMinusReflectivityFromMetallic1(half metallic)
      {
          // We'll need oneMinusReflectivity, so
          //   1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
          // store (1-dielectricSpec) in unity_ColorSpaceDielectricSpec.a, then
          //   1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) =
          //                  = alpha - metallic * alpha
          half oneMinusDielectricSpec = unity_ColorSpaceDielectricSpec.a;
          return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
      }
    
      inline half3 DiffuseAndSpecularFromMetallic1 (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity)
      {
          //计算镜面高光颜色
          //当metallic为0(即非金属时),返回unity_ColorSpaceDielectricSpec.rgb(0.04)
          //unity_ColorSpaceDielectricSpec.rgb表示的是绝缘体的通用反射颜色
          //迪士尼经大量测量用 0.04 来表示
          //当 metallic = 1 时(金属),返回Albedo,也就是物体本身的颜色
          specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
          oneMinusReflectivity = OneMinusReflectivityFromMetallic1(metallic);
          return albedo * oneMinusReflectivity;
      }
    
      //s : 物体表面数据信息
      //viewDir : 视线方向
      //gi : 全局光照(GI漫反射 和 GI镜面反射)
      inline half4 LightingStandard1 (SurfaceOutputStandard s, float3 viewDir, UnityGI gi)
      {
          s.Normal = normalize(s.Normal);
    
          half oneMinusReflectivity;
          //镜面高光颜色
          half3 specColor;
          s.Albedo = DiffuseAndSpecularFromMetallic1 (s.Albedo, s.Metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
    
          // shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
          // this is necessary to handle transparency in physically correct way - only diffuse component gets affected by alpha
          //当开启半透明模式时,对 Alpha 进行相关计算
          half outputAlpha;
          s.Albedo = PreMultiplyAlpha (s.Albedo, s.Alpha, oneMinusReflectivity, /*out*/ outputAlpha);
    
          //具体的BRDF计算
          //s.Albedo : 物体表面的基础颜色
          //specColor : 镜面反射颜色
          //oneMinusReflectivity : 漫反射率 = 1 - 镜面反射率
          //s.Smoothness : 物体表面的光滑度
          //s.Normal : 物体表面的法线
          //viewDir : 视线方向
          //gi.light : 直接光信息
          //gi.indirect : GI间接光信息
          half4 c = UNITY_BRDF_PBS1 (s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);
          c.a = outputAlpha;
          return c;
      }
    
    
      //Standard的GI计算↓
      half3 Unity_GlossyEnvironment1 (UNITY_ARGS_TEXCUBE(tex), half4 hdr, Unity_GlossyEnvironmentData glossIn)
      {
          half perceptualRoughness = glossIn.roughness /* perceptualRoughness */ ;
    
          // TODO: CAUTION: remap from Morten may work only with offline convolution, see impact with runtime convolution!
          // For now disabled
          #if 0
          float m = PerceptualRoughnessToRoughness(perceptualRoughness); // m is the real roughness parameter
          const float fEps = 1.192092896e-07F;        // smallest such that 1.0+FLT_EPSILON != 1.0  (+1e-4h is NOT good here. is visibly very wrong)
          float n =  (2.0/max(fEps, m*m))-2.0;        // remap to spec power. See eq. 21 in --> https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf
    
          n /= 4;                                     // remap from n_dot_h formulatino to n_dot_r. See section "Pre-convolved Cube Maps vs Path Tracers" --> https://s3.amazonaws.com/docs.knaldtech.com/knald/1.0.0/lys_power_drops.html
    
          perceptualRoughness = pow( 2/(n+2), 0.25);      // remap back to square root of real roughness (0.25 include both the sqrt root of the conversion and sqrt for going from roughness to perceptualRoughness)
          #else
          // MM: came up with a surprisingly close approximation to what the #if 0'ed out code above does.
          //r = r * (1.7 - 0.7*r)
          //由于粗糙度与反射探针的mip变化不呈现线性正比,所以需要一个公式来改变
          perceptualRoughness = perceptualRoughness*(1.7 - 0.7*perceptualRoughness);
          #endif
    
          //UNITY_SPECCUBE_LOD_STEPS = 6,表示反射探针的mip级别有 6 档。粗糙度X6得到最终得mip级别
          half mip = perceptualRoughnessToMipmapLevel(perceptualRoughness);
          half3 R = glossIn.reflUVW;
          half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, R, mip);
    
          return DecodeHDR(rgbm, hdr);
      }
    
    
    
      //GI中的镜面反射
      inline half3 UnityGI_IndirectSpecular1(UnityGIInput data, half occlusion, Unity_GlossyEnvironmentData glossIn)
      {
          half3 specular;
          //如果开启了反射探针的Box Projection
          #ifdef UNITY_SPECCUBE_BOX_PROJECTION
          // we will tweak reflUVW in glossIn directly (as we pass it to Unity_GlossyEnvironment twice for probe0 and probe1), so keep original to pass into BoxProjectedCubemapDirection
          half3 originalReflUVW = glossIn.reflUVW;
          glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[0], data.boxMin[0], data.boxMax[0]);
          #endif
    
          #ifdef _GLOSSYREFLECTIONS_OFF
          specular = unity_IndirectSpecColor.rgb;
          #else
          half3 env0 = Unity_GlossyEnvironment1 (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], glossIn);
          //如果开启了反射探针混合
          #ifdef UNITY_SPECCUBE_BLENDING
          const float kBlendFactor = 0.99999;
          float blendLerp = data.boxMin[0].w;
          UNITY_BRANCH
          if (blendLerp < kBlendFactor)
          {
              #ifdef UNITY_SPECCUBE_BOX_PROJECTION
              glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[1], data.boxMin[1], data.boxMax[1]);
              #endif
    
              half3 env1 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1,unity_SpecCube0), data.probeHDR[1], glossIn);
              specular = lerp(env1, env0, blendLerp);
          }
          else
          {
              specular = env0;
          }
          #else
          specular = env0;
          #endif
          #endif
    
          return specular * occlusion;
      }
    
    
      inline UnityGI UnityGlobalIllumination1 (UnityGIInput data, half occlusion, half3 normalWorld)
      {
          return UnityGI_Base(data, occlusion, normalWorld);
      }
      //GI计算
      inline UnityGI UnityGlobalIllumination1 (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)
      {
          //计算得出GI中的漫反射
          UnityGI o_gi = UnityGI_Base(data, occlusion, normalWorld);
          //计算得出GI中的镜面反射
          o_gi.indirect.specular = UnityGI_IndirectSpecular1(data, occlusion, glossIn);
          return o_gi;
      }
      float SmoothnessToPerceptualRoughness1(float smoothness)
      {
          return (1 - smoothness);
      }
      Unity_GlossyEnvironmentData UnityGlossyEnvironmentSetup1(half Smoothness, half3 worldViewDir, half3 Normal, half3 fresnel0)
      {
          Unity_GlossyEnvironmentData g;
          //粗糙度
          g.roughness /* perceptualRoughness */   = SmoothnessToPerceptualRoughness1(Smoothness);
          //反射球的采样坐标
          g.reflUVW   = reflect(-worldViewDir, Normal);
    
          return g;
      }
    
      //PBR光照模型的GI计算
      inline void LightingStandard_GI1(
          SurfaceOutputStandard s,
          UnityGIInput data,
          inout UnityGI gi)
      {
          //如果是延迟渲染PASS并且开启了延迟渲染反射探针的话
          #if defined(UNITY_PASS_DEFERRED) && UNITY_ENABLE_REFLECTION_BUFFERS
          gi = UnityGlobalIllumination1(data, s.Occlusion, s.Normal);
          #else
    
          //Unity_GlossyEnvironmentData表示GI中的反射准备数据
          Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup1(s.Smoothness, data.worldViewDir, s.Normal,
                                                                      lerp(unity_ColorSpaceDielectricSpec.rgb, s.Albedo,
                                                                           s.Metallic));
          //进行GI计算并返回输出gi
          gi = UnityGlobalIllumination1(data, s.Occlusion, s.Normal, g);
          #endif
      }
    

    #endif

  • Shader文件

    //Standard材质
    Shader "MyShader/P2_2_9"
    {
    Properties
    {
    _Color ("Color", Color) = (1,1,1,1)
    _MainTex ("Albedo (RGB)", 2D) = "white" {}
    [NoScaleOffset]_MetallicTex("Metallic(R) Smoothness(G) AO(B)",2D) = "white" {}
    [NoScaleOffset][Normal]_NormalTex("NormalTex",2D) = "bump" {}

          _Glossiness ("Smoothness", Range(0,1)) = 0.0
          _Metallic ("Metallic", Range(0,1)) = 0.0
          _AO("AO",Range(0,1)) = 1.0
      }
      SubShader
      {
          Tags
          {
              "RenderType"="Opaque"
          }
          LOD 200
    
          // ---- forward rendering base pass:
          Pass
          {
              Name "FORWARD"
              Tags
              {
                  "LightMode" = "ForwardBase"
              }
    
              CGPROGRAM
              // compile directives
              #pragma vertex vert
              #pragma fragment frag
              #pragma target 3.0
              #pragma multi_compile_instancing
              #pragma multi_compile_fog
              #pragma multi_compile_fwdbase
    
              #include "UnityCG.cginc"
              #include "Lighting.cginc"
              #include "UnityPBSLighting.cginc"
              #include "AutoLight.cginc"
              #include "CGInclude/MyPhysicallyBasedRendering.cginc"
                  
              sampler2D _MainTex;
              float4 _MainTex_ST;
              half _Glossiness;
              half _Metallic;
              fixed4 _Color;
              sampler2D _MetallicTex;
              half _AO;
              sampler2D _NormalTex;
              
              struct appdata
              {
                  float4 vertex : POSITION;
                  float4 tangent : TANGENT;
                  float3 normal : NORMAL;
                  float4 texcoord : TEXCOORD0;
                  float4 texcoord1 : TEXCOORD1;
                  float4 texcoord2 : TEXCOORD2;
                  float4 texcoord3 : TEXCOORD3;
                  fixed4 color : COLOR;
                  UNITY_VERTEX_INPUT_INSTANCE_ID
              };
    
              // vertex-to-fragment interpolation data
              // no lightmaps:
              struct v2f
              {
                  float4 pos : SV_POSITION;
                  float2 uv : TEXCOORD0; // _MainTex
                  float3 worldNormal : TEXCOORD1;
                  float3 worldPos : TEXCOORD2;
                  #if UNITY_SHOULD_SAMPLE_SH
                      half3 sh : TEXCOORD3; // SH
                  #endif
                  //切线空间需要使用的矩阵
                  float3 tSpace0 : TEXCOORD4;
                  float3 tSpace1 : TEXCOORD5;
                  float3 tSpace2 : TEXCOORD6;
    
                  UNITY_FOG_COORDS(7)
                  UNITY_SHADOW_COORDS(8)
              };
    
              // vertex shader
              v2f vert(appdata v)
              {
                  v2f o;
    
                  o.pos = UnityObjectToClipPos(v.vertex);
                  o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
                  float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                  float3 worldNormal = UnityObjectToWorldNormal(v.normal);
    
                  //世界空间下的切线
                  half3 worldTangent = UnityObjectToWorldDir(v.tangent);
                  //切线方向
                  half tangentSign = v.tangent.w * unity_WorldTransformParams.w;
                  //世界空间下的副切线
                  half3 worldBinormal = cross(worldNormal, worldTangent) * tangentSign;
                  //切线矩阵
                  o.tSpace0 = float3(worldTangent.x, worldBinormal.x, worldNormal.x);
                  o.tSpace1 = float3(worldTangent.y, worldBinormal.y, worldNormal.y);
                  o.tSpace2 = float3(worldTangent.z, worldBinormal.z, worldNormal.z);
    
                  o.worldPos.xyz = worldPos;
                  o.worldNormal = worldNormal;
    
                  // SH/ambient and vertex lights
    
                  #if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL
                      o.sh = 0;
                      // Approximated illumination from non-important point lights
                  #ifdef VERTEXLIGHT_ON
                      o.sh += Shade4PointLights (
                      unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
                      unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
                      unity_4LightAtten0, worldPos, worldNormal);
                  #endif
                      o.sh = ShadeSHPerVertex (worldNormal, o.sh);
                  #endif
    
    
                  UNITY_TRANSFER_LIGHTING(o, v.texcoord1.xy);
    
                  UNITY_TRANSFER_FOG(o, o.pos); // pass fog coordinates to pixel shader
    
                  return o;
              }
    
              // fragment shader
              fixed4 frag(v2f i) : SV_Target
              {
                  UNITY_EXTRACT_FOG(i);
                  
                  float3 worldPos = i.worldPos.xyz;
                  
                  float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
    
                  SurfaceOutputStandard o;
                  UNITY_INITIALIZE_OUTPUT(SurfaceOutputStandard, o);
    
                  fixed4 mainTex = tex2D(_MainTex, i.uv);
                  o.Albedo = mainTex.rgb * _Color;
    
                  o.Emission = 0.0;
    
                  fixed4 metallicTex = tex2D(_MetallicTex, i.uv);
                  o.Metallic = metallicTex.r * _Metallic;
                  o.Smoothness = metallicTex.g * _Glossiness;
                  o.Occlusion = metallicTex.b * _AO;
                  o.Alpha = 1;
    
    
                  half3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));
                  half3 worldNormal = half3(dot(i.tSpace0,normalTex),dot(i.tSpace1,normalTex),dot(i.tSpace2,normalTex));
                  o.Normal = worldNormal;
    
    
                  // compute lighting & shadowing factor
                  UNITY_LIGHT_ATTENUATION(atten, i, worldPos)
    
                  // Setup lighting environment
                  UnityGI gi;
                  UNITY_INITIALIZE_OUTPUT(UnityGI, gi);
                  gi.indirect.diffuse = 0;
                  gi.indirect.specular = 0;
                  gi.light.color = _LightColor0.rgb;
                  gi.light.dir = _WorldSpaceLightPos0.xyz;
                  // Call GI (lightmaps/SH/reflections) lighting function
                  UnityGIInput giInput;
                  UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput);
                  giInput.light = gi.light;
                  giInput.worldPos = worldPos;
                  giInput.worldViewDir = worldViewDir;
                  giInput.atten = atten;
                  #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
                      giInput.lightmapUV = IN.lmap;
                  #else
                  giInput.lightmapUV = 0.0;
                  #endif
                  #if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL
                      giInput.ambient = i.sh;
                  #else
                  giInput.ambient.rgb = 0.0;
                  #endif
                  giInput.probeHDR[0] = unity_SpecCube0_HDR;
                  giInput.probeHDR[1] = unity_SpecCube1_HDR;
                  #if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION)
                      giInput.boxMin[0] = unity_SpecCube0_BoxMin; // .w holds lerp value for blending
                  #endif
                  #ifdef UNITY_SPECCUBE_BOX_PROJECTION
                      giInput.boxMax[0] = unity_SpecCube0_BoxMax;
                      giInput.probePosition[0] = unity_SpecCube0_ProbePosition;
                      giInput.boxMax[1] = unity_SpecCube1_BoxMax;
                      giInput.boxMin[1] = unity_SpecCube1_BoxMin;
                      giInput.probePosition[1] = unity_SpecCube1_ProbePosition;
                  #endif
                  
                  LightingStandard_GI1(o, giInput, gi);
                  
                  //return fixed4(gi.indirect.specular,1);
                  
                  // PBS的核心计算
                  fixed4 c = LightingStandard1(o, worldViewDir, gi);
                  
                  UNITY_APPLY_FOG(_unity_fogCoord, c); // apply fog
                  UNITY_OPAQUE_ALPHA(c.a); //把c的Alpha置1
                  
                  return c;
              }
              ENDCG
    
          }
      }
    

    }

相关推荐
优梦创客5 小时前
《黑神话悟空》开发框架与战斗系统解析
unity·游戏开发·黑神话悟空·战斗系统·解包
仙魁XAN11 小时前
Unity 设计模式 之 创造型模式-【工厂方法模式】【抽象工厂模式】
unity·设计模式·工厂方法模式·抽象工厂模式
我要吐泡泡了哦13 小时前
GAMES104:15 游戏引擎的玩法系统基础-学习笔记
笔记·学习·游戏引擎
躺下睡觉~21 小时前
Unity-Transform类-父子关系
java·unity·游戏引擎
躺下睡觉~21 小时前
Unity-Transform类-缩放和看向
unity·游戏引擎
君莫愁。1 天前
【Unity】检测鼠标点击位置是否有2D对象
unity·c#·游戏引擎
咩咩觉主1 天前
Unity实战案例全解析:PVZ 植物卡片状态分析
unity·c#·游戏引擎
蓝裕安1 天前
伪工厂模式制造敌人
开发语言·unity·游戏引擎
谢泽浩1 天前
Unity 给模型贴上照片
unity·游戏引擎
z2014z1 天前
Unity Resource System 优化笔记
unity·游戏引擎