import { AdditiveBlending, Color, HalfFloatType, MeshBasicMaterial, ShaderMaterial, UniformsUtils, Vector2, Vector3, WebGLRenderTarget, NoBlending, NormalBlending } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js'; /** * UnrealBloomPass is inspired by the bloom pass of Unreal Engine. It creates a * mip map chain of bloom textures and blurs them with different radii. Because * of the weighted combination of mips, and because larger blurs are done on * higher mips, this effect provides good quality and performance. * * Reference: * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ */ class BloomPass extends Pass { constructor( width, height ) { super(); this.strength = 1 this.interation = 6; this.radius = 1; this.threshold = 1; this.width = ( width !== undefined ) ? width : 256 this.height = ( height !== undefined ) ? height : 256 this.clearColor = new Color( 0, 0, 0 ); // render targets this.renderTargetsDownSample = []; this.renderTargetsUpSample = []; let resx = Math.round( this.width / 2 ); let resy = Math.round( this.height / 2 ); this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'; this.renderTargetBright.texture.generateMipmaps = false; for(let i = 0; i < this.interation + 1; i++) { const rendererTargetDownSample = new WebGLRenderTarget(resx, resy, { type: HalfFloatType}); rendererTargetDownSample.texture.name = 'DownSample_' + i; rendererTargetDownSample.texture.generateMipmaps = false; this.renderTargetsDownSample.push(rendererTargetDownSample); if(i == this.interation) break; const rendererTargetUpSample = new WebGLRenderTarget(resx, resy, { type: HalfFloatType}); rendererTargetUpSample.texture.name = 'UpSample_' + i; rendererTargetUpSample.texture.generateMipmaps = false; this.renderTargetsUpSample.push(rendererTargetUpSample); resx = Math.round( resx / 2 ); resy = Math.round( resy / 2 ); } // console.log(this.renderTargetsDownSample, this.renderTargetsUpSample) // this.renderTargetDown = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); // this.renderTargetDown.texture.generateMipmaps = false; // luminosity high pass material const highPassShader = LuminosityHighPassShader; this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms ); this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; this.highPassUniforms[ 'smoothWidth' ].value = 0.01; this.materialHighPassFilter = new ShaderMaterial( { uniforms: this.highPassUniforms, vertexShader: highPassShader.vertexShader, fragmentShader: highPassShader.fragmentShader } ); // blur material this.downSampleMaterial = new ShaderMaterial({ uniforms: UniformsUtils.clone( DownSampleShader.uniforms ), vertexShader: DownSampleShader.vertexShader, fragmentShader: DownSampleShader.fragmentShader, blending: NoBlending }) this.upSampleMaterial = new ShaderMaterial({ uniforms: UniformsUtils.clone( UpSampleShader.uniforms ), vertexShader: UpSampleShader.vertexShader, fragmentShader: UpSampleShader.fragmentShader, blending: NoBlending }) // blend material const copyShader = BlendShader; this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); this.copyMaterial = new ShaderMaterial( { uniforms: this.copyUniforms, vertexShader: copyShader.vertexShader, fragmentShader: copyShader.fragmentShader, blending: AdditiveBlending, // depthTest: false, // depthWrite: false, // transparent: true } ); this.enabled = true; this.needsSwap = false; this._oldClearColor = new Color(); this.oldClearAlpha = 1; this.fsQuad = new FullScreenQuad( null ); } render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { renderer.getClearColor( this._oldClearColor ); this.oldClearAlpha = renderer.getClearAlpha(); const oldAutoClear = renderer.autoClear; renderer.autoClear = false; renderer.setClearColor( this.clearColor, 0 ); // 1. Extract Bright Areas this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; this.fsQuad.material = this.materialHighPassFilter; renderer.setRenderTarget( this.renderTargetBright ); renderer.clear(); this.fsQuad.render( renderer ); // 2. DownSample this.downSampleMaterial.uniforms[ 'offset' ].value = this.radius; for(let i = 0; i < this.interation + 1; i++) { let preTarget = this.renderTargetsDownSample[i-1]; if(i == 0) { preTarget = this.renderTargetBright; } this.downSampleMaterial.uniforms[ 'tDiffuse' ].value = preTarget.texture this.downSampleMaterial.uniforms[ 'resolution' ].value.set(preTarget.width, preTarget.height); this.renderPass(renderer, this.downSampleMaterial, this.renderTargetsDownSample[i]); } // 3. UpSample this.upSampleMaterial.uniforms[ 'offset' ].value = this.radius; for(let i = this.interation - 1; i >= 0; i--) { let preTarget = this.renderTargetsUpSample[i + 1]; let addTarget = this.renderTargetsDownSample[i] if(i == this.interation - 1) { preTarget = this.renderTargetsDownSample[i + 1]; } this.upSampleMaterial.uniforms[ 'tDiffuse' ].value = preTarget.texture this.upSampleMaterial.uniforms[ 'tAddDiffuse' ].value = addTarget.texture this.upSampleMaterial.uniforms[ 'resolution' ].value.set(preTarget.width, preTarget.height); this.renderPass(renderer, this.upSampleMaterial, this.renderTargetsUpSample[i]); } this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.renderTargetsUpSample[0].texture; this.copyMaterial.uniforms[ 'strength' ].value = this.strength; this.renderPass( renderer, this.copyMaterial, readBuffer ); // Restore renderer settings renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); renderer.autoClear = oldAutoClear; } renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { // save original state renderer.getClearColor( this._oldClearColor ); this.oldClearAlpha = renderer.getClearAlpha(); const oldAutoClear = renderer.autoClear; renderer.setRenderTarget( renderTarget ); // setup pass state renderer.autoClear = false; if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) { renderer.setClearColor( clearColor ); renderer.setClearAlpha( clearAlpha || 0 ); renderer.clear(); } this.fsQuad.material = passMaterial; this.fsQuad.render( renderer ); // restore original state renderer.autoClear = oldAutoClear; renderer.setClearColor( this._oldClearColor ); renderer.setClearAlpha( this.oldClearAlpha ); } dispose() { for ( let i = 0; i < this.renderTargetsDownSample.length; i ++ ) { this.renderTargetsDownSample[ i ].dispose(); } for ( let i = 0; i < this.renderTargetsUpSample.length; i ++ ) { this.renderTargetsUpSample[ i ].dispose(); } this.renderTargetBright.dispose(); this.materialHighPassFilter.dispose(); this.downSampleMaterial.dispose(); this.upSampleMaterial.dispose(); this.copyMaterial.dispose(); this.fsQuad.dispose(); } setSize( width, height ) { this.width = width; this.height = height; let resx = Math.round( width / 2 ); let resy = Math.round( height / 2 ); this.renderTargetBright.setSize( resx, resy ); //threshold for ( let i = 0; i < this.nMips; i ++ ) { this.renderTargetsDownSample[ i ].setSize( resx, resy ); this.renderTargetsUpSample[ i ].setSize( resx, resy ); resx = Math.round( resx / 2 ); resy = Math.round( resy / 2 ); } } } const DownSampleShader = { name: 'DownSampleShader', uniforms: { 'tDiffuse': { value: null }, 'resolution': { value: new Vector2() }, 'offset' : { value: 1.0 }, }, vertexShader: /* glsl */` varying vec2 vUv; void main() { vec4 modelViewPosition = modelViewMatrix * vec4( position, 1.0 ); gl_Position = projectionMatrix * modelViewPosition; vUv = uv; }`, fragmentShader: /* glsl */` precision highp sampler2D; uniform sampler2D tDiffuse; uniform vec2 resolution; varying vec2 vUv; float GaussWeight2D(float x, float y, float sigma) { float PI = 3.14159265358; float E = 2.71828182846; float sigma_2 = pow(sigma, 2.0); float a = -(x*x + y*y) / (2.0 * sigma_2); return pow(E, a) / (2.0 * PI * sigma_2); } vec4 GaussNxN(sampler2D tex, vec2 uv, int n, vec2 stride, float sigma) { vec4 color = vec4(0.0); int r = n / 2; float weight = 0.0; for(int i=-r; i<=r; i++) { for(int j=-r; j<=r; j++) { float w = GaussWeight2D(float(i), float(j), sigma); vec2 coord = uv + vec2(i, j) * stride; color += texture2D(tex, coord) * w; weight += w; } } color /= weight; return color; } void main() { vec4 sum = vec4(0.0); vec2 stride = 1.0 / resolution; sum = GaussNxN(tDiffuse, vUv, 5, stride, 1.0); gl_FragColor = sum; }` }; const UpSampleShader = { name: 'UpSampleShader', uniforms: { 'tDiffuse': { value: null }, 'tAddDiffuse': { value: null }, 'resolution': { value: new Vector2() }, 'offset' : { value: 1.0 }, }, vertexShader: /* glsl */` varying vec2 vUv; void main() { vec4 modelViewPosition = modelViewMatrix * vec4( position, 1.0 ); gl_Position = projectionMatrix * modelViewPosition; vUv = uv; }`, fragmentShader: /* glsl */` precision highp sampler2D; uniform sampler2D tDiffuse; uniform sampler2D tAddDiffuse; uniform vec2 resolution; uniform float offset; varying vec2 vUv; float GaussWeight2D(float x, float y, float sigma) { float PI = 3.14159265358; float E = 2.71828182846; float sigma_2 = pow(sigma, 2.0); float a = -(x*x + y*y) / (2.0 * sigma_2); return pow(E, a) / (2.0 * PI * sigma_2); } vec4 GaussNxN(sampler2D tex, vec2 uv, int n, vec2 stride, float sigma) { vec4 color = vec4(0.0); int r = n / 2; float weight = 0.0; for(int i=-r; i<=r; i++) { for(int j=-r; j<=r; j++) { float w = GaussWeight2D(float(i), float(j), sigma); vec2 coord = uv + vec2(i, j) * stride; color += texture2D(tex, coord) * w; weight += w; } } color /= weight; return color; } void main() { vec2 stride = 1.0 / resolution; vec2 curStride = 1.0 / resolution * 2.0; vec4 sum = GaussNxN(tDiffuse, vUv, 5, stride, offset); vec4 add = GaussNxN(tAddDiffuse, vUv, 5, stride, offset); gl_FragColor = sum + add; }` }; const BlendShader = { name: 'BlendShader', uniforms: { 'tDiffuse': { value: null }, 'strength': { value: 0.2 } }, vertexShader: /* glsl */` varying vec2 vUv; void main() { vUv = uv; vec4 modelViewPosition = modelViewMatrix * vec4( position, 1.0 ); gl_Position = projectionMatrix * modelViewPosition; }`, fragmentShader: /* glsl */` precision highp sampler2D; uniform sampler2D tDiffuse; uniform float strength; varying vec2 vUv; void main() { vec4 bloom = texture2D(tDiffuse, vUv); gl_FragColor = bloom * strength; // gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }` }; export { BloomPass };