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/(x4+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.28x3 as approximation for (1/(x4+1))(1/2.2) on the domain 0;1

Unity使用 1-0.28x3 替代 (1/(x4+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);

}

  • F0 : 视线 与 物体法线 夹角为 0° 的情况
  • F90 : 视线 与 物体法线 夹角为 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
    
          }
      }

    }

相关推荐
WarPigs26 分钟前
游戏签到系统
unity
小拉达不是臭老鼠3 小时前
Unity中的UI系统之UGUI
学习·ui·unity
万兴丶3 小时前
Coplay适用于 Unity 的“Al 代理”使用指南
unity·游戏引擎·ai编程
魔士于安6 小时前
Unity材质球大合集
unity·游戏引擎·材质
mxwin8 小时前
Unity Shader 冰面 Shader 制作原理与流程
unity·游戏引擎·shader
玖玥拾9 小时前
Cocos学习笔记:关卡系统、音频管理与物理控制
游戏引擎·cocos2d
小拉达不是臭老鼠9 小时前
Unity中的UI系统之UGUI_登陆面板实现
ui·unity
郝学胜-神的一滴9 小时前
[简化版 GAMES 101] 计算机图形学 11:频域·卷积·抗锯齿
c++·unity·图形渲染·opengl·three·unreal
玖玥拾18 小时前
Cocos学习笔记:滚动视图、关卡系统与本地存储
游戏引擎·cocos2d
元气少女小圆丶1 天前
SenseGlove Nova 2+Unity开发笔记2
笔记·unity·游戏引擎