ProgressiveLightMapGPU.js 9.2 KB

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