RoughnessMipmapper.js 5.5 KB

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