// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt) #ifndef UNITY_STANDARD_BRDF_INCLUDED_TOON #define UNITY_STANDARD_BRDF_INCLUDED_TOON #include "UnityCG.cginc" #include "UnityStandardConfig.cginc" #include "UnityLightingCommon.cginc" void ConvertToCellShading(inout half nl, inout half nv, ToonShadingData toonShadingData) { if (toonShadingData.enableToonShading == 1) { //Cell Shading Calculations: toonShadingData.cellTransitionSmoothness = (1.0 / toonShadingData.numberOfCells) * toonShadingData.cellTransitionSmoothness; // adjusted for smoothstep half currentCell = ceil(nl * toonShadingData.numberOfCells) / toonShadingData.numberOfCells; half previousCell = max(0, ceil(nl * toonShadingData.numberOfCells) - 1) / toonShadingData.numberOfCells; nl = max(currentCell * smoothstep(previousCell, previousCell + toonShadingData.cellTransitionSmoothness, nl), previousCell); currentCell = ceil(nv * toonShadingData.numberOfCells) / toonShadingData.numberOfCells; previousCell = max(0, ceil(nv * toonShadingData.numberOfCells) - 1) / toonShadingData.numberOfCells; nv = max(currentCell * smoothstep(previousCell, previousCell + toonShadingData.cellTransitionSmoothness, nv), previousCell); } } half3 ConvertToCellShading(half3 value, ToonShadingData toonShadingData) { if (toonShadingData.enableToonShading == 1) { //Cell Shading Calculations: toonShadingData.cellTransitionSmoothness = (1.0 / toonShadingData.numberOfCells) * toonShadingData.cellTransitionSmoothness; // adjusted for smoothstep half3 currentCell = ceil(value * toonShadingData.numberOfCells) / toonShadingData.numberOfCells; half3 previousCell = max(0, ceil(value * toonShadingData.numberOfCells) - 1) / toonShadingData.numberOfCells; value = max(currentCell * smoothstep(previousCell, previousCell + toonShadingData.cellTransitionSmoothness, value), previousCell); return value; } return value; } ////----------------------------------------------------------------------------- //// 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 float SmithJointGGXVisibilityTerm (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 //} //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_Toon(half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness, float3 normal, float3 viewDir, UnityLight light, UnityIndirect gi, ToonShadingData toonShadingData) { if (toonShadingData.enableToonShading == 1 && toonShadingData.shadingAffectByNormalMap == 0) { normal = toonShadingData.normalWSNoMap; } 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); 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)); float nlCopy = float(nl); float nvCopy = float(nv); half nlCell = Posterize(nl, toonShadingData); half nvCell = Posterize(nv, toonShadingData); // Diffuse term half diffuseTerm = DisneyDiffuse(nvCell, nlCell, lh, perceptualRoughness) * nlCell; // 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 // GGX with roughtness to 0 would mean no specular at all, using max(roughness, 0.002) here to match HDrenderloop roughtness remapping. roughness = max(roughness, 0.002); float 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 float 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)); //original //half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm) // + specularTerm * light.color * FresnelTerm(specColor, lh) // + surfaceReduction * gi.specular * FresnelLerp(specColor, grazingTerm, nv); half3 color = 0; color = diffColor * (gi.diffuse + light.color * diffuseTerm); float3 specular = specularTerm * light.color * FresnelTerm(specColor, lh); color += PosterizeShifted(specular, toonShadingData); //float3 posterizedGISpecular = PosterizeShifted(gi.specular, toonShadingData); color += surfaceReduction * gi.specular * FresnelLerp(specColor, grazingTerm, nvCell); 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_Toon(half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness, float3 normal, float3 viewDir, UnityLight light, UnityIndirect gi, ToonShadingData toonShadingData) { 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)); ConvertToCellShading(nl, nv, toonShadingData); // 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_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))).r * 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_Toon (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness, float3 normal, float3 viewDir, UnityLight light, UnityIndirect gi, ToonShadingData toonShadingData) { if (toonShadingData.enableToonShading == 1 && toonShadingData.shadingAffectByNormalMap == 0) { normal = toonShadingData.normalWSNoMap; } float3 reflDir = reflect(viewDir, normal); half nl = saturate(dot(normal, light.dir)); half nv = saturate(dot(normal, viewDir)); //if (toonShadingData.enableToonShading == 1) //{ // //Cell Shading Calculations: // toonShadingData.cellTransitionSmoothness = (1.0 / toonShadingData.numberOfCells) * toonShadingData.cellTransitionSmoothness; // adjusted for smoothstep // half currentCell = ceil(nl * toonShadingData.numberOfCells) / toonShadingData.numberOfCells; // half previousCell = max(0, ceil(nl * toonShadingData.numberOfCells) - 1) / toonShadingData.numberOfCells; // nl = max(currentCell * smoothstep(previousCell, previousCell + toonShadingData.cellTransitionSmoothness, nl), previousCell); // currentCell = ceil(nv * toonShadingData.numberOfCells) / toonShadingData.numberOfCells; // previousCell = max(0, ceil(nv * toonShadingData.numberOfCells) - 1) / toonShadingData.numberOfCells; // nv = max(currentCell * smoothstep(previousCell, previousCell + toonShadingData.cellTransitionSmoothness, nv), previousCell); //} ConvertToCellShading(nl, nv, toonShadingData); // 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