LightProbeGrid.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. import {
  2. Box3,
  3. CubeCamera,
  4. FloatType,
  5. HalfFloatType,
  6. LinearFilter,
  7. Mesh,
  8. NearestFilter,
  9. Object3D,
  10. OrthographicCamera,
  11. PlaneGeometry,
  12. RGBAFormat,
  13. Scene,
  14. ShaderMaterial,
  15. Vector3,
  16. Vector4,
  17. WebGL3DRenderTarget,
  18. WebGLCubeRenderTarget,
  19. WebGLRenderTarget
  20. } from 'three';
  21. // Shared fullscreen-quad scene / camera
  22. let _scene = null;
  23. let _camera = null;
  24. let _mesh = null;
  25. // SH projection material (depends on cubemapSize)
  26. let _shMaterial = null;
  27. let _lastCubemapSize = 0;
  28. // Repack materials (one per output sub-volume / texture index)
  29. let _repackMaterials = null;
  30. // Cached bake resources
  31. let _cubeRenderTarget = null;
  32. let _cubeCamera = null;
  33. let _cachedCubemapSize = 0;
  34. let _cachedNear = 0;
  35. let _cachedFar = 0;
  36. // Cached batch render target
  37. let _batchTarget = null;
  38. let _batchTargetProbes = 0;
  39. // Reusable temp objects
  40. const _position = /*@__PURE__*/ new Vector3();
  41. const _size = /*@__PURE__*/ new Vector3();
  42. const _currentViewport = /*@__PURE__*/ new Vector4();
  43. const _currentScissor = /*@__PURE__*/ new Vector4();
  44. // Number of padding texels added at each boundary of every sub-volume in the atlas.
  45. const ATLAS_PADDING = 1;
  46. /**
  47. * A 3D grid of L2 Spherical Harmonic irradiance probes that provides
  48. * position-dependent diffuse global illumination.
  49. *
  50. * Note that this class can only be used with {@link WebGLRenderer}.
  51. * A version for {@link WebGPURenderer} will be added at a later point.
  52. *
  53. * All seven packed SH sub-volumes are stored in a **single** RGBA
  54. * `WebGL3DRenderTarget` using a texture-atlas layout along the Z axis.
  55. * Each sub-volume occupies `( nz + 2 )` atlas slices: one padding slice at
  56. * each end (a copy of the nearest edge data slice) to prevent color bleeding
  57. * when the hardware trilinear filter reads across a sub-volume boundary.
  58. *
  59. * Atlas layout (nz = resolution.z, PADDING = 1):
  60. * ```
  61. * slice 0 : padding (copy of sub-volume 0, data slice 0)
  62. * slices 1 … nz : sub-volume 0 data
  63. * slice nz + 1 : padding (copy of sub-volume 0, data slice nz-1)
  64. * slice nz + 2 : padding (copy of sub-volume 1, data slice 0)
  65. * slices nz+3 … 2*nz+2 : sub-volume 1 data
  66. * …
  67. * ```
  68. * Total atlas depth = `7 * ( nz + 2 )`.
  69. *
  70. * Baking is fully GPU-resident: cubemap rendering, SH projection, and
  71. * texture packing all happen on the GPU with zero CPU readback.
  72. *
  73. * @three_import import { LightProbeGrid } from 'three/addons/lighting/LightProbeGrid.js';
  74. */
  75. class LightProbeGrid extends Object3D {
  76. /**
  77. * Constructs a new irradiance probe grid.
  78. *
  79. * The volume is centered at the object's position.
  80. *
  81. * @param {number} [width=1] - Full width of the volume along X.
  82. * @param {number} [height=1] - Full height of the volume along Y.
  83. * @param {number} [depth=1] - Full depth of the volume along Z.
  84. * @param {number} [widthProbes] - Number of probes along X. Defaults to `Math.max( 2, Math.round( width ) + 1 )`.
  85. * @param {number} [heightProbes] - Number of probes along Y. Defaults to `Math.max( 2, Math.round( height ) + 1 )`.
  86. * @param {number} [depthProbes] - Number of probes along Z. Defaults to `Math.max( 2, Math.round( depth ) + 1 )`.
  87. */
  88. constructor( width = 1, height = 1, depth = 1, widthProbes, heightProbes, depthProbes ) {
  89. super();
  90. /**
  91. * This flag can be used for type testing.
  92. *
  93. * @type {boolean}
  94. * @readonly
  95. * @default true
  96. */
  97. this.isLightProbeGrid = true;
  98. /**
  99. * The full width of the volume along X.
  100. *
  101. * @type {number}
  102. */
  103. this.width = width;
  104. /**
  105. * The full height of the volume along Y.
  106. *
  107. * @type {number}
  108. */
  109. this.height = height;
  110. /**
  111. * The full depth of the volume along Z.
  112. *
  113. * @type {number}
  114. */
  115. this.depth = depth;
  116. /**
  117. * The number of probes along each axis.
  118. *
  119. * @type {Vector3}
  120. */
  121. this.resolution = new Vector3(
  122. widthProbes !== undefined ? widthProbes : Math.max( 2, Math.round( width ) + 1 ),
  123. heightProbes !== undefined ? heightProbes : Math.max( 2, Math.round( height ) + 1 ),
  124. depthProbes !== undefined ? depthProbes : Math.max( 2, Math.round( depth ) + 1 )
  125. );
  126. /**
  127. * The world-space bounding box for the grid. Updated automatically
  128. * by {@link LightProbeGrid#bake}.
  129. *
  130. * @type {Box3}
  131. */
  132. this.boundingBox = new Box3();
  133. /**
  134. * The single RGBA atlas 3D texture storing all seven packed SH sub-volumes.
  135. *
  136. * @type {?Data3DTexture}
  137. * @default null
  138. */
  139. this.texture = null;
  140. /**
  141. * Internal render target for GPU-resident baking.
  142. *
  143. * @private
  144. * @type {?WebGL3DRenderTarget}
  145. * @default null
  146. */
  147. this._renderTarget = null;
  148. this.updateBoundingBox();
  149. }
  150. /**
  151. * Returns the world-space position of the probe at grid indices (ix, iy, iz).
  152. *
  153. * @param {number} ix - X index.
  154. * @param {number} iy - Y index.
  155. * @param {number} iz - Z index.
  156. * @param {Vector3} target - The target vector.
  157. * @return {Vector3} The world-space position.
  158. */
  159. getProbePosition( ix, iy, iz, target ) {
  160. const pos = this.position;
  161. const res = this.resolution;
  162. const w = this.width, h = this.height, d = this.depth;
  163. target.set(
  164. res.x > 1 ? pos.x - w / 2 + ix * w / ( res.x - 1 ) : pos.x,
  165. res.y > 1 ? pos.y - h / 2 + iy * h / ( res.y - 1 ) : pos.y,
  166. res.z > 1 ? pos.z - d / 2 + iz * d / ( res.z - 1 ) : pos.z
  167. );
  168. return target;
  169. }
  170. /**
  171. * Updates the world-space bounding box from the current position and size.
  172. */
  173. updateBoundingBox() {
  174. _size.set( this.width, this.height, this.depth );
  175. this.boundingBox.setFromCenterAndSize( this.position, _size );
  176. }
  177. /**
  178. * Bakes all probes by rendering cubemaps at each probe position
  179. * and projecting to L2 SH. Optionally iterates additional passes to
  180. * capture indirect bounces — each extra pass samples the previous pass's
  181. * atlas as indirect light, so a grid added to the scene before baking
  182. * accumulates one bounce per extra pass.
  183. *
  184. * @param {WebGLRenderer} renderer - The renderer.
  185. * @param {Scene} scene - The scene to render.
  186. * @param {Object} [options] - Bake options.
  187. * @param {number} [options.cubemapSize=8] - Resolution of each cubemap face.
  188. * @param {number} [options.near=0.1] - Near plane for the cube camera.
  189. * @param {number} [options.far=100] - Far plane for the cube camera.
  190. * @param {number} [options.bounces=0] - Additional bounce passes after the initial direct pass.
  191. */
  192. bake( renderer, scene, options = {} ) {
  193. const { bounces = 0 } = options;
  194. const { cubeRenderTarget, cubeCamera } = _ensureBakeResources( options );
  195. this._ensureTextures();
  196. this.updateBoundingBox();
  197. const res = this.resolution;
  198. const totalProbes = res.x * res.y * res.z;
  199. // Batch render target for SH coefficients: 9 pixels wide, one row per probe
  200. const batchTarget = _ensureBatchTarget( totalProbes );
  201. // Save renderer state
  202. const currentRenderTarget = renderer.getRenderTarget();
  203. renderer.getViewport( _currentViewport );
  204. renderer.getScissor( _currentScissor );
  205. const currentScissorTest = renderer.getScissorTest();
  206. // Scene is static across the bake — update once and disable per-render auto updates.
  207. const currentMatrixWorldAutoUpdate = scene.matrixWorldAutoUpdate;
  208. if ( currentMatrixWorldAutoUpdate === true ) {
  209. scene.updateMatrixWorld( true );
  210. scene.matrixWorldAutoUpdate = false;
  211. }
  212. // Disable shadow map auto-update across all passes — lights don't move.
  213. // Force a single shadow update on the first render so maps are initialized.
  214. const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
  215. renderer.shadowMap.autoUpdate = false;
  216. renderer.shadowMap.needsUpdate = true;
  217. _ensureRepackResources();
  218. const paddedSlices = res.z + 2 * ATLAS_PADDING;
  219. const rt = this._renderTarget;
  220. // const t0 = performance.now();
  221. // Pass 0 captures direct light only (grid hidden, so probesSH is not sampled
  222. // — the atlas at this point may be uninitialized or hold a prior bake).
  223. // Each subsequent pass keeps the grid visible so the cube cameras read the
  224. // previous pass's atlas as indirect light, accumulating one bounce per pass.
  225. // Phase 1 writes to the batch target and Phase 2 only swaps it into the atlas
  226. // at the very end of each pass, which gives an implicit ping-pong for free.
  227. for ( let pass = 0; pass <= bounces; pass ++ ) {
  228. this.visible = pass > 0;
  229. // Clear pooled batch target so skipped probes read as zero
  230. batchTarget.scissorTest = false;
  231. batchTarget.viewport.set( 0, 0, 9, totalProbes );
  232. renderer.setRenderTarget( batchTarget );
  233. renderer.clear();
  234. // Phase 1: Render cubemaps and project to SH into batch target
  235. // Note: set viewport/scissor on the render target directly to avoid pixel ratio scaling
  236. batchTarget.scissorTest = true;
  237. for ( let iz = 0; iz < res.z; iz ++ ) {
  238. for ( let iy = 0; iy < res.y; iy ++ ) {
  239. for ( let ix = 0; ix < res.x; ix ++ ) {
  240. const probeIndex = ix + iy * res.x + iz * res.x * res.y;
  241. this.getProbePosition( ix, iy, iz, _position );
  242. cubeCamera.position.copy( _position );
  243. cubeCamera.update( renderer, scene );
  244. // SH projection
  245. _shMaterial.uniforms.envMap.value = cubeRenderTarget.texture;
  246. _mesh.material = _shMaterial;
  247. batchTarget.viewport.set( 0, probeIndex, 9, 1 );
  248. batchTarget.scissor.set( 0, probeIndex, 9, 1 );
  249. renderer.setRenderTarget( batchTarget );
  250. renderer.render( _scene, _camera );
  251. }
  252. }
  253. }
  254. // Phase 2: Repack SH data from batch target into the atlas 3D texture (GPU-to-GPU).
  255. //
  256. // For each of the 7 packed sub-volumes (texture index t) we write:
  257. // - A leading padding slice (copy of data slice iz = 0)
  258. // - All nz data slices (iz = 0 … nz-1)
  259. // - A trailing padding slice (copy of data slice iz = nz-1)
  260. //
  261. // In the atlas the slices for sub-volume t occupy the range:
  262. // [ t * paddedSlices, t * paddedSlices + paddedSlices - 1 ]
  263. // where paddedSlices = nz + 2 * ATLAS_PADDING.
  264. rt.scissorTest = false;
  265. rt.viewport.set( 0, 0, res.x, res.y );
  266. for ( let t = 0; t < 7; t ++ ) {
  267. _repackMaterials[ t ].uniforms.batchTexture.value = batchTarget.texture;
  268. _repackMaterials[ t ].uniforms.resolution.value.copy( res );
  269. // Write data slices
  270. for ( let iz = 0; iz < res.z; iz ++ ) {
  271. _repackMaterials[ t ].uniforms.sliceZ.value = iz;
  272. _mesh.material = _repackMaterials[ t ];
  273. renderer.setRenderTarget( rt, t * paddedSlices + ATLAS_PADDING + iz );
  274. renderer.render( _scene, _camera );
  275. }
  276. // Leading padding: copy of data slice iz = 0
  277. _repackMaterials[ t ].uniforms.sliceZ.value = 0;
  278. _mesh.material = _repackMaterials[ t ];
  279. renderer.setRenderTarget( rt, t * paddedSlices );
  280. renderer.render( _scene, _camera );
  281. // Trailing padding: copy of data slice iz = nz - 1
  282. _repackMaterials[ t ].uniforms.sliceZ.value = res.z - 1;
  283. _mesh.material = _repackMaterials[ t ];
  284. renderer.setRenderTarget( rt, t * paddedSlices + ATLAS_PADDING + res.z );
  285. renderer.render( _scene, _camera );
  286. }
  287. }
  288. renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
  289. // Restore renderer state
  290. renderer.setRenderTarget( currentRenderTarget );
  291. renderer.setViewport( _currentViewport );
  292. renderer.setScissor( _currentScissor );
  293. renderer.setScissorTest( currentScissorTest );
  294. scene.matrixWorldAutoUpdate = currentMatrixWorldAutoUpdate;
  295. // console.log( `LightProbeGrid: bake complete ${ ( performance.now() - t0 ).toFixed( 1 ) }ms` );
  296. this.visible = true;
  297. }
  298. /**
  299. * Ensures the atlas 3D render target exists with the correct dimensions.
  300. *
  301. * @private
  302. */
  303. _ensureTextures() {
  304. if ( this._renderTarget !== null ) return;
  305. const res = this.resolution;
  306. const nx = res.x, ny = res.y, nz = res.z;
  307. // Atlas depth: 7 sub-volumes, each with ATLAS_PADDING slices at both ends
  308. const atlasDepth = 7 * ( nz + 2 * ATLAS_PADDING );
  309. const rt = new WebGL3DRenderTarget( nx, ny, atlasDepth, {
  310. format: RGBAFormat,
  311. type: FloatType,
  312. minFilter: LinearFilter,
  313. magFilter: LinearFilter,
  314. generateMipmaps: false,
  315. depthBuffer: false
  316. } );
  317. this._renderTarget = rt;
  318. this.texture = rt.texture;
  319. }
  320. /**
  321. * Frees GPU resources.
  322. */
  323. dispose() {
  324. if ( this._renderTarget !== null ) {
  325. this._renderTarget.dispose();
  326. this._renderTarget = null;
  327. this.texture = null;
  328. }
  329. }
  330. }
  331. // Internal: Ensure the shared fullscreen-quad scene exists
  332. function _ensureScene() {
  333. if ( _scene === null ) {
  334. _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
  335. _mesh = new Mesh( new PlaneGeometry( 2, 2 ) );
  336. _scene = new Scene();
  337. _scene.add( _mesh );
  338. }
  339. }
  340. // Internal: Ensure GPU resources for SH projection are created
  341. function _ensureGPUResources( cubemapSize ) {
  342. _ensureScene();
  343. // Recreate material when cubemap size changes
  344. if ( cubemapSize !== _lastCubemapSize ) {
  345. if ( _shMaterial !== null ) _shMaterial.dispose();
  346. _shMaterial = new ShaderMaterial( {
  347. precision: 'highp',
  348. defines: {
  349. CUBEMAP_SIZE: cubemapSize
  350. },
  351. uniforms: {
  352. envMap: { value: null }
  353. },
  354. vertexShader: /* glsl */`
  355. void main() {
  356. gl_Position = vec4( position.xy, 0.0, 1.0 );
  357. }
  358. `,
  359. fragmentShader: /* glsl */`
  360. #include <common>
  361. uniform samplerCube envMap;
  362. void main() {
  363. int coefIndex = int( gl_FragCoord.x );
  364. vec3 accum0 = vec3( 0.0 );
  365. vec3 accum1 = vec3( 0.0 );
  366. vec3 accum2 = vec3( 0.0 );
  367. vec3 accum3 = vec3( 0.0 );
  368. vec3 accum4 = vec3( 0.0 );
  369. vec3 accum5 = vec3( 0.0 );
  370. vec3 accum6 = vec3( 0.0 );
  371. vec3 accum7 = vec3( 0.0 );
  372. vec3 accum8 = vec3( 0.0 );
  373. float totalWeight = 0.0;
  374. float pixelSize = 2.0 / float( CUBEMAP_SIZE );
  375. for ( int face = 0; face < 6; face ++ ) {
  376. for ( int iy = 0; iy < CUBEMAP_SIZE; iy ++ ) {
  377. for ( int ix = 0; ix < CUBEMAP_SIZE; ix ++ ) {
  378. // WebGL cubemaps have a left-handed orientation (flip = -1)
  379. float col = ( float( ix ) + 0.5 ) * pixelSize - 1.0;
  380. float row = 1.0 - ( float( iy ) + 0.5 ) * pixelSize;
  381. vec3 coord;
  382. if ( face == 0 ) coord = vec3( 1.0, row, -col );
  383. else if ( face == 1 ) coord = vec3( -1.0, row, col );
  384. else if ( face == 2 ) coord = vec3( col, 1.0, -row );
  385. else if ( face == 3 ) coord = vec3( col, -1.0, row );
  386. else if ( face == 4 ) coord = vec3( col, row, 1.0 );
  387. else coord = vec3( -col, row, -1.0 );
  388. float lengthSq = dot( coord, coord );
  389. float weight = 4.0 / ( sqrt( lengthSq ) * lengthSq );
  390. totalWeight += weight;
  391. vec3 dir = normalize( coord );
  392. vec3 cw = textureCube( envMap, coord ).rgb * weight;
  393. // band 0
  394. accum0 += cw * 0.282095;
  395. // band 1
  396. accum1 += cw * ( 0.488603 * dir.y );
  397. accum2 += cw * ( 0.488603 * dir.z );
  398. accum3 += cw * ( 0.488603 * dir.x );
  399. // band 2
  400. accum4 += cw * ( 1.092548 * ( dir.x * dir.y ) );
  401. accum5 += cw * ( 1.092548 * ( dir.y * dir.z ) );
  402. accum6 += cw * ( 0.315392 * ( 3.0 * dir.z * dir.z - 1.0 ) );
  403. accum7 += cw * ( 1.092548 * ( dir.x * dir.z ) );
  404. accum8 += cw * ( 0.546274 * ( dir.x * dir.x - dir.y * dir.y ) );
  405. }
  406. }
  407. }
  408. float norm = 4.0 * PI / totalWeight;
  409. vec3 accum;
  410. if ( coefIndex == 0 ) accum = accum0;
  411. else if ( coefIndex == 1 ) accum = accum1;
  412. else if ( coefIndex == 2 ) accum = accum2;
  413. else if ( coefIndex == 3 ) accum = accum3;
  414. else if ( coefIndex == 4 ) accum = accum4;
  415. else if ( coefIndex == 5 ) accum = accum5;
  416. else if ( coefIndex == 6 ) accum = accum6;
  417. else if ( coefIndex == 7 ) accum = accum7;
  418. else accum = accum8;
  419. gl_FragColor = vec4( accum * norm, 1.0 );
  420. }
  421. `
  422. } );
  423. _lastCubemapSize = cubemapSize;
  424. }
  425. }
  426. // Internal: Ensure GPU resources for repacking SH into the atlas 3D texture
  427. function _ensureRepackResources() {
  428. if ( _repackMaterials !== null ) return;
  429. _ensureScene();
  430. // Create 7 materials, one per output texture packing
  431. // Texture 0: (c0.r, c0.g, c0.b, c1.r)
  432. // Texture 1: (c1.g, c1.b, c2.r, c2.g)
  433. // Texture 2: (c2.b, c3.r, c3.g, c3.b)
  434. // Texture 3: (c4.r, c4.g, c4.b, c5.r)
  435. // Texture 4: (c5.g, c5.b, c6.r, c6.g)
  436. // Texture 5: (c6.b, c7.r, c7.g, c7.b)
  437. // Texture 6: (c8.r, c8.g, c8.b, 0.0)
  438. const repackVertexShader = /* glsl */`
  439. void main() {
  440. gl_Position = vec4( position.xy, 0.0, 1.0 );
  441. }
  442. `;
  443. _repackMaterials = [];
  444. for ( let t = 0; t < 7; t ++ ) {
  445. _repackMaterials[ t ] = new ShaderMaterial( {
  446. precision: 'highp',
  447. defines: {
  448. TEXTURE_INDEX: t
  449. },
  450. uniforms: {
  451. batchTexture: { value: null },
  452. resolution: { value: new Vector3() },
  453. sliceZ: { value: 0 }
  454. },
  455. vertexShader: repackVertexShader,
  456. fragmentShader: /* glsl */`
  457. uniform sampler2D batchTexture;
  458. uniform vec3 resolution;
  459. uniform int sliceZ;
  460. void main() {
  461. int ix = int( gl_FragCoord.x );
  462. int iy = int( gl_FragCoord.y );
  463. int iz = sliceZ;
  464. int probeIndex = ix + iy * int( resolution.x ) + iz * int( resolution.x ) * int( resolution.y );
  465. // Read 9 SH coefficients from the batch texture row
  466. vec4 c0 = texelFetch( batchTexture, ivec2( 0, probeIndex ), 0 );
  467. vec4 c1 = texelFetch( batchTexture, ivec2( 1, probeIndex ), 0 );
  468. vec4 c2 = texelFetch( batchTexture, ivec2( 2, probeIndex ), 0 );
  469. vec4 c3 = texelFetch( batchTexture, ivec2( 3, probeIndex ), 0 );
  470. vec4 c4 = texelFetch( batchTexture, ivec2( 4, probeIndex ), 0 );
  471. vec4 c5 = texelFetch( batchTexture, ivec2( 5, probeIndex ), 0 );
  472. vec4 c6 = texelFetch( batchTexture, ivec2( 6, probeIndex ), 0 );
  473. vec4 c7 = texelFetch( batchTexture, ivec2( 7, probeIndex ), 0 );
  474. vec4 c8 = texelFetch( batchTexture, ivec2( 8, probeIndex ), 0 );
  475. // Pack into the output format for this texture index
  476. #if TEXTURE_INDEX == 0
  477. gl_FragColor = vec4( c0.rgb, c1.r );
  478. #elif TEXTURE_INDEX == 1
  479. gl_FragColor = vec4( c1.gb, c2.rg );
  480. #elif TEXTURE_INDEX == 2
  481. gl_FragColor = vec4( c2.b, c3.rgb );
  482. #elif TEXTURE_INDEX == 3
  483. gl_FragColor = vec4( c4.rgb, c5.r );
  484. #elif TEXTURE_INDEX == 4
  485. gl_FragColor = vec4( c5.gb, c6.rg );
  486. #elif TEXTURE_INDEX == 5
  487. gl_FragColor = vec4( c6.b, c7.rgb );
  488. #else
  489. gl_FragColor = vec4( c8.rgb, 0.0 );
  490. #endif
  491. }
  492. `
  493. } );
  494. }
  495. }
  496. // Internal: Ensure cube render target and camera exist with the right parameters
  497. function _ensureBakeResources( options ) {
  498. const {
  499. cubemapSize = 8,
  500. near = 0.1,
  501. far = 100
  502. } = options;
  503. if ( _cubeRenderTarget === null || cubemapSize !== _cachedCubemapSize || near !== _cachedNear || far !== _cachedFar ) {
  504. if ( _cubeRenderTarget !== null ) _cubeRenderTarget.dispose();
  505. _cubeRenderTarget = new WebGLCubeRenderTarget( cubemapSize, { type: HalfFloatType } );
  506. _cubeCamera = new CubeCamera( near, far, _cubeRenderTarget );
  507. _cachedCubemapSize = cubemapSize;
  508. _cachedNear = near;
  509. _cachedFar = far;
  510. }
  511. _ensureGPUResources( cubemapSize );
  512. return { cubeRenderTarget: _cubeRenderTarget, cubeCamera: _cubeCamera };
  513. }
  514. function _ensureBatchTarget( totalProbes ) {
  515. if ( _batchTarget === null || _batchTargetProbes !== totalProbes ) {
  516. if ( _batchTarget !== null ) _batchTarget.dispose();
  517. _batchTarget = new WebGLRenderTarget( 9, totalProbes, {
  518. type: FloatType,
  519. minFilter: NearestFilter,
  520. magFilter: NearestFilter,
  521. depthBuffer: false
  522. } );
  523. _batchTargetProbes = totalProbes;
  524. }
  525. return _batchTarget;
  526. }
  527. export { LightProbeGrid };
粤ICP备19079148号