UnrealBloomPass.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /**
  2. * @author spidersharma / http://eduperiment.com/
  3. */
  4. import {
  5. AdditiveBlending,
  6. Color,
  7. LinearFilter,
  8. MeshBasicMaterial,
  9. RGBAFormat,
  10. ShaderMaterial,
  11. UniformsUtils,
  12. Vector2,
  13. Vector3,
  14. WebGLRenderTarget
  15. } from "../../../build/three.module.js";
  16. import { Pass } from "../postprocessing/Pass.js";
  17. import { CopyShader } from "../shaders/CopyShader.js";
  18. import { LuminosityHighPassShader } from "../shaders/LuminosityHighPassShader.js";
  19. /**
  20. * UnrealBloomPass is inspired by the bloom pass of Unreal Engine. It creates a
  21. * mip map chain of bloom textures and blurs them with different radii. Because
  22. * of the weighted combination of mips, and because larger blurs are done on
  23. * higher mips, this effect provides good quality and performance.
  24. *
  25. * Reference:
  26. * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/
  27. */
  28. var UnrealBloomPass = function ( resolution, strength, radius, threshold ) {
  29. Pass.call( this );
  30. this.strength = ( strength !== undefined ) ? strength : 1;
  31. this.radius = radius;
  32. this.threshold = threshold;
  33. this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 );
  34. // create color only once here, reuse it later inside the render function
  35. this.clearColor = new Color( 0, 0, 0 );
  36. // render targets
  37. var pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat };
  38. this.renderTargetsHorizontal = [];
  39. this.renderTargetsVertical = [];
  40. this.nMips = 5;
  41. var resx = Math.round( this.resolution.x / 2 );
  42. var resy = Math.round( this.resolution.y / 2 );
  43. this.renderTargetBright = new WebGLRenderTarget( resx, resy, pars );
  44. this.renderTargetBright.texture.name = "UnrealBloomPass.bright";
  45. this.renderTargetBright.texture.generateMipmaps = false;
  46. for ( var i = 0; i < this.nMips; i ++ ) {
  47. var renderTargetHorizonal = new WebGLRenderTarget( resx, resy, pars );
  48. renderTargetHorizonal.texture.name = "UnrealBloomPass.h" + i;
  49. renderTargetHorizonal.texture.generateMipmaps = false;
  50. this.renderTargetsHorizontal.push( renderTargetHorizonal );
  51. var renderTargetVertical = new WebGLRenderTarget( resx, resy, pars );
  52. renderTargetVertical.texture.name = "UnrealBloomPass.v" + i;
  53. renderTargetVertical.texture.generateMipmaps = false;
  54. this.renderTargetsVertical.push( renderTargetVertical );
  55. resx = Math.round( resx / 2 );
  56. resy = Math.round( resy / 2 );
  57. }
  58. // luminosity high pass material
  59. if ( LuminosityHighPassShader === undefined )
  60. console.error( "UnrealBloomPass relies on LuminosityHighPassShader" );
  61. var highPassShader = LuminosityHighPassShader;
  62. this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms );
  63. this.highPassUniforms[ "luminosityThreshold" ].value = threshold;
  64. this.highPassUniforms[ "smoothWidth" ].value = 0.01;
  65. this.materialHighPassFilter = new ShaderMaterial( {
  66. uniforms: this.highPassUniforms,
  67. vertexShader: highPassShader.vertexShader,
  68. fragmentShader: highPassShader.fragmentShader,
  69. defines: {}
  70. } );
  71. // Gaussian Blur Materials
  72. this.separableBlurMaterials = [];
  73. var kernelSizeArray = [ 3, 5, 7, 9, 11 ];
  74. var resx = Math.round( this.resolution.x / 2 );
  75. var resy = Math.round( this.resolution.y / 2 );
  76. for ( var i = 0; i < this.nMips; i ++ ) {
  77. this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) );
  78. this.separableBlurMaterials[ i ].uniforms[ "texSize" ].value = new Vector2( resx, resy );
  79. resx = Math.round( resx / 2 );
  80. resy = Math.round( resy / 2 );
  81. }
  82. // Composite material
  83. this.compositeMaterial = this.getCompositeMaterial( this.nMips );
  84. this.compositeMaterial.uniforms[ "blurTexture1" ].value = this.renderTargetsVertical[ 0 ].texture;
  85. this.compositeMaterial.uniforms[ "blurTexture2" ].value = this.renderTargetsVertical[ 1 ].texture;
  86. this.compositeMaterial.uniforms[ "blurTexture3" ].value = this.renderTargetsVertical[ 2 ].texture;
  87. this.compositeMaterial.uniforms[ "blurTexture4" ].value = this.renderTargetsVertical[ 3 ].texture;
  88. this.compositeMaterial.uniforms[ "blurTexture5" ].value = this.renderTargetsVertical[ 4 ].texture;
  89. this.compositeMaterial.uniforms[ "bloomStrength" ].value = strength;
  90. this.compositeMaterial.uniforms[ "bloomRadius" ].value = 0.1;
  91. this.compositeMaterial.needsUpdate = true;
  92. var bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ];
  93. this.compositeMaterial.uniforms[ "bloomFactors" ].value = bloomFactors;
  94. this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ),
  95. new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ];
  96. this.compositeMaterial.uniforms[ "bloomTintColors" ].value = this.bloomTintColors;
  97. // copy material
  98. if ( CopyShader === undefined ) {
  99. console.error( "UnrealBloomPass relies on CopyShader" );
  100. }
  101. var copyShader = CopyShader;
  102. this.copyUniforms = UniformsUtils.clone( copyShader.uniforms );
  103. this.copyUniforms[ "opacity" ].value = 1.0;
  104. this.materialCopy = new ShaderMaterial( {
  105. uniforms: this.copyUniforms,
  106. vertexShader: copyShader.vertexShader,
  107. fragmentShader: copyShader.fragmentShader,
  108. blending: AdditiveBlending,
  109. depthTest: false,
  110. depthWrite: false,
  111. transparent: true
  112. } );
  113. this.enabled = true;
  114. this.needsSwap = false;
  115. this.oldClearColor = new Color();
  116. this.oldClearAlpha = 1;
  117. this.basic = new MeshBasicMaterial();
  118. this.fsQuad = new Pass.FullScreenQuad( null );
  119. };
  120. UnrealBloomPass.prototype = Object.assign( Object.create( Pass.prototype ), {
  121. constructor: UnrealBloomPass,
  122. dispose: function () {
  123. for ( var i = 0; i < this.renderTargetsHorizontal.length; i ++ ) {
  124. this.renderTargetsHorizontal[ i ].dispose();
  125. }
  126. for ( var i = 0; i < this.renderTargetsVertical.length; i ++ ) {
  127. this.renderTargetsVertical[ i ].dispose();
  128. }
  129. this.renderTargetBright.dispose();
  130. },
  131. setSize: function ( width, height ) {
  132. var resx = Math.round( width / 2 );
  133. var resy = Math.round( height / 2 );
  134. this.renderTargetBright.setSize( resx, resy );
  135. for ( var i = 0; i < this.nMips; i ++ ) {
  136. this.renderTargetsHorizontal[ i ].setSize( resx, resy );
  137. this.renderTargetsVertical[ i ].setSize( resx, resy );
  138. this.separableBlurMaterials[ i ].uniforms[ "texSize" ].value = new Vector2( resx, resy );
  139. resx = Math.round( resx / 2 );
  140. resy = Math.round( resy / 2 );
  141. }
  142. },
  143. render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
  144. this.oldClearColor.copy( renderer.getClearColor() );
  145. this.oldClearAlpha = renderer.getClearAlpha();
  146. var oldAutoClear = renderer.autoClear;
  147. renderer.autoClear = false;
  148. renderer.setClearColor( this.clearColor, 0 );
  149. if ( maskActive ) renderer.state.buffers.stencil.setTest( false );
  150. // Render input to screen
  151. if ( this.renderToScreen ) {
  152. this.fsQuad.material = this.basic;
  153. this.basic.map = readBuffer.texture;
  154. renderer.setRenderTarget( null );
  155. renderer.clear();
  156. this.fsQuad.render( renderer );
  157. }
  158. // 1. Extract Bright Areas
  159. this.highPassUniforms[ "tDiffuse" ].value = readBuffer.texture;
  160. this.highPassUniforms[ "luminosityThreshold" ].value = this.threshold;
  161. this.fsQuad.material = this.materialHighPassFilter;
  162. renderer.setRenderTarget( this.renderTargetBright );
  163. renderer.clear();
  164. this.fsQuad.render( renderer );
  165. // 2. Blur All the mips progressively
  166. var inputRenderTarget = this.renderTargetBright;
  167. for ( var i = 0; i < this.nMips; i ++ ) {
  168. this.fsQuad.material = this.separableBlurMaterials[ i ];
  169. this.separableBlurMaterials[ i ].uniforms[ "colorTexture" ].value = inputRenderTarget.texture;
  170. this.separableBlurMaterials[ i ].uniforms[ "direction" ].value = UnrealBloomPass.BlurDirectionX;
  171. renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] );
  172. renderer.clear();
  173. this.fsQuad.render( renderer );
  174. this.separableBlurMaterials[ i ].uniforms[ "colorTexture" ].value = this.renderTargetsHorizontal[ i ].texture;
  175. this.separableBlurMaterials[ i ].uniforms[ "direction" ].value = UnrealBloomPass.BlurDirectionY;
  176. renderer.setRenderTarget( this.renderTargetsVertical[ i ] );
  177. renderer.clear();
  178. this.fsQuad.render( renderer );
  179. inputRenderTarget = this.renderTargetsVertical[ i ];
  180. }
  181. // Composite All the mips
  182. this.fsQuad.material = this.compositeMaterial;
  183. this.compositeMaterial.uniforms[ "bloomStrength" ].value = this.strength;
  184. this.compositeMaterial.uniforms[ "bloomRadius" ].value = this.radius;
  185. this.compositeMaterial.uniforms[ "bloomTintColors" ].value = this.bloomTintColors;
  186. renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] );
  187. renderer.clear();
  188. this.fsQuad.render( renderer );
  189. // Blend it additively over the input texture
  190. this.fsQuad.material = this.materialCopy;
  191. this.copyUniforms[ "tDiffuse" ].value = this.renderTargetsHorizontal[ 0 ].texture;
  192. if ( maskActive ) renderer.state.buffers.stencil.setTest( true );
  193. if ( this.renderToScreen ) {
  194. renderer.setRenderTarget( null );
  195. this.fsQuad.render( renderer );
  196. } else {
  197. renderer.setRenderTarget( readBuffer );
  198. this.fsQuad.render( renderer );
  199. }
  200. // Restore renderer settings
  201. renderer.setClearColor( this.oldClearColor, this.oldClearAlpha );
  202. renderer.autoClear = oldAutoClear;
  203. },
  204. getSeperableBlurMaterial: function ( kernelRadius ) {
  205. return new ShaderMaterial( {
  206. defines: {
  207. "KERNEL_RADIUS": kernelRadius,
  208. "SIGMA": kernelRadius
  209. },
  210. uniforms: {
  211. "colorTexture": { value: null },
  212. "texSize": { value: new Vector2( 0.5, 0.5 ) },
  213. "direction": { value: new Vector2( 0.5, 0.5 ) }
  214. },
  215. vertexShader:
  216. "varying vec2 vUv;\n\
  217. void main() {\n\
  218. vUv = uv;\n\
  219. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\
  220. }",
  221. fragmentShader:
  222. "#include <common>\
  223. varying vec2 vUv;\n\
  224. uniform sampler2D colorTexture;\n\
  225. uniform vec2 texSize;\
  226. uniform vec2 direction;\
  227. \
  228. float gaussianPdf(in float x, in float sigma) {\
  229. return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;\
  230. }\
  231. void main() {\n\
  232. vec2 invSize = 1.0 / texSize;\
  233. float fSigma = float(SIGMA);\
  234. float weightSum = gaussianPdf(0.0, fSigma);\
  235. vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;\
  236. for( int i = 1; i < KERNEL_RADIUS; i ++ ) {\
  237. float x = float(i);\
  238. float w = gaussianPdf(x, fSigma);\
  239. vec2 uvOffset = direction * invSize * x;\
  240. vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb;\
  241. vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb;\
  242. diffuseSum += (sample1 + sample2) * w;\
  243. weightSum += 2.0 * w;\
  244. }\
  245. gl_FragColor = vec4(diffuseSum/weightSum, 1.0);\n\
  246. }"
  247. } );
  248. },
  249. getCompositeMaterial: function ( nMips ) {
  250. return new ShaderMaterial( {
  251. defines: {
  252. "NUM_MIPS": nMips
  253. },
  254. uniforms: {
  255. "blurTexture1": { value: null },
  256. "blurTexture2": { value: null },
  257. "blurTexture3": { value: null },
  258. "blurTexture4": { value: null },
  259. "blurTexture5": { value: null },
  260. "dirtTexture": { value: null },
  261. "bloomStrength": { value: 1.0 },
  262. "bloomFactors": { value: null },
  263. "bloomTintColors": { value: null },
  264. "bloomRadius": { value: 0.0 }
  265. },
  266. vertexShader:
  267. "varying vec2 vUv;\n\
  268. void main() {\n\
  269. vUv = uv;\n\
  270. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\
  271. }",
  272. fragmentShader:
  273. "varying vec2 vUv;\
  274. uniform sampler2D blurTexture1;\
  275. uniform sampler2D blurTexture2;\
  276. uniform sampler2D blurTexture3;\
  277. uniform sampler2D blurTexture4;\
  278. uniform sampler2D blurTexture5;\
  279. uniform sampler2D dirtTexture;\
  280. uniform float bloomStrength;\
  281. uniform float bloomRadius;\
  282. uniform float bloomFactors[NUM_MIPS];\
  283. uniform vec3 bloomTintColors[NUM_MIPS];\
  284. \
  285. float lerpBloomFactor(const in float factor) { \
  286. float mirrorFactor = 1.2 - factor;\
  287. return mix(factor, mirrorFactor, bloomRadius);\
  288. }\
  289. \
  290. void main() {\
  291. gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + \
  292. lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + \
  293. lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + \
  294. lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + \
  295. lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );\
  296. }"
  297. } );
  298. }
  299. } );
  300. UnrealBloomPass.BlurDirectionX = new Vector2( 1.0, 0.0 );
  301. UnrealBloomPass.BlurDirectionY = new Vector2( 0.0, 1.0 );
  302. export { UnrealBloomPass };