BSDFs.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import FunctionNode from '../core/FunctionNode.js';
  2. import { pow2 } from './MathFunctions.js';
  3. export const F_Schlick = new FunctionNode( `
  4. vec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {
  5. // Original approximation by Christophe Schlick '94
  6. // float fresnel = pow( 1.0 - dotVH, 5.0 );
  7. // Optimized variant (presented by Epic at SIGGRAPH '13)
  8. // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
  9. float fresnel = exp2( ( -5.55473 * dotVH - 6.98316 ) * dotVH );
  10. return ( f90 - f0 ) * fresnel + f0;
  11. }` ); // validated
  12. export const G_BlinnPhong_Implicit = new FunctionNode( `
  13. float G_BlinnPhong_Implicit() {
  14. // ( const in float dotNL, const in float dotNV )
  15. // geometry term is (n dot l)(n dot v) / 4(n dot l)(n dot v)
  16. return 0.25;
  17. }` ); // validated
  18. export const BRDF_Lambert = new FunctionNode( `
  19. vec3 BRDF_Lambert( const in vec3 diffuseColor ) {
  20. return RECIPROCAL_PI * diffuseColor;
  21. }` ); // validated
  22. export const getDistanceAttenuation = new FunctionNode( `
  23. float getDistanceAttenuation( float lightDistance, float cutoffDistance, float decayExponent ) {
  24. #if defined ( PHYSICALLY_CORRECT_LIGHTS )
  25. // based upon Frostbite 3 Moving to Physically-based Rendering
  26. // page 32, equation 26: E[window1]
  27. // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
  28. float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );
  29. if( cutoffDistance > 0.0 ) {
  30. distanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );
  31. }
  32. return distanceFalloff;
  33. #else
  34. if( cutoffDistance > 0.0 && decayExponent > 0.0 ) {
  35. return pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );
  36. }
  37. return 1.0;
  38. #endif
  39. }` ).setIncludes( [ pow2 ] );
  40. //
  41. // STANDARD
  42. //
  43. // Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
  44. // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
  45. export const V_GGX_SmithCorrelated = new FunctionNode( `
  46. float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {
  47. float a2 = pow2( alpha );
  48. float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );
  49. float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );
  50. return 0.5 / max( gv + gl, EPSILON );
  51. }` ).setIncludes( [ pow2 ] );
  52. // Microfacet Models for Refraction through Rough Surfaces - equation (33)
  53. // http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
  54. // alpha is "roughness squared" in Disney’s reparameterization
  55. export const D_GGX = new FunctionNode( `
  56. float D_GGX( const in float alpha, const in float dotNH ) {
  57. float a2 = pow2( alpha );
  58. float denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0; // avoid alpha = 0 with dotNH = 1
  59. return RECIPROCAL_PI * a2 / pow2( denom );
  60. }` ).setIncludes( [ pow2 ] );
  61. // GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
  62. export const BRDF_Specular_GGX = new FunctionNode( `
  63. vec3 BRDF_Specular_GGX( vec3 lightDirection, const in vec3 f0, const in float f90, const in float roughness ) {
  64. float alpha = pow2( roughness ); // UE4's roughness
  65. vec3 halfDir = normalize( lightDirection + PositionViewDirection );
  66. float dotNL = saturate( dot( TransformedNormalView, lightDirection ) );
  67. float dotNV = saturate( dot( TransformedNormalView, PositionViewDirection ) );
  68. float dotNH = saturate( dot( TransformedNormalView, halfDir ) );
  69. float dotVH = saturate( dot( PositionViewDirection, halfDir ) );
  70. vec3 F = F_Schlick( f0, f90, dotVH );
  71. float V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );
  72. float D = D_GGX( alpha, dotNH );
  73. return F * ( V * D );
  74. }` ).setIncludes( [ pow2, F_Schlick, V_GGX_SmithCorrelated, D_GGX ] ); // validated
  75. export const RE_Direct_Physical = new FunctionNode( `
  76. void RE_Direct_Physical( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) {
  77. float dotNL = saturate( dot( TransformedNormalView, lightDirection ) );
  78. vec3 irradiance = dotNL * lightColor;
  79. #ifndef PHYSICALLY_CORRECT_LIGHTS
  80. irradiance *= PI; // punctual light
  81. #endif
  82. reflectedLight.directDiffuse += irradiance * BRDF_Lambert( MaterialDiffuseColor.rgb );
  83. reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX( lightDirection, MaterialSpecularTint, 1.0, MaterialRoughness );
  84. }` ).setIncludes( [ BRDF_Lambert, BRDF_Specular_GGX ] );
  85. export const PhysicalLightingModel = new FunctionNode( `
  86. void ( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) {
  87. RE_Direct_Physical( reflectedLight, lightDirection, lightColor );
  88. }` ).setIncludes( [ RE_Direct_Physical ] );
  89. // utils
  90. // Trowbridge-Reitz distribution to Mip level, following the logic of http://casual-effects.blogspot.ca/2011/08/plausible-environment-lighting-in-two.html
  91. export const getSpecularMIPLevel = new FunctionNode( `
  92. float ( const in float roughness, const in float maxMIPLevelScalar ) {
  93. float sigma = PI * roughness * roughness / ( 1.0 + roughness );
  94. float desiredMIPLevel = maxMIPLevelScalar + log2( sigma );
  95. // clamp to allowable LOD ranges.
  96. return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );
  97. }` );