LTC.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { tslFn, If, mat3, vec2, vec3 } from '../../shadernode/ShaderNode.js';
  2. import { max } from '../../math/MathNode.js';
  3. // Rect Area Light
  4. // Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
  5. // by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt
  6. // code: https://github.com/selfshadow/ltc_code/
  7. const LTC_Uv = tslFn( ( { N, V, roughness } ) => {
  8. const LUT_SIZE = 64.0;
  9. const LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;
  10. const LUT_BIAS = 0.5 / LUT_SIZE;
  11. const dotNV = N.dot( V ).saturate();
  12. // texture parameterized by sqrt( GGX alpha ) and sqrt( 1 - cos( theta ) )
  13. const uv = vec2( roughness, dotNV.oneMinus().sqrt() );
  14. uv.assign( uv.mul( LUT_SCALE ).add( LUT_BIAS ) );
  15. return uv;
  16. } ).setLayout( {
  17. name: 'LTC_Uv',
  18. type: 'vec2',
  19. inputs: [
  20. { name: 'N', type: 'vec3' },
  21. { name: 'V', type: 'vec3' },
  22. { name: 'roughness', type: 'float' }
  23. ]
  24. } );
  25. const LTC_ClippedSphereFormFactor = tslFn( ( { f } ) => {
  26. // Real-Time Area Lighting: a Journey from Research to Production (p.102)
  27. // An approximation of the form factor of a horizon-clipped rectangle.
  28. const l = f.length();
  29. return max( l.mul( l ).add( f.z ).div( l.add( 1.0 ) ), 0 );
  30. } ).setLayout( {
  31. name: 'LTC_ClippedSphereFormFactor',
  32. type: 'float',
  33. inputs: [
  34. { name: 'f', type: 'vec3' }
  35. ]
  36. } );
  37. const LTC_EdgeVectorFormFactor = tslFn( ( { v1, v2 } ) => {
  38. const x = v1.dot( v2 );
  39. const y = x.abs().toVar();
  40. // rational polynomial approximation to theta / sin( theta ) / 2PI
  41. const a = y.mul( 0.0145206 ).add( 0.4965155 ).mul( y ).add( 0.8543985 ).toVar();
  42. const b = y.add( 4.1616724 ).mul( y ).add( 3.4175940 ).toVar();
  43. const v = a.div( b );
  44. const theta_sintheta = x.greaterThan( 0.0 ).cond( v, max( x.mul( x ).oneMinus(), 1e-7 ).inverseSqrt().mul( 0.5 ).sub( v ) );
  45. return v1.cross( v2 ).mul( theta_sintheta );
  46. } ).setLayout( {
  47. name: 'LTC_EdgeVectorFormFactor',
  48. type: 'vec3',
  49. inputs: [
  50. { name: 'v1', type: 'vec3' },
  51. { name: 'v2', type: 'vec3' }
  52. ]
  53. } );
  54. const LTC_Evaluate = tslFn( ( { N, V, P, mInv, p0, p1, p2, p3 } ) => {
  55. // bail if point is on back side of plane of light
  56. // assumes ccw winding order of light vertices
  57. const v1 = p1.sub( p0 ).toVar();
  58. const v2 = p3.sub( p0 ).toVar();
  59. const lightNormal = v1.cross( v2 );
  60. const result = vec3().toVar();
  61. If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => {
  62. // construct orthonormal basis around N
  63. const T1 = V.sub( N.mul( V.dot( N ) ) ).normalize();
  64. const T2 = N.cross( T1 ).negate(); // negated from paper; possibly due to a different handedness of world coordinate system
  65. // compute transform
  66. const mat = mInv.mul( mat3( T1, T2, N ).transpose() ).toVar();
  67. // transform rect
  68. // & project rect onto sphere
  69. const coords0 = mat.mul( p0.sub( P ) ).normalize().toVar();
  70. const coords1 = mat.mul( p1.sub( P ) ).normalize().toVar();
  71. const coords2 = mat.mul( p2.sub( P ) ).normalize().toVar();
  72. const coords3 = mat.mul( p3.sub( P ) ).normalize().toVar();
  73. // calculate vector form factor
  74. const vectorFormFactor = vec3( 0 ).toVar();
  75. vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) );
  76. vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) );
  77. vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) );
  78. vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) );
  79. // adjust for horizon clipping
  80. result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor } ) ) );
  81. } );
  82. return result;
  83. } ).setLayout( {
  84. name: 'LTC_Evaluate',
  85. type: 'vec3',
  86. inputs: [
  87. { name: 'N', type: 'vec3' },
  88. { name: 'V', type: 'vec3' },
  89. { name: 'P', type: 'vec3' },
  90. { name: 'mInv', type: 'mat3' },
  91. { name: 'p0', type: 'vec3' },
  92. { name: 'p1', type: 'vec3' },
  93. { name: 'p2', type: 'vec3' },
  94. { name: 'p3', type: 'vec3' }
  95. ]
  96. } );
  97. export { LTC_Evaluate, LTC_Uv };