demotool/shaders/lib/shading-pbr.glsl

144 lines
3.6 KiB
GLSL

//! type library
//! include lib/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 );
}