float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) { if( decayExponent > 0.0 ) { #if defined ( PHYSICALLY_CORRECT_LIGHTS ) // based upon Frostbite 3 Moving to Physically-based Rendering // page 32, equation 26: E[window1] // http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr_v2.pdf // this is intended to be used on spot and point lights who are represented as luminous intensity // but who must be converted to luminous irradiance for surface lighting calculation float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 ); float maxDistanceCutoffFactor = pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) ); return distanceFalloff * maxDistanceCutoffFactor; #else return pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent ); #endif } return 1.0; } vec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) { return RECIPROCAL_PI * diffuseColor; } // validated vec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) { // Original approximation by Christophe Schlick '94 // float fresnel = pow( 1.0 - dotLH, 5.0 ); // Optimized variant (presented by Epic at SIGGRAPH '13) float fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH ); return ( 1.0 - specularColor ) * fresnel + specularColor; } // validated // Microfacet Models for Refraction through Rough Surfaces - equation (34) // http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html // alpha is "roughness squared" in Disney’s reparameterization float G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) { // geometry term = G(l)⋅G(v) / 4(n⋅l)(n⋅v) float a2 = pow2( alpha ); float gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) ); float gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) ); return 1.0 / ( gl * gv ); } // validated // Moving Frostbite to Physically Based Rendering 2.0 - page 12, listing 2 // http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr_v2.pdf float G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) { float a2 = pow2( alpha ); // dotNL and dotNV are explicitly swapped. This is not a mistake. float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) ); float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) ); return 0.5 / max( gv + gl, EPSILON ); } // Microfacet Models for Refraction through Rough Surfaces - equation (33) // http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html // alpha is "roughness squared" in Disney’s reparameterization float D_GGX( const in float alpha, const in float dotNH ) { float a2 = pow2( alpha ); float denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0; // avoid alpha = 0 with dotNH = 1 return RECIPROCAL_PI * a2 / pow2( denom ); } // GGX Distribution, Schlick Fresnel, GGX-Smith Visibility vec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) { float alpha = pow2( roughness ); // UE4's roughness vec3 halfDir = normalize( incidentLight.direction + geometry.viewDir ); float dotNL = saturate( dot( geometry.normal, incidentLight.direction ) ); float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) ); float dotNH = saturate( dot( geometry.normal, halfDir ) ); float dotLH = saturate( dot( incidentLight.direction, halfDir ) ); vec3 F = F_Schlick( specularColor, dotLH ); float G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV ); float D = D_GGX( alpha, dotNH ); return F * ( G * D ); } // validated // Rect Area Light // Area light computation code adapted from: // Real-Time Polygonal-Light Shading with Linearly Transformed Cosines // By: Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt // https://drive.google.com/file/d/0BzvWIdpUpRx_d09ndGVjNVJzZjA/view // https://eheitzresearch.wordpress.com/415-2/ // http://blog.selfshadow.com/sandbox/ltc.html vec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) { const float LUT_SIZE = 64.0; const float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE; const float LUT_BIAS = 0.5 / LUT_SIZE; float theta = acos( dot( N, V ) ); // Parameterization of texture: // sqrt(roughness) -> [0,1] // theta -> [0, PI/2] vec2 uv = vec2( sqrt( saturate( roughness ) ), saturate( theta / ( 0.5 * PI ) ) ); // Ensure we don't have nonlinearities at the look-up table's edges // see: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter24.html // "Shader Analysis" section uv = uv * LUT_SCALE + LUT_BIAS; return uv; } // Real-Time Area Lighting: a Journey from Research to Production // By: Stephen Hill & Eric Heitz // http://advances.realtimerendering.com/s2016/s2016_ltc_rnd.pdf // An approximation for the form factor of a clipped rectangle. float LTC_ClippedSphereFormFactor( const in vec3 f ) { float l = length( f ); return max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 ); } // Real-Time Polygonal-Light Shading with Linearly Transformed Cosines // also Real-Time Area Lighting: a Journey from Research to Production // http://advances.realtimerendering.com/s2016/s2016_ltc_rnd.pdf // Normalization by 2*PI is incorporated in this function itself. // theta/sin(theta) is approximated by rational polynomial vec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) { float x = dot( v1, v2 ); float y = abs( x ); float a = 0.86267 + (0.49788 + 0.01436 * y ) * y; float b = 3.45068 + (4.18814 + y) * y; float v = a / b; float theta_sintheta = (x > 0.0) ? v : 0.5 * inversesqrt( 1.0 - x * x ) - v; return cross( v1, v2 ) * theta_sintheta; } vec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) { // bail if point is on back side of plane of light // assumes ccw winding order of light vertices vec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ]; vec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ]; vec3 lightNormal = cross( v1, v2 ); if( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 ); // construct orthonormal basis around N vec3 T1, T2; T1 = normalize( V - N * dot( V, N ) ); T2 = - cross( N, T1 ); // negated from paper; possibly due to a different assumed handedness of world coordinate system // compute transform mat3 mat = mInv * transpose( mat3( T1, T2, N ) ); // transform rect vec3 coords[ 4 ]; coords[ 0 ] = mat * ( rectCoords[ 0 ] - P ); coords[ 1 ] = mat * ( rectCoords[ 1 ] - P ); coords[ 2 ] = mat * ( rectCoords[ 2 ] - P ); coords[ 3 ] = mat * ( rectCoords[ 3 ] - P ); // project rect onto sphere coords[ 0 ] = normalize( coords[ 0 ] ); coords[ 1 ] = normalize( coords[ 1 ] ); coords[ 2 ] = normalize( coords[ 2 ] ); coords[ 3 ] = normalize( coords[ 3 ] ); // calculate vector form factor vec3 vectorFormFactor = vec3( 0.0 ); vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] ); vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] ); vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] ); vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] ); // adjust for horizon clipping vec3 result = vec3( LTC_ClippedSphereFormFactor( vectorFormFactor ) ); return result; } // End Rect Area Light // ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile vec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) { float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) ); const vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 ); const vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 ); vec4 r = roughness * c0 + c1; float a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y; vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw; return specularColor * AB.x + AB.y; } // validated float G_BlinnPhong_Implicit( /* const in float dotNL, const in float dotNV */ ) { // geometry term is (n dot l)(n dot v) / 4(n dot l)(n dot v) return 0.25; } float D_BlinnPhong( const in float shininess, const in float dotNH ) { return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess ); } vec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) { vec3 halfDir = normalize( incidentLight.direction + geometry.viewDir ); //float dotNL = saturate( dot( geometry.normal, incidentLight.direction ) ); //float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) ); float dotNH = saturate( dot( geometry.normal, halfDir ) ); float dotLH = saturate( dot( incidentLight.direction, halfDir ) ); vec3 F = F_Schlick( specularColor, dotLH ); float G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ ); float D = D_BlinnPhong( shininess, dotNH ); return F * ( G * D ); } // validated // source: http://simonstechblog.blogspot.ca/2011/12/microfacet-brdf.html float GGXRoughnessToBlinnExponent( const in float ggxRoughness ) { return ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 ); } float BlinnExponentToGGXRoughness( const in float blinnExponent ) { return sqrt( 2.0 / ( blinnExponent + 2.0 ) ); }