PMREMGenerator.js 21 KB

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