2
0

RoughnessMipmapper.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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. THREE.RoughnessMipmapper = ( function () {
  11. var _mipmapMaterial = _getMipmapMaterial();
  12. var _scene = new THREE.Scene();
  13. _scene.add( new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), _mipmapMaterial ) );
  14. var _flatCamera = new THREE.OrthographicCamera( 0, 1, 0, 1, 0, 1 );
  15. var _tempTarget = null;
  16. var _renderer = null;
  17. // constructor
  18. var RoughnessMipmapper = function ( renderer ) {
  19. _renderer = renderer;
  20. };
  21. RoughnessMipmapper.prototype = {
  22. constructor: RoughnessMipmapper,
  23. generateMipmaps: function ( material ) {
  24. var { roughnessMap, normalMap } = material;
  25. if ( roughnessMap == null || normalMap == null || ! roughnessMap.generateMipmaps ||
  26. material.userData.roughnessUpdated ) return;
  27. material.userData.roughnessUpdated = true;
  28. var width = Math.max( roughnessMap.image.width, normalMap.image.width );
  29. var height = Math.max( roughnessMap.image.height, normalMap.image.height );
  30. if ( ! THREE.Math.isPowerOfTwo( width ) || ! THREE.Math.isPowerOfTwo( height ) ) return;
  31. var 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, { depthBuffer: false, stencilBuffer: false } );
  36. }
  37. if ( width !== roughnessMap.image.width || height !== roughnessMap.image.height ) {
  38. var newRoughnessTarget = new THREE.WebGLRenderTarget( width, height, {
  39. minFilter: THREE.LinearMipMapLinearFilter,
  40. depthBuffer: false,
  41. stencilBuffer: false
  42. } );
  43. newRoughnessTarget.texture.generateMipmaps = true;
  44. // Setting the render target causes the memory to be allocated.
  45. _renderer.setRenderTarget( newRoughnessTarget );
  46. material.roughnessMap = newRoughnessTarget.texture;
  47. if ( material.metalnessMap == roughnessMap ) material.metalnessMap = material.roughnessMap;
  48. if ( material.aoMap == roughnessMap ) material.aoMap = material.roughnessMap;
  49. }
  50. _renderer.setRenderTarget( _tempTarget );
  51. _mipmapMaterial.uniforms.roughnessMap.value = roughnessMap;
  52. _mipmapMaterial.uniforms.normalMap.value = normalMap;
  53. var dpr = _renderer.getPixelRatio();
  54. var position = new THREE.Vector2( 0, 0 );
  55. var texelSize = _mipmapMaterial.uniforms.texelSize.value;
  56. for ( var mip = 0; width >= 1 && height >= 1;
  57. ++ 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. _renderer.setViewport( position.x, position.y, width / dpr, height / dpr );
  64. _renderer.render( _scene, _flatCamera );
  65. _renderer.copyFramebufferToTexture( position, material.roughnessMap, mip );
  66. _mipmapMaterial.uniforms.roughnessMap.value = material.roughnessMap;
  67. }
  68. if ( roughnessMap !== material.roughnessMap ) roughnessMap.dispose();
  69. _renderer.autoClear = autoClear;
  70. },
  71. dispose: function ( ) {
  72. _mipmapMaterial.dispose();
  73. _scene.children[ 0 ].geometry.dispose();
  74. if ( _tempTarget != null ) _tempTarget.dispose();
  75. }
  76. };
  77. function _getMipmapMaterial() {
  78. var shaderMaterial = new THREE.RawShaderMaterial( {
  79. uniforms: {
  80. roughnessMap: { value: null },
  81. normalMap: { value: null },
  82. texelSize: { value: new THREE.Vector2( 1, 1 ) }
  83. },
  84. vertexShader: `
  85. precision mediump float;
  86. precision mediump int;
  87. attribute vec3 position;
  88. attribute vec2 uv;
  89. varying vec2 vUv;
  90. void main() {
  91. vUv = uv;
  92. gl_Position = vec4( position, 1.0 );
  93. }
  94. `,
  95. fragmentShader: `
  96. precision mediump float;
  97. precision mediump int;
  98. varying vec2 vUv;
  99. uniform sampler2D roughnessMap;
  100. uniform sampler2D normalMap;
  101. uniform vec2 texelSize;
  102. #define ENVMAP_TYPE_CUBE_UV
  103. #include <cube_uv_reflection_fragment>
  104. void main() {
  105. gl_FragColor = texture2D(roughnessMap, vUv, -1.0);
  106. if (texelSize.x == 0.0) return;
  107. float roughness = gl_FragColor.g;
  108. float variance = roughnessToVariance(roughness);
  109. vec3 avgNormal;
  110. for (float x = -1.0; x < 2.0; x += 2.0) {
  111. for (float y = -1.0; y < 2.0; y += 2.0) {
  112. vec2 uv = vUv + vec2(x, y) * 0.25 * texelSize;
  113. avgNormal += normalize(texture2D(normalMap, uv, -1.0).xyz - 0.5);
  114. }
  115. }
  116. variance += 1.0 - 0.25 * length(avgNormal);
  117. gl_FragColor.g = varianceToRoughness(variance);
  118. }
  119. `,
  120. blending: THREE.NoBlending,
  121. depthTest: false,
  122. depthWrite: false
  123. } );
  124. shaderMaterial.type = 'RoughnessMipmapper';
  125. return shaderMaterial;
  126. }
  127. return RoughnessMipmapper;
  128. } )();