LightProbeGenerator.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import {
  2. Color,
  3. LightProbe,
  4. LinearSRGBColorSpace,
  5. SphericalHarmonics3,
  6. Vector3,
  7. SRGBColorSpace,
  8. NoColorSpace,
  9. HalfFloatType,
  10. DataUtils,
  11. WebGLCoordinateSystem
  12. } from 'three';
  13. /**
  14. * Utility class for creating instances of {@link LightProbe}.
  15. *
  16. * @hideconstructor
  17. */
  18. class LightProbeGenerator {
  19. /**
  20. * Creates a light probe from the given (radiance) environment map.
  21. * The method expects that the environment map is represented as a cube texture.
  22. *
  23. * @param {CubeTexture} cubeTexture - The environment map.
  24. * @return {LightProbe} The created light probe.
  25. */
  26. static fromCubeTexture( cubeTexture ) {
  27. // https://www.ppsloan.org/publications/StupidSH36.pdf
  28. let totalWeight = 0;
  29. const coord = new Vector3();
  30. const dir = new Vector3();
  31. const color = new Color();
  32. const shBasis = [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
  33. const sh = new SphericalHarmonics3();
  34. const shCoefficients = sh.coefficients;
  35. for ( let faceIndex = 0; faceIndex < 6; faceIndex ++ ) {
  36. const image = cubeTexture.image[ faceIndex ];
  37. const width = image.width;
  38. const height = image.height;
  39. const canvas = document.createElement( 'canvas' );
  40. canvas.width = width;
  41. canvas.height = height;
  42. const context = canvas.getContext( '2d' );
  43. context.drawImage( image, 0, 0, width, height );
  44. const imageData = context.getImageData( 0, 0, width, height );
  45. const data = imageData.data;
  46. const imageWidth = imageData.width; // assumed to be square
  47. const pixelSize = 2 / imageWidth;
  48. for ( let i = 0, il = data.length; i < il; i += 4 ) { // RGBA assumed
  49. // pixel color
  50. color.setRGB( data[ i ] / 255, data[ i + 1 ] / 255, data[ i + 2 ] / 255 );
  51. // convert to linear color space
  52. convertColorToLinear( color, cubeTexture.colorSpace );
  53. // pixel coordinate on unit cube
  54. const pixelIndex = i / 4;
  55. const col = - 1 + ( pixelIndex % imageWidth + 0.5 ) * pixelSize;
  56. const row = 1 - ( Math.floor( pixelIndex / imageWidth ) + 0.5 ) * pixelSize;
  57. switch ( faceIndex ) {
  58. case 0: coord.set( - 1, row, - col ); break;
  59. case 1: coord.set( 1, row, col ); break;
  60. case 2: coord.set( - col, 1, - row ); break;
  61. case 3: coord.set( - col, - 1, row ); break;
  62. case 4: coord.set( - col, row, 1 ); break;
  63. case 5: coord.set( col, row, - 1 ); break;
  64. }
  65. // weight assigned to this pixel
  66. const lengthSq = coord.lengthSq();
  67. const weight = 4 / ( Math.sqrt( lengthSq ) * lengthSq );
  68. totalWeight += weight;
  69. // direction vector to this pixel
  70. dir.copy( coord ).normalize();
  71. // evaluate SH basis functions in direction dir
  72. SphericalHarmonics3.getBasisAt( dir, shBasis );
  73. // accumulate
  74. for ( let j = 0; j < 9; j ++ ) {
  75. shCoefficients[ j ].x += shBasis[ j ] * color.r * weight;
  76. shCoefficients[ j ].y += shBasis[ j ] * color.g * weight;
  77. shCoefficients[ j ].z += shBasis[ j ] * color.b * weight;
  78. }
  79. }
  80. }
  81. // normalize
  82. const norm = ( 4 * Math.PI ) / totalWeight;
  83. for ( let j = 0; j < 9; j ++ ) {
  84. shCoefficients[ j ].x *= norm;
  85. shCoefficients[ j ].y *= norm;
  86. shCoefficients[ j ].z *= norm;
  87. }
  88. return new LightProbe( sh );
  89. }
  90. /**
  91. * Creates a light probe from the given (radiance) environment map.
  92. * The method expects that the environment map is represented as a cube render target.
  93. *
  94. * The cube render target must be in RGBA so `cubeRenderTarget.texture.format` must be
  95. * set to {@link RGBAFormat}.
  96. *
  97. * @async
  98. * @param {WebGPURenderer|WebGLRenderer} renderer - The renderer.
  99. * @param {CubeRenderTarget|WebGLCubeRenderTarget} cubeRenderTarget - The environment map.
  100. * @return {Promise<LightProbe>} A Promise that resolves with the created light probe.
  101. */
  102. static async fromCubeRenderTarget( renderer, cubeRenderTarget ) {
  103. const flip = renderer.coordinateSystem === WebGLCoordinateSystem ? - 1 : 1;
  104. // The renderTarget must be set to RGBA in order to make readRenderTargetPixels works
  105. let totalWeight = 0;
  106. const coord = new Vector3();
  107. const dir = new Vector3();
  108. const color = new Color();
  109. const shBasis = [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
  110. const sh = new SphericalHarmonics3();
  111. const shCoefficients = sh.coefficients;
  112. const dataType = cubeRenderTarget.texture.type;
  113. const imageWidth = cubeRenderTarget.width; // assumed to be square
  114. let data;
  115. if ( renderer.isWebGLRenderer ) {
  116. if ( dataType === HalfFloatType ) {
  117. data = new Uint16Array( imageWidth * imageWidth * 4 );
  118. } else {
  119. // assuming UnsignedByteType
  120. data = new Uint8Array( imageWidth * imageWidth * 4 );
  121. }
  122. }
  123. for ( let faceIndex = 0; faceIndex < 6; faceIndex ++ ) {
  124. if ( renderer.isWebGLRenderer ) {
  125. await renderer.readRenderTargetPixelsAsync( cubeRenderTarget, 0, 0, imageWidth, imageWidth, data, faceIndex );
  126. } else {
  127. data = await renderer.readRenderTargetPixelsAsync( cubeRenderTarget, 0, 0, imageWidth, imageWidth, 0, faceIndex );
  128. }
  129. const pixelSize = 2 / imageWidth;
  130. for ( let i = 0, il = data.length; i < il; i += 4 ) { // RGBA assumed
  131. let r, g, b;
  132. if ( dataType === HalfFloatType ) {
  133. r = DataUtils.fromHalfFloat( data[ i ] );
  134. g = DataUtils.fromHalfFloat( data[ i + 1 ] );
  135. b = DataUtils.fromHalfFloat( data[ i + 2 ] );
  136. } else {
  137. r = data[ i ] / 255;
  138. g = data[ i + 1 ] / 255;
  139. b = data[ i + 2 ] / 255;
  140. }
  141. // pixel color
  142. color.setRGB( r, g, b );
  143. // convert to linear color space
  144. convertColorToLinear( color, cubeRenderTarget.texture.colorSpace );
  145. // pixel coordinate on unit cube
  146. const pixelIndex = i / 4;
  147. const col = ( 1 - ( pixelIndex % imageWidth + 0.5 ) * pixelSize ) * flip;
  148. const row = 1 - ( Math.floor( pixelIndex / imageWidth ) + 0.5 ) * pixelSize;
  149. switch ( faceIndex ) {
  150. case 0: coord.set( - 1 * flip, row, col * flip ); break;
  151. case 1: coord.set( 1 * flip, row, - col * flip ); break;
  152. case 2: coord.set( col, 1, - row ); break;
  153. case 3: coord.set( col, - 1, row ); break;
  154. case 4: coord.set( col, row, 1 ); break;
  155. case 5: coord.set( - col, row, - 1 ); break;
  156. }
  157. // weight assigned to this pixel
  158. const lengthSq = coord.lengthSq();
  159. const weight = 4 / ( Math.sqrt( lengthSq ) * lengthSq );
  160. totalWeight += weight;
  161. // direction vector to this pixel
  162. dir.copy( coord ).normalize();
  163. // evaluate SH basis functions in direction dir
  164. SphericalHarmonics3.getBasisAt( dir, shBasis );
  165. // accumulate
  166. for ( let j = 0; j < 9; j ++ ) {
  167. shCoefficients[ j ].x += shBasis[ j ] * color.r * weight;
  168. shCoefficients[ j ].y += shBasis[ j ] * color.g * weight;
  169. shCoefficients[ j ].z += shBasis[ j ] * color.b * weight;
  170. }
  171. }
  172. }
  173. // normalize
  174. const norm = ( 4 * Math.PI ) / totalWeight;
  175. for ( let j = 0; j < 9; j ++ ) {
  176. shCoefficients[ j ].x *= norm;
  177. shCoefficients[ j ].y *= norm;
  178. shCoefficients[ j ].z *= norm;
  179. }
  180. return new LightProbe( sh );
  181. }
  182. }
  183. function convertColorToLinear( color, colorSpace ) {
  184. switch ( colorSpace ) {
  185. case SRGBColorSpace:
  186. color.convertSRGBToLinear();
  187. break;
  188. case LinearSRGBColorSpace:
  189. case NoColorSpace:
  190. break;
  191. default:
  192. console.warn( 'WARNING: LightProbeGenerator convertColorToLinear() encountered an unsupported color space.' );
  193. break;
  194. }
  195. return color;
  196. }
  197. export { LightProbeGenerator };
粤ICP备19079148号