RoughnessMipmapper.js 6.4 KB

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