文章目录
- 前言
- [一、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.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);
}
- 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 } }
}