PMREMGenerator.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  1. import {
  2. CubeReflectionMapping,
  3. CubeRefractionMapping,
  4. CubeUVReflectionMapping,
  5. LinearFilter,
  6. NoToneMapping,
  7. NoBlending,
  8. RGBAFormat,
  9. HalfFloatType,
  10. BackSide,
  11. LinearSRGBColorSpace
  12. } from '../constants.js';
  13. import { BufferAttribute } from '../core/BufferAttribute.js';
  14. import { BufferGeometry } from '../core/BufferGeometry.js';
  15. import { Mesh } from '../objects/Mesh.js';
  16. import { OrthographicCamera } from '../cameras/OrthographicCamera.js';
  17. import { PerspectiveCamera } from '../cameras/PerspectiveCamera.js';
  18. import { ShaderMaterial } from '../materials/ShaderMaterial.js';
  19. import { Vector3 } from '../math/Vector3.js';
  20. import { Color } from '../math/Color.js';
  21. import { WebGLRenderTarget } from '../renderers/WebGLRenderTarget.js';
  22. import { MeshBasicMaterial } from '../materials/MeshBasicMaterial.js';
  23. import { BoxGeometry } from '../geometries/BoxGeometry.js';
  24. const LOD_MIN = 4;
  25. // The standard deviations (radians) associated with the extra mips. These are
  26. // chosen to approximate a Trowbridge-Reitz distribution function times the
  27. // geometric shadowing function. These sigma values squared must match the
  28. // variance #defines in cube_uv_reflection_fragment.glsl.js.
  29. const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ];
  30. // The maximum length of the blur for loop. Smaller sigmas will use fewer
  31. // samples and exit early, but not recompile the shader.
  32. const MAX_SAMPLES = 20;
  33. const _flatCamera = /*@__PURE__*/ new OrthographicCamera();
  34. const _clearColor = /*@__PURE__*/ new Color();
  35. let _oldTarget = null;
  36. let _oldActiveCubeFace = 0;
  37. let _oldActiveMipmapLevel = 0;
  38. // Golden Ratio
  39. const PHI = ( 1 + Math.sqrt( 5 ) ) / 2;
  40. const INV_PHI = 1 / PHI;
  41. // Vertices of a dodecahedron (except the opposites, which represent the
  42. // same axis), used as axis directions evenly spread on a sphere.
  43. const _axisDirections = [
  44. /*@__PURE__*/ new Vector3( 1, 1, 1 ),
  45. /*@__PURE__*/ new Vector3( - 1, 1, 1 ),
  46. /*@__PURE__*/ new Vector3( 1, 1, - 1 ),
  47. /*@__PURE__*/ new Vector3( - 1, 1, - 1 ),
  48. /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ),
  49. /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ),
  50. /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ),
  51. /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ),
  52. /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ),
  53. /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ) ];
  54. /**
  55. * This class generates a Prefiltered, Mipmapped Radiance Environment Map
  56. * (PMREM) from a cubeMap environment texture. This allows different levels of
  57. * blur to be quickly accessed based on material roughness. It is packed into a
  58. * special CubeUV format that allows us to perform custom interpolation so that
  59. * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap
  60. * chain, it only goes down to the LOD_MIN level (above), and then creates extra
  61. * even more filtered 'mips' at the same LOD_MIN resolution, associated with
  62. * higher roughness levels. In this way we maintain resolution to smoothly
  63. * interpolate diffuse lighting while limiting sampling computation.
  64. *
  65. * Paper: Fast, Accurate Image-Based Lighting
  66. * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view
  67. */
  68. class PMREMGenerator {
  69. constructor( renderer ) {
  70. this._renderer = renderer;
  71. this._pingPongRenderTarget = null;
  72. this._lodMax = 0;
  73. this._cubeSize = 0;
  74. this._lodPlanes = [];
  75. this._sizeLods = [];
  76. this._sigmas = [];
  77. this._blurMaterial = null;
  78. this._cubemapMaterial = null;
  79. this._equirectMaterial = null;
  80. this._compileMaterial( this._blurMaterial );
  81. }
  82. /**
  83. * Generates a PMREM from a supplied Scene, which can be faster than using an
  84. * image if networking bandwidth is low. Optional sigma specifies a blur radius
  85. * in radians to be applied to the scene before PMREM generation. Optional near
  86. * and far planes ensure the scene is rendered in its entirety (the cubeCamera
  87. * is placed at the origin).
  88. */
  89. fromScene( scene, sigma = 0, near = 0.1, far = 100 ) {
  90. _oldTarget = this._renderer.getRenderTarget();
  91. _oldActiveCubeFace = this._renderer.getActiveCubeFace();
  92. _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel();
  93. this._setSize( 256 );
  94. const cubeUVRenderTarget = this._allocateTargets();
  95. cubeUVRenderTarget.depthBuffer = true;
  96. this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget );
  97. if ( sigma > 0 ) {
  98. this._blur( cubeUVRenderTarget, 0, 0, sigma );
  99. }
  100. this._applyPMREM( cubeUVRenderTarget );
  101. this._cleanup( cubeUVRenderTarget );
  102. return cubeUVRenderTarget;
  103. }
  104. /**
  105. * Generates a PMREM from an equirectangular texture, which can be either LDR
  106. * or HDR. The ideal input image size is 1k (1024 x 512),
  107. * as this matches best with the 256 x 256 cubemap output.
  108. * The smallest supported equirectangular image size is 64 x 32.
  109. */
  110. fromEquirectangular( equirectangular, renderTarget = null ) {
  111. return this._fromTexture( equirectangular, renderTarget );
  112. }
  113. /**
  114. * Generates a PMREM from an cubemap texture, which can be either LDR
  115. * or HDR. The ideal input cube size is 256 x 256,
  116. * as this matches best with the 256 x 256 cubemap output.
  117. * The smallest supported cube size is 16 x 16.
  118. */
  119. fromCubemap( cubemap, renderTarget = null ) {
  120. return this._fromTexture( cubemap, renderTarget );
  121. }
  122. /**
  123. * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during
  124. * your texture's network fetch for increased concurrency.
  125. */
  126. compileCubemapShader() {
  127. if ( this._cubemapMaterial === null ) {
  128. this._cubemapMaterial = _getCubemapMaterial();
  129. this._compileMaterial( this._cubemapMaterial );
  130. }
  131. }
  132. /**
  133. * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during
  134. * your texture's network fetch for increased concurrency.
  135. */
  136. compileEquirectangularShader() {
  137. if ( this._equirectMaterial === null ) {
  138. this._equirectMaterial = _getEquirectMaterial();
  139. this._compileMaterial( this._equirectMaterial );
  140. }
  141. }
  142. /**
  143. * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class,
  144. * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on
  145. * one of them will cause any others to also become unusable.
  146. */
  147. dispose() {
  148. this._dispose();
  149. if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose();
  150. if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose();
  151. }
  152. // private interface
  153. _setSize( cubeSize ) {
  154. this._lodMax = Math.floor( Math.log2( cubeSize ) );
  155. this._cubeSize = Math.pow( 2, this._lodMax );
  156. }
  157. _dispose() {
  158. if ( this._blurMaterial !== null ) this._blurMaterial.dispose();
  159. if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose();
  160. for ( let i = 0; i < this._lodPlanes.length; i ++ ) {
  161. this._lodPlanes[ i ].dispose();
  162. }
  163. }
  164. _cleanup( outputTarget ) {
  165. this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel );
  166. outputTarget.scissorTest = false;
  167. _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height );
  168. }
  169. _fromTexture( texture, renderTarget ) {
  170. if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) {
  171. this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) );
  172. } else { // Equirectangular
  173. this._setSize( texture.image.width / 4 );
  174. }
  175. _oldTarget = this._renderer.getRenderTarget();
  176. _oldActiveCubeFace = this._renderer.getActiveCubeFace();
  177. _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel();
  178. const cubeUVRenderTarget = renderTarget || this._allocateTargets();
  179. this._textureToCubeUV( texture, cubeUVRenderTarget );
  180. this._applyPMREM( cubeUVRenderTarget );
  181. this._cleanup( cubeUVRenderTarget );
  182. return cubeUVRenderTarget;
  183. }
  184. _allocateTargets() {
  185. const width = 3 * Math.max( this._cubeSize, 16 * 7 );
  186. const height = 4 * this._cubeSize;
  187. const params = {
  188. magFilter: LinearFilter,
  189. minFilter: LinearFilter,
  190. generateMipmaps: false,
  191. type: HalfFloatType,
  192. format: RGBAFormat,
  193. colorSpace: LinearSRGBColorSpace,
  194. depthBuffer: false
  195. };
  196. const cubeUVRenderTarget = _createRenderTarget( width, height, params );
  197. if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) {
  198. if ( this._pingPongRenderTarget !== null ) {
  199. this._dispose();
  200. }
  201. this._pingPongRenderTarget = _createRenderTarget( width, height, params );
  202. const { _lodMax } = this;
  203. ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas } = _createPlanes( _lodMax ) );
  204. this._blurMaterial = _getBlurShader( _lodMax, width, height );
  205. }
  206. return cubeUVRenderTarget;
  207. }
  208. _compileMaterial( material ) {
  209. const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material );
  210. this._renderer.compile( tmpMesh, _flatCamera );
  211. }
  212. _sceneToCubeUV( scene, near, far, cubeUVRenderTarget ) {
  213. const fov = 90;
  214. const aspect = 1;
  215. const cubeCamera = new PerspectiveCamera( fov, aspect, near, far );
  216. const upSign = [ 1, - 1, 1, 1, 1, 1 ];
  217. const forwardSign = [ 1, 1, 1, - 1, - 1, - 1 ];
  218. const renderer = this._renderer;
  219. const originalAutoClear = renderer.autoClear;
  220. const toneMapping = renderer.toneMapping;
  221. renderer.getClearColor( _clearColor );
  222. renderer.toneMapping = NoToneMapping;
  223. renderer.autoClear = false;
  224. const backgroundMaterial = new MeshBasicMaterial( {
  225. name: 'PMREM.Background',
  226. side: BackSide,
  227. depthWrite: false,
  228. depthTest: false,
  229. } );
  230. const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial );
  231. let useSolidColor = false;
  232. const background = scene.background;
  233. if ( background ) {
  234. if ( background.isColor ) {
  235. backgroundMaterial.color.copy( background );
  236. scene.background = null;
  237. useSolidColor = true;
  238. }
  239. } else {
  240. backgroundMaterial.color.copy( _clearColor );
  241. useSolidColor = true;
  242. }
  243. for ( let i = 0; i < 6; i ++ ) {
  244. const col = i % 3;
  245. if ( col === 0 ) {
  246. cubeCamera.up.set( 0, upSign[ i ], 0 );
  247. cubeCamera.lookAt( forwardSign[ i ], 0, 0 );
  248. } else if ( col === 1 ) {
  249. cubeCamera.up.set( 0, 0, upSign[ i ] );
  250. cubeCamera.lookAt( 0, forwardSign[ i ], 0 );
  251. } else {
  252. cubeCamera.up.set( 0, upSign[ i ], 0 );
  253. cubeCamera.lookAt( 0, 0, forwardSign[ i ] );
  254. }
  255. const size = this._cubeSize;
  256. _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size );
  257. renderer.setRenderTarget( cubeUVRenderTarget );
  258. if ( useSolidColor ) {
  259. renderer.render( backgroundBox, cubeCamera );
  260. }
  261. renderer.render( scene, cubeCamera );
  262. }
  263. backgroundBox.geometry.dispose();
  264. backgroundBox.material.dispose();
  265. renderer.toneMapping = toneMapping;
  266. renderer.autoClear = originalAutoClear;
  267. scene.background = background;
  268. }
  269. _textureToCubeUV( texture, cubeUVRenderTarget ) {
  270. const renderer = this._renderer;
  271. const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping );
  272. if ( isCubeTexture ) {
  273. if ( this._cubemapMaterial === null ) {
  274. this._cubemapMaterial = _getCubemapMaterial();
  275. }
  276. this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? - 1 : 1;
  277. } else {
  278. if ( this._equirectMaterial === null ) {
  279. this._equirectMaterial = _getEquirectMaterial();
  280. }
  281. }
  282. const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial;
  283. const mesh = new Mesh( this._lodPlanes[ 0 ], material );
  284. const uniforms = material.uniforms;
  285. uniforms[ 'envMap' ].value = texture;
  286. const size = this._cubeSize;
  287. _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size );
  288. renderer.setRenderTarget( cubeUVRenderTarget );
  289. renderer.render( mesh, _flatCamera );
  290. }
  291. _applyPMREM( cubeUVRenderTarget ) {
  292. const renderer = this._renderer;
  293. const autoClear = renderer.autoClear;
  294. renderer.autoClear = false;
  295. for ( let i = 1; i < this._lodPlanes.length; i ++ ) {
  296. const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] );
  297. const poleAxis = _axisDirections[ ( i - 1 ) % _axisDirections.length ];
  298. this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis );
  299. }
  300. renderer.autoClear = autoClear;
  301. }
  302. /**
  303. * This is a two-pass Gaussian blur for a cubemap. Normally this is done
  304. * vertically and horizontally, but this breaks down on a cube. Here we apply
  305. * the blur latitudinally (around the poles), and then longitudinally (towards
  306. * the poles) to approximate the orthogonally-separable blur. It is least
  307. * accurate at the poles, but still does a decent job.
  308. */
  309. _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) {
  310. const pingPongRenderTarget = this._pingPongRenderTarget;
  311. this._halfBlur(
  312. cubeUVRenderTarget,
  313. pingPongRenderTarget,
  314. lodIn,
  315. lodOut,
  316. sigma,
  317. 'latitudinal',
  318. poleAxis );
  319. this._halfBlur(
  320. pingPongRenderTarget,
  321. cubeUVRenderTarget,
  322. lodOut,
  323. lodOut,
  324. sigma,
  325. 'longitudinal',
  326. poleAxis );
  327. }
  328. _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) {
  329. const renderer = this._renderer;
  330. const blurMaterial = this._blurMaterial;
  331. if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) {
  332. console.error(
  333. 'blur direction must be either latitudinal or longitudinal!' );
  334. }
  335. // Number of standard deviations at which to cut off the discrete approximation.
  336. const STANDARD_DEVIATIONS = 3;
  337. const blurMesh = new Mesh( this._lodPlanes[ lodOut ], blurMaterial );
  338. const blurUniforms = blurMaterial.uniforms;
  339. const pixels = this._sizeLods[ lodIn ] - 1;
  340. const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 );
  341. const sigmaPixels = sigmaRadians / radiansPerPixel;
  342. const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES;
  343. if ( samples > MAX_SAMPLES ) {
  344. console.warn( `sigmaRadians, ${
  345. sigmaRadians}, is too large and will clip, as it requested ${
  346. samples} samples when the maximum is set to ${MAX_SAMPLES}` );
  347. }
  348. const weights = [];
  349. let sum = 0;
  350. for ( let i = 0; i < MAX_SAMPLES; ++ i ) {
  351. const x = i / sigmaPixels;
  352. const weight = Math.exp( - x * x / 2 );
  353. weights.push( weight );
  354. if ( i === 0 ) {
  355. sum += weight;
  356. } else if ( i < samples ) {
  357. sum += 2 * weight;
  358. }
  359. }
  360. for ( let i = 0; i < weights.length; i ++ ) {
  361. weights[ i ] = weights[ i ] / sum;
  362. }
  363. blurUniforms[ 'envMap' ].value = targetIn.texture;
  364. blurUniforms[ 'samples' ].value = samples;
  365. blurUniforms[ 'weights' ].value = weights;
  366. blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal';
  367. if ( poleAxis ) {
  368. blurUniforms[ 'poleAxis' ].value = poleAxis;
  369. }
  370. const { _lodMax } = this;
  371. blurUniforms[ 'dTheta' ].value = radiansPerPixel;
  372. blurUniforms[ 'mipInt' ].value = _lodMax - lodIn;
  373. const outputSize = this._sizeLods[ lodOut ];
  374. const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 );
  375. const y = 4 * ( this._cubeSize - outputSize );
  376. _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize );
  377. renderer.setRenderTarget( targetOut );
  378. renderer.render( blurMesh, _flatCamera );
  379. }
  380. }
  381. function _createPlanes( lodMax ) {
  382. const lodPlanes = [];
  383. const sizeLods = [];
  384. const sigmas = [];
  385. let lod = lodMax;
  386. const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length;
  387. for ( let i = 0; i < totalLods; i ++ ) {
  388. const sizeLod = Math.pow( 2, lod );
  389. sizeLods.push( sizeLod );
  390. let sigma = 1.0 / sizeLod;
  391. if ( i > lodMax - LOD_MIN ) {
  392. sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ];
  393. } else if ( i === 0 ) {
  394. sigma = 0;
  395. }
  396. sigmas.push( sigma );
  397. const texelSize = 1.0 / ( sizeLod - 2 );
  398. const min = - texelSize;
  399. const max = 1 + texelSize;
  400. const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ];
  401. const cubeFaces = 6;
  402. const vertices = 6;
  403. const positionSize = 3;
  404. const uvSize = 2;
  405. const faceIndexSize = 1;
  406. const position = new Float32Array( positionSize * vertices * cubeFaces );
  407. const uv = new Float32Array( uvSize * vertices * cubeFaces );
  408. const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces );
  409. for ( let face = 0; face < cubeFaces; face ++ ) {
  410. const x = ( face % 3 ) * 2 / 3 - 1;
  411. const y = face > 2 ? 0 : - 1;
  412. const coordinates = [
  413. x, y, 0,
  414. x + 2 / 3, y, 0,
  415. x + 2 / 3, y + 1, 0,
  416. x, y, 0,
  417. x + 2 / 3, y + 1, 0,
  418. x, y + 1, 0
  419. ];
  420. position.set( coordinates, positionSize * vertices * face );
  421. uv.set( uv1, uvSize * vertices * face );
  422. const fill = [ face, face, face, face, face, face ];
  423. faceIndex.set( fill, faceIndexSize * vertices * face );
  424. }
  425. const planes = new BufferGeometry();
  426. planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) );
  427. planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) );
  428. planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) );
  429. lodPlanes.push( planes );
  430. if ( lod > LOD_MIN ) {
  431. lod --;
  432. }
  433. }
  434. return { lodPlanes, sizeLods, sigmas };
  435. }
  436. function _createRenderTarget( width, height, params ) {
  437. const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params );
  438. cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping;
  439. cubeUVRenderTarget.texture.name = 'PMREM.cubeUv';
  440. cubeUVRenderTarget.scissorTest = true;
  441. return cubeUVRenderTarget;
  442. }
  443. function _setViewport( target, x, y, width, height ) {
  444. target.viewport.set( x, y, width, height );
  445. target.scissor.set( x, y, width, height );
  446. }
  447. function _getBlurShader( lodMax, width, height ) {
  448. const weights = new Float32Array( MAX_SAMPLES );
  449. const poleAxis = new Vector3( 0, 1, 0 );
  450. const shaderMaterial = new ShaderMaterial( {
  451. name: 'SphericalGaussianBlur',
  452. defines: {
  453. 'n': MAX_SAMPLES,
  454. 'CUBEUV_TEXEL_WIDTH': 1.0 / width,
  455. 'CUBEUV_TEXEL_HEIGHT': 1.0 / height,
  456. 'CUBEUV_MAX_MIP': `${lodMax}.0`,
  457. },
  458. uniforms: {
  459. 'envMap': { value: null },
  460. 'samples': { value: 1 },
  461. 'weights': { value: weights },
  462. 'latitudinal': { value: false },
  463. 'dTheta': { value: 0 },
  464. 'mipInt': { value: 0 },
  465. 'poleAxis': { value: poleAxis }
  466. },
  467. vertexShader: _getCommonVertexShader(),
  468. fragmentShader: /* glsl */`
  469. precision mediump float;
  470. precision mediump int;
  471. varying vec3 vOutputDirection;
  472. uniform sampler2D envMap;
  473. uniform int samples;
  474. uniform float weights[ n ];
  475. uniform bool latitudinal;
  476. uniform float dTheta;
  477. uniform float mipInt;
  478. uniform vec3 poleAxis;
  479. #define ENVMAP_TYPE_CUBE_UV
  480. #include <cube_uv_reflection_fragment>
  481. vec3 getSample( float theta, vec3 axis ) {
  482. float cosTheta = cos( theta );
  483. // Rodrigues' axis-angle rotation
  484. vec3 sampleDirection = vOutputDirection * cosTheta
  485. + cross( axis, vOutputDirection ) * sin( theta )
  486. + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );
  487. return bilinearCubeUV( envMap, sampleDirection, mipInt );
  488. }
  489. void main() {
  490. vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );
  491. if ( all( equal( axis, vec3( 0.0 ) ) ) ) {
  492. axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );
  493. }
  494. axis = normalize( axis );
  495. gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
  496. gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );
  497. for ( int i = 1; i < n; i++ ) {
  498. if ( i >= samples ) {
  499. break;
  500. }
  501. float theta = dTheta * float( i );
  502. gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );
  503. gl_FragColor.rgb += weights[ i ] * getSample( theta, axis );
  504. }
  505. }
  506. `,
  507. blending: NoBlending,
  508. depthTest: false,
  509. depthWrite: false
  510. } );
  511. return shaderMaterial;
  512. }
  513. function _getEquirectMaterial() {
  514. return new ShaderMaterial( {
  515. name: 'EquirectangularToCubeUV',
  516. uniforms: {
  517. 'envMap': { value: null }
  518. },
  519. vertexShader: _getCommonVertexShader(),
  520. fragmentShader: /* glsl */`
  521. precision mediump float;
  522. precision mediump int;
  523. varying vec3 vOutputDirection;
  524. uniform sampler2D envMap;
  525. #include <common>
  526. void main() {
  527. vec3 outputDirection = normalize( vOutputDirection );
  528. vec2 uv = equirectUv( outputDirection );
  529. gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );
  530. }
  531. `,
  532. blending: NoBlending,
  533. depthTest: false,
  534. depthWrite: false
  535. } );
  536. }
  537. function _getCubemapMaterial() {
  538. return new ShaderMaterial( {
  539. name: 'CubemapToCubeUV',
  540. uniforms: {
  541. 'envMap': { value: null },
  542. 'flipEnvMap': { value: - 1 }
  543. },
  544. vertexShader: _getCommonVertexShader(),
  545. fragmentShader: /* glsl */`
  546. precision mediump float;
  547. precision mediump int;
  548. uniform float flipEnvMap;
  549. varying vec3 vOutputDirection;
  550. uniform samplerCube envMap;
  551. void main() {
  552. gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );
  553. }
  554. `,
  555. blending: NoBlending,
  556. depthTest: false,
  557. depthWrite: false
  558. } );
  559. }
  560. function _getCommonVertexShader() {
  561. return /* glsl */`
  562. precision mediump float;
  563. precision mediump int;
  564. attribute float faceIndex;
  565. varying vec3 vOutputDirection;
  566. // RH coordinate system; PMREM face-indexing convention
  567. vec3 getDirection( vec2 uv, float face ) {
  568. uv = 2.0 * uv - 1.0;
  569. vec3 direction = vec3( uv, 1.0 );
  570. if ( face == 0.0 ) {
  571. direction = direction.zyx; // ( 1, v, u ) pos x
  572. } else if ( face == 1.0 ) {
  573. direction = direction.xzy;
  574. direction.xz *= -1.0; // ( -u, 1, -v ) pos y
  575. } else if ( face == 2.0 ) {
  576. direction.x *= -1.0; // ( -u, v, 1 ) pos z
  577. } else if ( face == 3.0 ) {
  578. direction = direction.zyx;
  579. direction.xz *= -1.0; // ( -1, v, -u ) neg x
  580. } else if ( face == 4.0 ) {
  581. direction = direction.xzy;
  582. direction.xy *= -1.0; // ( -u, -1, v ) neg y
  583. } else if ( face == 5.0 ) {
  584. direction.z *= -1.0; // ( u, v, -1 ) neg z
  585. }
  586. return direction;
  587. }
  588. void main() {
  589. vOutputDirection = getDirection( uv, faceIndex );
  590. gl_Position = vec4( position, 1.0 );
  591. }
  592. `;
  593. }
  594. export { PMREMGenerator };
粤ICP备19079148号