PMREMGenerator.js 20 KB

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