PMREMUtils.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. import { tslFn, int, float, vec2, vec3, vec4, If } from '../shadernode/ShaderNode.js';
  2. import { cos, sin, abs, max, exp2, log2, clamp, fract, mix, floor, normalize, cross, all } from '../math/MathNode.js';
  3. import { mul } from '../math/OperatorNode.js';
  4. import { cond } from '../math/CondNode.js';
  5. import { loop, Break } from '../utils/LoopNode.js';
  6. // These defines must match with PMREMGenerator
  7. const cubeUV_r0 = float( 1.0 );
  8. const cubeUV_m0 = float( - 2.0 );
  9. const cubeUV_r1 = float( 0.8 );
  10. const cubeUV_m1 = float( - 1.0 );
  11. const cubeUV_r4 = float( 0.4 );
  12. const cubeUV_m4 = float( 2.0 );
  13. const cubeUV_r5 = float( 0.305 );
  14. const cubeUV_m5 = float( 3.0 );
  15. const cubeUV_r6 = float( 0.21 );
  16. const cubeUV_m6 = float( 4.0 );
  17. const cubeUV_minMipLevel = float( 4.0 );
  18. const cubeUV_minTileSize = float( 16.0 );
  19. // These shader functions convert between the UV coordinates of a single face of
  20. // a cubemap, the 0-5 integer index of a cube face, and the direction vector for
  21. // sampling a textureCube (not generally normalized ).
  22. const getFace = tslFn( ( [ direction ] ) => {
  23. const absDirection = vec3( abs( direction ) ).toVar();
  24. const face = float( - 1.0 ).toVar();
  25. If( absDirection.x.greaterThan( absDirection.z ), () => {
  26. If( absDirection.x.greaterThan( absDirection.y ), () => {
  27. face.assign( cond( direction.x.greaterThan( 0.0 ), 0.0, 3.0 ) );
  28. } ).else( () => {
  29. face.assign( cond( direction.y.greaterThan( 0.0 ), 1.0, 4.0 ) );
  30. } );
  31. } ).else( () => {
  32. If( absDirection.z.greaterThan( absDirection.y ), () => {
  33. face.assign( cond( direction.z.greaterThan( 0.0 ), 2.0, 5.0 ) );
  34. } ).else( () => {
  35. face.assign( cond( direction.y.greaterThan( 0.0 ), 1.0, 4.0 ) );
  36. } );
  37. } );
  38. return face;
  39. } ).setLayout( {
  40. name: 'getFace',
  41. type: 'float',
  42. inputs: [
  43. { name: 'direction', type: 'vec3' }
  44. ]
  45. } );
  46. // RH coordinate system; PMREM face-indexing convention
  47. const getUV = tslFn( ( [ direction, face ] ) => {
  48. const uv = vec2().toVar();
  49. If( face.equal( 0.0 ), () => {
  50. uv.assign( vec2( direction.z, direction.y ).div( abs( direction.x ) ) ); // pos x
  51. } ).elseif( face.equal( 1.0 ), () => {
  52. uv.assign( vec2( direction.x.negate(), direction.z.negate() ).div( abs( direction.y ) ) ); // pos y
  53. } ).elseif( face.equal( 2.0 ), () => {
  54. uv.assign( vec2( direction.x.negate(), direction.y ).div( abs( direction.z ) ) ); // pos z
  55. } ).elseif( face.equal( 3.0 ), () => {
  56. uv.assign( vec2( direction.z.negate(), direction.y ).div( abs( direction.x ) ) ); // neg x
  57. } ).elseif( face.equal( 4.0 ), () => {
  58. uv.assign( vec2( direction.x.negate(), direction.z ).div( abs( direction.y ) ) ); // neg y
  59. } ).else( () => {
  60. uv.assign( vec2( direction.x, direction.y ).div( abs( direction.z ) ) ); // neg z
  61. } );
  62. return mul( 0.5, uv.add( 1.0 ) );
  63. } ).setLayout( {
  64. name: 'getUV',
  65. type: 'vec2',
  66. inputs: [
  67. { name: 'direction', type: 'vec3' },
  68. { name: 'face', type: 'float' }
  69. ]
  70. } );
  71. const roughnessToMip = tslFn( ( [ roughness ] ) => {
  72. const mip = float( 0.0 ).toVar();
  73. If( roughness.greaterThanEqual( cubeUV_r1 ), () => {
  74. mip.assign( cubeUV_r0.sub( roughness ).mul( cubeUV_m1.sub( cubeUV_m0 ) ).div( cubeUV_r0.sub( cubeUV_r1 ) ).add( cubeUV_m0 ) );
  75. } ).elseif( roughness.greaterThanEqual( cubeUV_r4 ), () => {
  76. mip.assign( cubeUV_r1.sub( roughness ).mul( cubeUV_m4.sub( cubeUV_m1 ) ).div( cubeUV_r1.sub( cubeUV_r4 ) ).add( cubeUV_m1 ) );
  77. } ).elseif( roughness.greaterThanEqual( cubeUV_r5 ), () => {
  78. mip.assign( cubeUV_r4.sub( roughness ).mul( cubeUV_m5.sub( cubeUV_m4 ) ).div( cubeUV_r4.sub( cubeUV_r5 ) ).add( cubeUV_m4 ) );
  79. } ).elseif( roughness.greaterThanEqual( cubeUV_r6 ), () => {
  80. mip.assign( cubeUV_r5.sub( roughness ).mul( cubeUV_m6.sub( cubeUV_m5 ) ).div( cubeUV_r5.sub( cubeUV_r6 ) ).add( cubeUV_m5 ) );
  81. } ).else( () => {
  82. mip.assign( float( - 2.0 ).mul( log2( mul( 1.16, roughness ) ) ) ); // 1.16 = 1.79^0.25
  83. } );
  84. return mip;
  85. } ).setLayout( {
  86. name: 'roughnessToMip',
  87. type: 'float',
  88. inputs: [
  89. { name: 'roughness', type: 'float' }
  90. ]
  91. } );
  92. // RH coordinate system; PMREM face-indexing convention
  93. export const getDirection = tslFn( ( [ uv_immutable, face ] ) => {
  94. const uv = uv_immutable.toVar();
  95. uv.assign( mul( 2.0, uv ).sub( 1.0 ) );
  96. const direction = vec3( uv, 1.0 ).toVar();
  97. If( face.equal( 0.0 ), () => {
  98. direction.assign( direction.zyx ); // ( 1, v, u ) pos x
  99. } ).elseif( face.equal( 1.0 ), () => {
  100. direction.assign( direction.xzy );
  101. direction.xz.mulAssign( - 1.0 ); // ( -u, 1, -v ) pos y
  102. } ).elseif( face.equal( 2.0 ), () => {
  103. direction.x.mulAssign( - 1.0 ); // ( -u, v, 1 ) pos z
  104. } ).elseif( face.equal( 3.0 ), () => {
  105. direction.assign( direction.zyx );
  106. direction.xz.mulAssign( - 1.0 ); // ( -1, v, -u ) neg x
  107. } ).elseif( face.equal( 4.0 ), () => {
  108. direction.assign( direction.xzy );
  109. direction.xy.mulAssign( - 1.0 ); // ( -u, -1, v ) neg y
  110. } ).elseif( face.equal( 5.0 ), () => {
  111. direction.z.mulAssign( - 1.0 ); // ( u, v, -1 ) neg zS
  112. } );
  113. return direction;
  114. } ).setLayout( {
  115. name: 'getDirection',
  116. type: 'vec3',
  117. inputs: [
  118. { name: 'uv', type: 'vec2' },
  119. { name: 'face', type: 'float' }
  120. ]
  121. } );
  122. //
  123. export const textureCubeUV = tslFn( ( [ envMap, sampleDir_immutable, roughness_immutable, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ] ) => {
  124. const roughness = float( roughness_immutable );
  125. const sampleDir = vec3( sampleDir_immutable );
  126. const mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );
  127. const mipF = fract( mip );
  128. const mipInt = floor( mip );
  129. const color0 = vec3( bilinearCubeUV( envMap, sampleDir, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ) ).toVar();
  130. If( mipF.notEqual( 0.0 ), () => {
  131. const color1 = vec3( bilinearCubeUV( envMap, sampleDir, mipInt.add( 1.0 ), CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ) ).toVar();
  132. color0.assign( mix( color0, color1, mipF ) );
  133. } );
  134. return color0;
  135. } );
  136. const bilinearCubeUV = tslFn( ( [ envMap, direction_immutable, mipInt_immutable, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ] ) => {
  137. const mipInt = float( mipInt_immutable ).toVar();
  138. const direction = vec3( direction_immutable );
  139. const face = float( getFace( direction ) ).toVar();
  140. const filterInt = float( max( cubeUV_minMipLevel.sub( mipInt ), 0.0 ) ).toVar();
  141. mipInt.assign( max( mipInt, cubeUV_minMipLevel ) );
  142. const faceSize = float( exp2( mipInt ) ).toVar();
  143. const uv = vec2( getUV( direction, face ).mul( faceSize.sub( 2.0 ) ).add( 1.0 ) ).toVar();
  144. If( face.greaterThan( 2.0 ), () => {
  145. uv.y.addAssign( faceSize );
  146. face.subAssign( 3.0 );
  147. } );
  148. uv.x.addAssign( face.mul( faceSize ) );
  149. uv.x.addAssign( filterInt.mul( mul( 3.0, cubeUV_minTileSize ) ) );
  150. uv.y.addAssign( mul( 4.0, exp2( CUBEUV_MAX_MIP ).sub( faceSize ) ) );
  151. uv.x.mulAssign( CUBEUV_TEXEL_WIDTH );
  152. uv.y.mulAssign( CUBEUV_TEXEL_HEIGHT );
  153. return envMap.uv( uv ).grad( vec2(), vec2() ); // disable anisotropic filtering
  154. } );
  155. const getSample = tslFn( ( { envMap, mipInt, outputDirection, theta, axis, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) => {
  156. const cosTheta = cos( theta );
  157. // Rodrigues' axis-angle rotation
  158. const sampleDirection = outputDirection.mul( cosTheta )
  159. .add( axis.cross( outputDirection ).mul( sin( theta ) ) )
  160. .add( axis.mul( axis.dot( outputDirection ).mul( cosTheta.oneMinus() ) ) );
  161. return bilinearCubeUV( envMap, sampleDirection, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP );
  162. } );
  163. export const blur = tslFn( ( { n, latitudinal, poleAxis, outputDirection, weights, samples, dTheta, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) => {
  164. const axis = vec3( cond( latitudinal, poleAxis, cross( poleAxis, outputDirection ) ) ).toVar();
  165. If( all( axis.equals( vec3( 0.0 ) ) ), () => {
  166. axis.assign( vec3( outputDirection.z, 0.0, outputDirection.x.negate() ) );
  167. } );
  168. axis.assign( normalize( axis ) );
  169. const gl_FragColor = vec3().toVar();
  170. gl_FragColor.addAssign( weights.element( int( 0 ) ).mul( getSample( { theta: 0.0, axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) );
  171. loop( { start: int( 1 ), end: n }, ( { i } ) => {
  172. If( i.greaterThanEqual( samples ), () => {
  173. Break();
  174. } );
  175. const theta = float( dTheta.mul( float( i ) ) ).toVar();
  176. gl_FragColor.addAssign( weights.element( i ).mul( getSample( { theta: theta.mul( - 1.0 ), axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) );
  177. gl_FragColor.addAssign( weights.element( i ).mul( getSample( { theta, axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) );
  178. } );
  179. return vec4( gl_FragColor, 1 );
  180. } );