RoughnessMipmapper.js 6.1 KB

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