RoughnessMipmapper.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. ( function () {
  2. /**
  3. * This class generates custom mipmaps for a roughness map by encoding the lost variation in the
  4. * normal map mip levels as increased roughness in the corresponding roughness mip levels. This
  5. * helps with rendering accuracy for MeshStandardMaterial, and also helps with anti-aliasing when
  6. * using PMREM. If the normal map is larger than the roughness map, the roughness map will be
  7. * enlarged to match the dimensions of the normal map.
  8. */
  9. const _mipmapMaterial = _getMipmapMaterial();
  10. const _mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), _mipmapMaterial );
  11. const _flatCamera = new THREE.OrthographicCamera( 0, 1, 0, 1, 0, 1 );
  12. let _tempTarget = null;
  13. let _renderer = null;
  14. class RoughnessMipmapper {
  15. constructor( renderer ) {
  16. _renderer = renderer;
  17. _renderer.compile( _mesh, _flatCamera );
  18. }
  19. generateMipmaps( material ) {
  20. if ( 'roughnessMap' in material === false ) return;
  21. const {
  22. roughnessMap,
  23. normalMap
  24. } = material;
  25. if ( roughnessMap === null || normalMap === null || ! roughnessMap.generateMipmaps || material.userData.roughnessUpdated ) return;
  26. material.userData.roughnessUpdated = true;
  27. let width = Math.max( roughnessMap.image.width, normalMap.image.width );
  28. let height = Math.max( roughnessMap.image.height, normalMap.image.height );
  29. if ( ! THREE.MathUtils.isPowerOfTwo( width ) || ! THREE.MathUtils.isPowerOfTwo( height ) ) return;
  30. const oldTarget = _renderer.getRenderTarget();
  31. const autoClear = _renderer.autoClear;
  32. _renderer.autoClear = false;
  33. if ( _tempTarget === null || _tempTarget.width !== width || _tempTarget.height !== height ) {
  34. if ( _tempTarget !== null ) _tempTarget.dispose();
  35. _tempTarget = new THREE.WebGLRenderTarget( width, height, {
  36. depthBuffer: false
  37. } );
  38. _tempTarget.scissorTest = true;
  39. }
  40. const newRoughnessTexture = new THREE.FramebufferTexture( width, height, roughnessMap.format );
  41. newRoughnessTexture.wrapS = roughnessMap.wrapS;
  42. newRoughnessTexture.wrapT = roughnessMap.wrapT;
  43. newRoughnessTexture.minFilter = roughnessMap.minFilter;
  44. newRoughnessTexture.magFilter = roughnessMap.magFilter;
  45. material.roughnessMap = newRoughnessTexture;
  46. if ( material.metalnessMap == roughnessMap ) material.metalnessMap = material.roughnessMap;
  47. if ( material.aoMap == roughnessMap ) material.aoMap = material.roughnessMap; // Copy UV transform parameters
  48. material.roughnessMap.offset.copy( roughnessMap.offset );
  49. material.roughnessMap.repeat.copy( roughnessMap.repeat );
  50. material.roughnessMap.center.copy( roughnessMap.center );
  51. material.roughnessMap.rotation = roughnessMap.rotation;
  52. material.roughnessMap.image = roughnessMap.image; // required for USDZExporter, see #22741
  53. material.roughnessMap.matrixAutoUpdate = roughnessMap.matrixAutoUpdate;
  54. material.roughnessMap.matrix.copy( roughnessMap.matrix );
  55. _mipmapMaterial.uniforms.roughnessMap.value = roughnessMap;
  56. _mipmapMaterial.uniforms.normalMap.value = normalMap;
  57. const position = new THREE.Vector2( 0, 0 );
  58. const texelSize = _mipmapMaterial.uniforms.texelSize.value;
  59. for ( let mip = 0; width >= 1 && height >= 1; ++ mip, width /= 2, height /= 2 ) {
  60. // Rendering to a mip level is not allowed in webGL1. Instead we must set
  61. // up a secondary texture to write the result to, then copy it back to the
  62. // proper mipmap level.
  63. texelSize.set( 1.0 / width, 1.0 / height );
  64. if ( mip == 0 ) texelSize.set( 0.0, 0.0 );
  65. _tempTarget.viewport.set( position.x, position.y, width, height );
  66. _tempTarget.scissor.set( position.x, position.y, width, height );
  67. _renderer.setRenderTarget( _tempTarget );
  68. _renderer.render( _mesh, _flatCamera );
  69. _renderer.copyFramebufferToTexture( position, material.roughnessMap, mip );
  70. _mipmapMaterial.uniforms.roughnessMap.value = material.roughnessMap;
  71. }
  72. roughnessMap.dispose();
  73. _renderer.setRenderTarget( oldTarget );
  74. _renderer.autoClear = autoClear;
  75. }
  76. dispose() {
  77. _mipmapMaterial.dispose();
  78. _mesh.geometry.dispose();
  79. if ( _tempTarget != null ) _tempTarget.dispose();
  80. }
  81. }
  82. function _getMipmapMaterial() {
  83. const shaderMaterial = new THREE.RawShaderMaterial( {
  84. uniforms: {
  85. roughnessMap: {
  86. value: null
  87. },
  88. normalMap: {
  89. value: null
  90. },
  91. texelSize: {
  92. value: new THREE.Vector2( 1, 1 )
  93. }
  94. },
  95. vertexShader:
  96. /* glsl */
  97. `
  98. precision mediump float;
  99. precision mediump int;
  100. attribute vec3 position;
  101. attribute vec2 uv;
  102. varying vec2 vUv;
  103. void main() {
  104. vUv = uv;
  105. gl_Position = vec4( position, 1.0 );
  106. }
  107. `,
  108. fragmentShader:
  109. /* glsl */
  110. `
  111. precision mediump float;
  112. precision mediump int;
  113. varying vec2 vUv;
  114. uniform sampler2D roughnessMap;
  115. uniform sampler2D normalMap;
  116. uniform vec2 texelSize;
  117. #define ENVMAP_TYPE_CUBE_UV
  118. vec4 envMapTexelToLinear( vec4 a ) { return a; }
  119. #include <cube_uv_reflection_fragment>
  120. float roughnessToVariance( float roughness ) {
  121. float variance = 0.0;
  122. if ( roughness >= r1 ) {
  123. variance = ( r0 - roughness ) * ( v1 - v0 ) / ( r0 - r1 ) + v0;
  124. } else if ( roughness >= r4 ) {
  125. variance = ( r1 - roughness ) * ( v4 - v1 ) / ( r1 - r4 ) + v1;
  126. } else if ( roughness >= r5 ) {
  127. variance = ( r4 - roughness ) * ( v5 - v4 ) / ( r4 - r5 ) + v4;
  128. } else {
  129. float roughness2 = roughness * roughness;
  130. variance = 1.79 * roughness2 * roughness2;
  131. }
  132. return variance;
  133. }
  134. float varianceToRoughness( float variance ) {
  135. float roughness = 0.0;
  136. if ( variance >= v1 ) {
  137. roughness = ( v0 - variance ) * ( r1 - r0 ) / ( v0 - v1 ) + r0;
  138. } else if ( variance >= v4 ) {
  139. roughness = ( v1 - variance ) * ( r4 - r1 ) / ( v1 - v4 ) + r1;
  140. } else if ( variance >= v5 ) {
  141. roughness = ( v4 - variance ) * ( r5 - r4 ) / ( v4 - v5 ) + r4;
  142. } else {
  143. roughness = pow( 0.559 * variance, 0.25 ); // 0.559 = 1.0 / 1.79
  144. }
  145. return roughness;
  146. }
  147. void main() {
  148. gl_FragColor = texture2D( roughnessMap, vUv, - 1.0 );
  149. if ( texelSize.x == 0.0 ) return;
  150. float roughness = gl_FragColor.g;
  151. float variance = roughnessToVariance( roughness );
  152. vec3 avgNormal;
  153. for ( float x = - 1.0; x < 2.0; x += 2.0 ) {
  154. for ( float y = - 1.0; y < 2.0; y += 2.0 ) {
  155. vec2 uv = vUv + vec2( x, y ) * 0.25 * texelSize;
  156. avgNormal += normalize( texture2D( normalMap, uv, - 1.0 ).xyz - 0.5 );
  157. }
  158. }
  159. variance += 1.0 - 0.25 * length( avgNormal );
  160. gl_FragColor.g = varianceToRoughness( variance );
  161. }
  162. `,
  163. blending: THREE.NoBlending,
  164. depthTest: false,
  165. depthWrite: false
  166. } );
  167. shaderMaterial.type = 'RoughnessMipmapper';
  168. return shaderMaterial;
  169. }
  170. THREE.RoughnessMipmapper = RoughnessMipmapper;
  171. } )();