PMREMGenerator.js 19 KB

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