ProgressiveLightMapGPU.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. import { DoubleSide, FloatType, HalfFloatType, PlaneGeometry, Mesh, RenderTarget, Scene, MeshPhongNodeMaterial, NodeMaterial } from 'three/webgpu';
  2. import { add, float, mix, output, sub, texture, uniform, uv, vec2, vec4 } from 'three/tsl';
  3. import { potpack } from '../libs/potpack.module.js';
  4. /**
  5. * Progressive Light Map Accumulator, by [zalo](https://github.com/zalo/)
  6. *
  7. * To use, simply construct a `ProgressiveLightMap` object,
  8. * `plmap.addObjectsToLightMap(object)` an array of semi-static
  9. * objects and lights to the class once, and then call
  10. * `plmap.update(camera)` every frame to begin accumulating
  11. * lighting samples.
  12. *
  13. * This should begin accumulating lightmaps which apply to
  14. * your objects, so you can start jittering lighting to achieve
  15. * the texture-space effect you're looking for.
  16. */
  17. class ProgressiveLightMap {
  18. /**
  19. * @param {WebGPURenderer} renderer An instance of WebGPURenderer.
  20. * @param {number} [resolution=1024] The side-long dimension of you total lightmap.
  21. */
  22. constructor( renderer, resolution = 1024 ) {
  23. this.renderer = renderer;
  24. this.resolution = resolution;
  25. this._lightMapContainers = [];
  26. this._scene = new Scene();
  27. this._buffer1Active = false;
  28. this._labelMesh = null;
  29. this._blurringPlane = null;
  30. // Create the Progressive LightMap Texture
  31. const type = /(Android|iPad|iPhone|iPod)/g.test( navigator.userAgent ) ? HalfFloatType : FloatType;
  32. this._progressiveLightMap1 = new RenderTarget( this.resolution, this.resolution, { type: type } );
  33. this._progressiveLightMap2 = new RenderTarget( this.resolution, this.resolution, { type: type } );
  34. this._progressiveLightMap2.texture.channel = 1;
  35. // uniforms
  36. this._averagingWindow = uniform( 100 );
  37. this._previousShadowMap = texture( this._progressiveLightMap1.texture );
  38. // materials
  39. const uvNode = uv( 1 ).flipY();
  40. this._uvMat = new MeshPhongNodeMaterial();
  41. this._uvMat.vertexNode = vec4( sub( uvNode, vec2( 0.5 ) ).mul( 2 ), 1, 1 );
  42. this._uvMat.outputNode = vec4( mix( this._previousShadowMap.sample( uv( 1 ) ), output, float( 1 ).div( this._averagingWindow ) ) );
  43. }
  44. /**
  45. * Sets these objects' materials' lightmaps and modifies their uv1's.
  46. * @param {Object3D} objects An array of objects and lights to set up your lightmap.
  47. */
  48. addObjectsToLightMap( objects ) {
  49. // Prepare list of UV bounding boxes for packing later...
  50. const uv_boxes = [];
  51. const padding = 3 / this.resolution;
  52. for ( let ob = 0; ob < objects.length; ob ++ ) {
  53. const object = objects[ ob ];
  54. // If this object is a light, simply add it to the internal scene
  55. if ( object.isLight ) {
  56. this._scene.attach( object ); continue;
  57. }
  58. if ( object.geometry.hasAttribute( 'uv' ) === false ) {
  59. console.warn( 'THREE.ProgressiveLightMap: All lightmap objects need uvs.' ); continue;
  60. }
  61. if ( this._blurringPlane === null ) {
  62. this._initializeBlurPlane();
  63. }
  64. // Apply the lightmap to the object
  65. object.material.lightMap = this._progressiveLightMap2.texture;
  66. object.material.dithering = true;
  67. object.castShadow = true;
  68. object.receiveShadow = true;
  69. object.renderOrder = 1000 + ob;
  70. // Prepare UV boxes for potpack
  71. // TODO: Size these by object surface area
  72. uv_boxes.push( { w: 1 + ( padding * 2 ), h: 1 + ( padding * 2 ), index: ob } );
  73. this._lightMapContainers.push( { basicMat: object.material, object: object } );
  74. }
  75. // Pack the objects' lightmap UVs into the same global space
  76. const dimensions = potpack( uv_boxes );
  77. uv_boxes.forEach( ( box ) => {
  78. const uv1 = objects[ box.index ].geometry.getAttribute( 'uv' ).clone();
  79. for ( let i = 0; i < uv1.array.length; i += uv1.itemSize ) {
  80. uv1.array[ i ] = ( uv1.array[ i ] + box.x + padding ) / dimensions.w;
  81. uv1.array[ i + 1 ] = 1 - ( ( uv1.array[ i + 1 ] + box.y + padding ) / dimensions.h );
  82. }
  83. objects[ box.index ].geometry.setAttribute( 'uv1', uv1 );
  84. objects[ box.index ].geometry.getAttribute( 'uv1' ).needsUpdate = true;
  85. } );
  86. }
  87. /**
  88. * Frees all internal resources.
  89. */
  90. dispose() {
  91. this._progressiveLightMap1.dispose();
  92. this._progressiveLightMap2.dispose();
  93. this._uvMat.dispose();
  94. if ( this._blurringPlane !== null ) {
  95. this._blurringPlane.geometry.dispose();
  96. this._blurringPlane.material.dispose();
  97. }
  98. if ( this._labelMesh !== null ) {
  99. this._labelMesh.geometry.dispose();
  100. this._labelMesh.material.dispose();
  101. }
  102. }
  103. /**
  104. * This function renders each mesh one at a time into their respective surface maps
  105. * @param {Camera} camera Standard Rendering Camera
  106. * @param {number} blendWindow When >1, samples will accumulate over time.
  107. * @param {boolean} blurEdges Whether to fix UV Edges via blurring
  108. */
  109. update( camera, blendWindow = 100, blurEdges = true ) {
  110. if ( this._blurringPlane === null ) {
  111. return;
  112. }
  113. // Store the original Render Target
  114. const currentRenderTarget = this.renderer.getRenderTarget();
  115. // The blurring plane applies blur to the seams of the lightmap
  116. this._blurringPlane.visible = blurEdges;
  117. // Steal the Object3D from the real world to our special dimension
  118. for ( let l = 0; l < this._lightMapContainers.length; l ++ ) {
  119. this._lightMapContainers[ l ].object.oldScene = this._lightMapContainers[ l ].object.parent;
  120. this._scene.attach( this._lightMapContainers[ l ].object );
  121. }
  122. // Set each object's material to the UV Unwrapped Surface Mapping Version
  123. for ( let l = 0; l < this._lightMapContainers.length; l ++ ) {
  124. this._averagingWindow.value = blendWindow;
  125. this._lightMapContainers[ l ].object.material = this._uvMat;
  126. this._lightMapContainers[ l ].object.oldFrustumCulled = this._lightMapContainers[ l ].object.frustumCulled;
  127. this._lightMapContainers[ l ].object.frustumCulled = false;
  128. }
  129. // Ping-pong two surface buffers for reading/writing
  130. const activeMap = this._buffer1Active ? this._progressiveLightMap1 : this._progressiveLightMap2;
  131. const inactiveMap = this._buffer1Active ? this._progressiveLightMap2 : this._progressiveLightMap1;
  132. // Render the object's surface maps
  133. this.renderer.setRenderTarget( activeMap );
  134. this._previousShadowMap.value = inactiveMap.texture;
  135. this._buffer1Active = ! this._buffer1Active;
  136. this.renderer.render( this._scene, camera );
  137. // Restore the object's Real-time Material and add it back to the original world
  138. for ( let l = 0; l < this._lightMapContainers.length; l ++ ) {
  139. this._lightMapContainers[ l ].object.frustumCulled = this._lightMapContainers[ l ].object.oldFrustumCulled;
  140. this._lightMapContainers[ l ].object.material = this._lightMapContainers[ l ].basicMat;
  141. this._lightMapContainers[ l ].object.oldScene.attach( this._lightMapContainers[ l ].object );
  142. }
  143. // Restore the original Render Target
  144. this.renderer.setRenderTarget( currentRenderTarget );
  145. }
  146. /**
  147. * Draw the lightmap in the main scene. Call this after adding the objects to it.
  148. * @param {boolean} visible Whether the debug plane should be visible.
  149. * @param {Vector3} position Where the debug plane should be drawn.
  150. */
  151. showDebugLightmap( visible, position = null ) {
  152. if ( this._lightMapContainers.length === 0 ) {
  153. console.warn( 'THREE.ProgressiveLightMap: Call .showDebugLightmap() after adding the objects.' );
  154. return;
  155. }
  156. if ( this._labelMesh === null ) {
  157. const labelMaterial = new NodeMaterial();
  158. labelMaterial.colorNode = texture( this._progressiveLightMap1.texture ).sample( uv().flipY() );
  159. labelMaterial.side = DoubleSide;
  160. const labelGeometry = new PlaneGeometry( 100, 100 );
  161. this._labelMesh = new Mesh( labelGeometry, labelMaterial );
  162. this._labelMesh.position.y = 250;
  163. this._lightMapContainers[ 0 ].object.parent.add( this._labelMesh );
  164. }
  165. if ( position !== null ) {
  166. this._labelMesh.position.copy( position );
  167. }
  168. this._labelMesh.visible = visible;
  169. }
  170. /**
  171. * Creates the Blurring Plane.
  172. */
  173. _initializeBlurPlane() {
  174. const blurMaterial = new NodeMaterial();
  175. blurMaterial.polygonOffset = true;
  176. blurMaterial.polygonOffsetFactor = - 1;
  177. blurMaterial.polygonOffsetUnits = 3;
  178. blurMaterial.vertexNode = vec4( sub( uv(), vec2( 0.5 ) ).mul( 2 ), 1, 1 );
  179. const uvNode = uv().flipY().toVar();
  180. const pixelOffset = float( 0.5 ).div( float( this.resolution ) ).toVar();
  181. const color = add(
  182. this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset, 0 ) ) ),
  183. this._previousShadowMap.sample( uvNode.add( vec2( 0, pixelOffset ) ) ),
  184. this._previousShadowMap.sample( uvNode.add( vec2( 0, pixelOffset.negate() ) ) ),
  185. this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset.negate(), 0 ) ) ),
  186. this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset, pixelOffset ) ) ),
  187. this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset.negate(), pixelOffset ) ) ),
  188. this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset, pixelOffset.negate() ) ) ),
  189. this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset.negate(), pixelOffset.negate() ) ) ),
  190. ).div( 8 );
  191. blurMaterial.fragmentNode = color;
  192. this._blurringPlane = new Mesh( new PlaneGeometry( 1, 1 ), blurMaterial );
  193. this._blurringPlane.name = 'Blurring Plane';
  194. this._blurringPlane.frustumCulled = false;
  195. this._blurringPlane.renderOrder = 0;
  196. this._blurringPlane.material.depthWrite = false;
  197. this._scene.add( this._blurringPlane );
  198. }
  199. }
  200. export { ProgressiveLightMap };
粤ICP备19079148号