//! type library //! include utils.glsl struct T_PBRMaterial { vec3 cAlbedo; float roughness; float metallic; float subsurface; float anisotropy; float specular; // Specular strengh for non-metals float specularTint; // Albedo color% in specular tint (non-metals) }; struct T_PBRPrecomputed { float nDotV , ax , ay , fv , vgs; vec3 tangent , bitangent , cSpecular; }; // ----------------------------------------------------------------------------- float PBR_SchlickFresnel( in float u ) { const float m = 1 - u , m2 = M_Sqr( m ); return M_Sqr( m2 ) * m; } float PBR_GTR2Aniso( in float nDotH , in float hDotX , in float hDotY , in float ax , in float ay ) { float x = hDotX / ax , y = hDotY / ay , p = x * x + y * y + nDotH * nDotH; return 1 / ( ax * ay * p * p ); } float PBR_SmithGGXAniso( in float nDotV , in float vDotX , in float vDotY , in float ax , in float ay ) { float x = vDotX * ax , y = vDotY * ay; return 1 / ( nDotV + sqrt( x * x + y * y + nDotV * nDotV ) ); } T_PBRPrecomputed PBR_Precompute( in T_PBRMaterial material , in vec3 camDir , in vec3 normal ) { T_PBRPrecomputed rv; rv.nDotV = abs( dot( normal , camDir ) ) + 1e-5; rv.fv = PBR_SchlickFresnel( rv.nDotV ); float tDir = step( .99 , abs( normal.z ) ) , aspect = sqrt( 1 - material.anisotropy * .9 ) , rsqr = M_Sqr( material.roughness ); vec3 tUp = mix( vec3( 0 , 0 , 1 ) , vec3( 1 , 0 , 0 ) , tDir ) , tint = M_NormalizeColor( material.cAlbedo ); rv.tangent = normalize( cross( tUp , normal ) ); rv.bitangent = normalize( cross( normal , rv.tangent ) ); rv.cSpecular = mix( material.specular * .08 * mix( vec3( 1 ) , tint , material.specularTint ) , material.cAlbedo , material.metallic ); rv.ax = max( .001, rsqr / aspect ); rv.ay = max( .001, rsqr * aspect ); rv.vgs = PBR_SmithGGXAniso( rv.nDotV , dot( camDir , rv.tangent ) , dot( camDir , rv.bitangent ) , rv.ax , rv.ay ); return rv; } vec3 PBR_Shade( in T_PBRMaterial material , in T_PBRPrecomputed pre , in vec3 camDir , in vec3 normal , in vec3 lightDir ) { float nDotL = dot( normal , lightDir ); if ( nDotL < 0 ) { return vec3( 0 ); } vec3 halfVec = normalize( lightDir + camDir ) ; float nDotH = M_Saturate( dot( normal , halfVec ) ) , lDotH = M_Saturate( dot( lightDir , halfVec ) ) , // Diffuse fresnel - go from 1 at normal incidence to .5 at grazing // and mix in diffuse retro-reflection based on roughness FL = PBR_SchlickFresnel( nDotL ) , Fd90 = ( 0.5 + 2 * lDotH * lDotH ) * material.roughness , Fd = ( 1 + ( Fd90 - 1 ) * FL ) * ( 1 + ( Fd90 - 1 ) * pre.fv ) * ( 1 - 0.51 * material.roughness / 1.51 ) , // Based on Hanrahan-Krueger brdf approximation of isotropic bssrdf // 1.25 scale is used to (roughly) preserve albedo // Fss90 used to "flatten" retroreflection based on roughness Fss90 = lDotH * lDotH * material.roughness , Fss = mix( 1 , Fss90 , FL ) * mix( 1 , Fss90 , pre.fv ) , ss = 1.25 * ( Fss * ( 1 / ( nDotL + pre.nDotV ) - .5 ) + .5 ) , // Specular Ds = PBR_GTR2Aniso( nDotH , dot( halfVec , pre.tangent ) , dot( halfVec , pre.bitangent ) , pre.ax , pre.ay ) , FH = PBR_SchlickFresnel( lDotH ) , Gs = PBR_SmithGGXAniso( nDotL , dot( lightDir , pre.tangent ) , dot( lightDir , pre.bitangent ) , pre.ax , pre.ay ) * pre.vgs; vec3 Fs = mix( pre.cSpecular , vec3(1) , FH ); return nDotL * ( mix( Fd , ss , material.subsurface ) * material.cAlbedo * ( 1 - material.metallic ) + Gs * Fs * Ds ) / PI; } vec3 PBR_Shade( in T_PBRMaterial material , in vec3 camDir , in vec3 normal , in vec3 lightDir ) { return PBR_Shade( material , PBR_Precompute( material , camDir , normal ) , camDir , normal , lightDir ); }