// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt) // This code in no way belongs to BrainFailProductions. I have just made a small change to make // it work with texture arrays #ifndef UNITY_STANDARD_BRDF_INCLUDED #define UNITY_STANDARD_BRDF_INCLUDED #include "UnityCG.cginc" #include "UnityStandardConfig.cginc" #include "UnityLightingCommon.cginc" //----------------------------------------------------------------------------- // Helper to convert smoothness to roughness //----------------------------------------------------------------------------- float PerceptualRoughnessToRoughness(float perceptualRoughness) { return perceptualRoughness * perceptualRoughness; } half RoughnessToPerceptualRoughness(half roughness) { return sqrt(roughness); } // Smoothness is the user facing name // it should be perceptualSmoothness but we don't want the user to have to deal with this name half SmoothnessToRoughness(half smoothness) { return (1 - smoothness) * (1 - smoothness); } float SmoothnessToPerceptualRoughness(float smoothness) { return (1 - smoothness); } //------------------------------------------------------------------------------------- inline half Pow4 (half x) { return x*x*x*x; } inline float2 Pow4 (float2 x) { return x*x*x*x; } inline half3 Pow4 (half3 x) { return x*x*x*x; } inline half4 Pow4 (half4 x) { return x*x*x*x; } // Pow5 uses the same amount of instructions as generic pow(), but has 2 advantages: // 1) better instruction pipelining // 2) no need to worry about NaNs inline half Pow5 (half x) { return x*x * x*x * x; } inline half2 Pow5 (half2 x) { return x*x * x*x * x; } inline half3 Pow5 (half3 x) { return x*x * x*x * x; } inline half4 Pow5 (half4 x) { return x*x * x*x * x; } inline half3 FresnelTerm (half3 F0, half cosA) { half t = Pow5 (1 - cosA); // ala Schlick interpoliation return F0 + (1-F0) * t; } inline half3 FresnelLerp (half3 F0, half3 F90, half cosA) { half t = Pow5 (1 - cosA); // ala Schlick interpoliation return lerp (F0, F90, t); } // approximage Schlick with ^4 instead of ^5 inline half3 FresnelLerpFast (half3 F0, half3 F90, half cosA) { half t = Pow4 (1 - cosA); return lerp (F0, F90, t); } // Note: Disney diffuse must be multiply by diffuseAlbedo / PI. This is done outside of this function. half DisneyDiffuse(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; } // NOTE: Visibility term here is the full form from Torrance-Sparrow model, it includes Geometric term: V = G / (N.L * N.V) // This way it is easier to swap Geometric terms and more room for optimizations (except maybe in case of CookTorrance geom term) // Generic Smith-Schlick visibility term inline half SmithVisibilityTerm (half NdotL, half NdotV, half k) { half gL = NdotL * (1-k) + k; half gV = NdotV * (1-k) + k; return 1.0 / (gL * gV + 1e-5f); // This function is not intended to be running on Mobile, // therefore epsilon is smaller than can be represented by half } // Smith-Schlick derived for Beckmann inline half SmithBeckmannVisibilityTerm (half NdotL, half NdotV, half roughness) { half c = 0.797884560802865h; // c = sqrt(2 / Pi) half k = roughness * c; return SmithVisibilityTerm (NdotL, NdotV, k) * 0.25f; // * 0.25 is the 1/4 of the visibility term } // Ref: http://jcgt.org/published/0003/02/03/paper.pdf inline half SmithJointGGXVisibilityTerm (half NdotL, half NdotV, half 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) half a = roughness; half lambdaV = NdotL * (NdotV * (1 - a) + a); half lambdaL = NdotV * (NdotL * (1 - a) + a); return 0.5f / (lambdaV + lambdaL + 1e-5f); #endif } inline float GGXTerm (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 } inline half PerceptualRoughnessToSpecPower (half perceptualRoughness) { half m = PerceptualRoughnessToRoughness(perceptualRoughness); // m is the true academic roughness. half sq = max(1e-4f, m*m); half n = (2.0 / sq) - 2.0; // https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf n = max(n, 1e-4f); // prevent possible cases of pow(0,0), which could happen when roughness is 1.0 and NdotH is zero return n; } // BlinnPhong normalized as normal distribution function (NDF) // for use in micro-facet model: spec=D*G*F // eq. 19 in https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf inline half NDFBlinnPhongNormalizedTerm (half NdotH, half n) { // norm = (n+2)/(2*pi) half normTerm = (n + 2.0) * (0.5/UNITY_PI); half specTerm = pow (NdotH, n); return specTerm * normTerm; } //------------------------------------------------------------------------------------- /* // https://s3.amazonaws.com/docs.knaldtech.com/knald/1.0.0/lys_power_drops.html const float k0 = 0.00098, k1 = 0.9921; // pass this as a constant for optimization const float fUserMaxSPow = 100000; // sqrt(12M) const float g_fMaxT = ( exp2(-10.0/fUserMaxSPow) - k0)/k1; float GetSpecPowToMip(float fSpecPow, int nMips) { // Default curve - Inverse of TB2 curve with adjusted constants float fSmulMaxT = ( exp2(-10.0/sqrt( fSpecPow )) - k0)/k1; return float(nMips-1)*(1.0 - clamp( fSmulMaxT/g_fMaxT, 0.0, 1.0 )); } //float specPower = PerceptualRoughnessToSpecPower(perceptualRoughness); //float mip = GetSpecPowToMip (specPower, 7); */ inline float3 Unity_SafeNormalize(float3 inVec) { float dp3 = max(0.001f, dot(inVec, inVec)); return inVec * rsqrt(dp3); } //------------------------------------------------------------------------------------- // Note: BRDF entry points use smoothness and oneMinusReflectivity for optimization // purposes, mostly for DX9 SM2.0 level. Most of the math is being done on these (1-x) values, and that saves // a few precious ALU slots. // 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_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness, float3 normal, float3 viewDir, UnityLight light, UnityIndirect gi) { float perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness); float3 halfDir = Unity_SafeNormalize (float3(light.dir) + viewDir); // 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); half 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 half 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 = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl; // Specular term // HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm! // 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 float roughness = PerceptualRoughnessToRoughness(perceptualRoughness); #if UNITY_BRDF_GGX half V = SmithJointGGXVisibilityTerm (nl, nv, roughness); float D = GGXTerm (nh, roughness); #else // Legacy half V = SmithBeckmannVisibilityTerm (nl, nv, roughness); half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness)); #endif half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later # 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 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. specularTerm *= any(specColor) ? 1.0 : 0.0; half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity)); half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm) + specularTerm * light.color * FresnelTerm (specColor, lh) + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv); return half4(color, 1); } // 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_PBS (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 half 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 unity_NHxRoughness; half3 BRDF3_Direct(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_NHxRoughness, half2(rlPow4, SmoothnessToPerceptualRoughness(smoothness))).UNITY_ATTEN_CHANNEL * LUT_RANGE; #if defined(_SPECULARHIGHLIGHTS_OFF) specular = 0.0; #endif return diffColor + specular * specColor; } half3 BRDF3_Indirect(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_PBS (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_Direct(diffColor, specColor, rlPow4, smoothness); color *= light.color * nl; color += BRDF3_Indirect(diffColor, specColor, gi, grazingTerm, fresnelTerm); return half4(color, 1); } // Include deprecated function #define INCLUDE_UNITY_STANDARD_BRDF_DEPRECATED #include "UnityDeprecated.cginc" #undef INCLUDE_UNITY_STANDARD_BRDF_DEPRECATED #endif // UNITY_STANDARD_BRDF_INCLUDED