| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 | /** * This class generates custom mipmaps for a roughness map by encoding the lost variation in the * normal map mip levels as increased roughness in the corresponding roughness mip levels. This * helps with rendering accuracy for MeshStandardMaterial, and also helps with anti-aliasing when * using PMREM. If the normal map is larger than the roughness map, the roughness map will be * enlarged to match the dimensions of the normal map. */import {	MathUtils,	Mesh,	NoBlending,	OrthographicCamera,	PlaneGeometry,	RawShaderMaterial,	Vector2,	WebGLRenderTarget} from '../../../build/three.module.js';const _mipmapMaterial = _getMipmapMaterial();const _mesh = new Mesh( new PlaneGeometry( 2, 2 ), _mipmapMaterial );const _flatCamera = new OrthographicCamera( 0, 1, 0, 1, 0, 1 );let _tempTarget = null;let _renderer = null;class RoughnessMipmapper {	constructor( renderer ) {		_renderer = renderer;		_renderer.compile( _mesh, _flatCamera );	}	generateMipmaps( material ) {		if ( 'roughnessMap' in material === false ) return;		const { roughnessMap, normalMap } = material;		if ( roughnessMap === null || normalMap === null || ! roughnessMap.generateMipmaps || material.userData.roughnessUpdated ) return;		material.userData.roughnessUpdated = true;		let width = Math.max( roughnessMap.image.width, normalMap.image.width );		let height = Math.max( roughnessMap.image.height, normalMap.image.height );		if ( ! MathUtils.isPowerOfTwo( width ) || ! MathUtils.isPowerOfTwo( height ) ) return;		const oldTarget = _renderer.getRenderTarget();		const autoClear = _renderer.autoClear;		_renderer.autoClear = false;		if ( _tempTarget === null || _tempTarget.width !== width || _tempTarget.height !== height ) {			if ( _tempTarget !== null ) _tempTarget.dispose();			_tempTarget = new WebGLRenderTarget( width, height, { depthBuffer: false } );			_tempTarget.scissorTest = true;		}		if ( width !== roughnessMap.image.width || height !== roughnessMap.image.height ) {			const params = {				wrapS: roughnessMap.wrapS,				wrapT: roughnessMap.wrapT,				magFilter: roughnessMap.magFilter,				minFilter: roughnessMap.minFilter,				depthBuffer: false			};			const newRoughnessTarget = new WebGLRenderTarget( width, height, params );			newRoughnessTarget.texture.generateMipmaps = true;			// Setting the render target causes the memory to be allocated.			_renderer.setRenderTarget( newRoughnessTarget );			material.roughnessMap = newRoughnessTarget.texture;			if ( material.metalnessMap == roughnessMap ) material.metalnessMap = material.roughnessMap;			if ( material.aoMap == roughnessMap ) material.aoMap = material.roughnessMap;			// Copy UV transform parameters			material.roughnessMap.offset.copy( roughnessMap.offset );			material.roughnessMap.repeat.copy( roughnessMap.repeat );			material.roughnessMap.center.copy( roughnessMap.center );			material.roughnessMap.rotation = roughnessMap.rotation;			material.roughnessMap.image = roughnessMap.image;			material.roughnessMap.matrixAutoUpdate = roughnessMap.matrixAutoUpdate;			material.roughnessMap.matrix.copy( roughnessMap.matrix );		}		_mipmapMaterial.uniforms.roughnessMap.value = roughnessMap;		_mipmapMaterial.uniforms.normalMap.value = normalMap;		const position = new Vector2( 0, 0 );		const texelSize = _mipmapMaterial.uniforms.texelSize.value;		for ( let mip = 0; width >= 1 && height >= 1; ++ mip, width /= 2, height /= 2 ) {			// Rendering to a mip level is not allowed in webGL1. Instead we must set			// up a secondary texture to write the result to, then copy it back to the			// proper mipmap level.			texelSize.set( 1.0 / width, 1.0 / height );			if ( mip == 0 ) texelSize.set( 0.0, 0.0 );			_tempTarget.viewport.set( position.x, position.y, width, height );			_tempTarget.scissor.set( position.x, position.y, width, height );			_renderer.setRenderTarget( _tempTarget );			_renderer.render( _mesh, _flatCamera );			_renderer.copyFramebufferToTexture( position, material.roughnessMap, mip );			_mipmapMaterial.uniforms.roughnessMap.value = material.roughnessMap;		}		if ( roughnessMap !== material.roughnessMap ) roughnessMap.dispose();		_renderer.setRenderTarget( oldTarget );		_renderer.autoClear = autoClear;	}	dispose() {		_mipmapMaterial.dispose();		_mesh.geometry.dispose();		if ( _tempTarget != null ) _tempTarget.dispose();	}}function _getMipmapMaterial() {	const shaderMaterial = new RawShaderMaterial( {		uniforms: {			roughnessMap: { value: null },			normalMap: { value: null },			texelSize: { value: new Vector2( 1, 1 ) }		},		vertexShader: /* glsl */`			precision mediump float;			precision mediump int;			attribute vec3 position;			attribute vec2 uv;			varying vec2 vUv;			void main() {				vUv = uv;				gl_Position = vec4( position, 1.0 );			}		`,		fragmentShader: /* glsl */`			precision mediump float;			precision mediump int;			varying vec2 vUv;			uniform sampler2D roughnessMap;			uniform sampler2D normalMap;			uniform vec2 texelSize;			#define ENVMAP_TYPE_CUBE_UV			vec4 envMapTexelToLinear( vec4 a ) { return a; }			#include <cube_uv_reflection_fragment>			float roughnessToVariance( float roughness ) {				float variance = 0.0;				if ( roughness >= r1 ) {					variance = ( r0 - roughness ) * ( v1 - v0 ) / ( r0 - r1 ) + v0;				} else if ( roughness >= r4 ) {					variance = ( r1 - roughness ) * ( v4 - v1 ) / ( r1 - r4 ) + v1;				} else if ( roughness >= r5 ) {					variance = ( r4 - roughness ) * ( v5 - v4 ) / ( r4 - r5 ) + v4;				} else {					float roughness2 = roughness * roughness;					variance = 1.79 * roughness2 * roughness2;				}				return variance;			}			float varianceToRoughness( float variance ) {				float roughness = 0.0;				if ( variance >= v1 ) {					roughness = ( v0 - variance ) * ( r1 - r0 ) / ( v0 - v1 ) + r0;				} else if ( variance >= v4 ) {					roughness = ( v1 - variance ) * ( r4 - r1 ) / ( v1 - v4 ) + r1;				} else if ( variance >= v5 ) {					roughness = ( v4 - variance ) * ( r5 - r4 ) / ( v4 - v5 ) + r4;				} else {					roughness = pow( 0.559 * variance, 0.25 ); // 0.559 = 1.0 / 1.79				}				return roughness;			}			void main() {				gl_FragColor = texture2D( roughnessMap, vUv, - 1.0 );				if ( texelSize.x == 0.0 ) return;				float roughness = gl_FragColor.g;				float variance = roughnessToVariance( roughness );				vec3 avgNormal;				for ( float x = - 1.0; x < 2.0; x += 2.0 ) {					for ( float y = - 1.0; y < 2.0; y += 2.0 ) {						vec2 uv = vUv + vec2( x, y ) * 0.25 * texelSize;						avgNormal += normalize( texture2D( normalMap, uv, - 1.0 ).xyz - 0.5 );					}				}				variance += 1.0 - 0.25 * length( avgNormal );				gl_FragColor.g = varianceToRoughness( variance );			}		`,		blending: NoBlending,		depthTest: false,		depthWrite: false	} );	shaderMaterial.type = 'RoughnessMipmapper';	return shaderMaterial;}export { RoughnessMipmapper };
 |