文章目录
前言
在上一篇文章中,我们解析了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.28roughnessperceptualRoughness; // 1-0.28x^3 as approximation for (1/(x4+1))(1/2.2) on the domain [0;1]
# else
surfaceReduction = 1.0 / (roughnessroughness + 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.specular 就是之前文章中解析计算的 GI 的镜面反射
-
镜面反射在不同角度下的过度 : 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
}
}
}