├── screenshot.png ├── assets ├── edmonton.png ├── parcel.json ├── isolate_bright.glsl └── blur.glsl ├── config.json ├── .gitattributes ├── README.md ├── project.flow ├── .gitignore ├── custom_index.html ├── LICENSE └── src ├── Main.hx ├── DigitalCircleParcelProgress.hx └── BloomEffect.hx /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamaluik/LuxeBloom/master/screenshot.png -------------------------------------------------------------------------------- /assets/edmonton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamaluik/LuxeBloom/master/assets/edmonton.png -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "window" : { 3 | "width" : 960, 4 | "height" : 640 5 | } 6 | } -------------------------------------------------------------------------------- /assets/parcel.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures" : [ 3 | { "id" : "assets/edmonton.png" } 4 | ], 5 | "shaders" : [ 6 | { "ps_id":"assets/isolate_bright.glsl" }, 7 | { "ps_id":"assets/blur.glsl" } 8 | ] 9 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /assets/isolate_bright.glsl: -------------------------------------------------------------------------------- 1 | uniform sampler2D tex0; 2 | varying vec2 tcoord; 3 | varying vec4 color; 4 | 5 | uniform float brightPassThreshold; 6 | 7 | void main() { 8 | // isolate only the bright colours 9 | vec3 luminanceVector = vec3(0.2125, 0.7154, 0.0721); 10 | vec4 sample = texture2D(tex0, tcoord); 11 | 12 | float luminance = dot(luminanceVector, sample.rgb); 13 | luminance = max(0.0, luminance - brightPassThreshold); 14 | sample.rgb *= sign(luminance); 15 | //sample.a = 1.0; 16 | 17 | gl_FragColor = sample; 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LuxeBloom 2 | 3 | A demo for [Luxe](http://luxeengine.com/) showing one way to use shaders and rendertextures to apply a bloom effect to the screen. 4 | 5 | >PLEASE NOTE! This demo was created using an ALPHA version of [Luxe](http://luxeengine.com/). As such, it may very well be obsolete by the time you look at it. It is current and working as of `alpha-2.0`, however the API can and likely will change without any hesitation! You've been warned! 6 | 7 | [Click here to check out a live demo.](http://fuzzywuzzie.github.io/LuxeBloom/) 8 | 9 | A screenshot of it in action: 10 | 11 | ![Screenshot of the custom preloader in action](https://raw.github.com/FuzzyWuzzie/LuxeBloom/master/screenshot.png) -------------------------------------------------------------------------------- /project.flow: -------------------------------------------------------------------------------- 1 | { 2 | 3 | luxe:{ 4 | window: { 5 | width:960, 6 | height:640, 7 | title:'Bloom', 8 | fullscreen:false, 9 | resizable:true, 10 | borderless:false 11 | } 12 | }, 13 | 14 | project : { 15 | name : 'Luxe Bloom', 16 | version : '0.0.1', 17 | author : 'FuzzyWuzzie', 18 | 19 | app : { 20 | name : 'LuxeBloom', 21 | package : 'com.blazingmammothgames.luxe' 22 | }, 23 | 24 | build : { 25 | dependencies : { 26 | luxe : '*', 27 | } 28 | }, 29 | 30 | files : { 31 | config : 'config.json', 32 | assets : 'assets/', 33 | index : { path:'custom_index.html => index.html', template:'project', not_listed:true } 34 | } 35 | 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Haxe binaries 2 | bin/ 3 | 4 | # Windows image file caches 5 | Thumbs.db 6 | ehthumbs.db 7 | 8 | # Folder config file 9 | Desktop.ini 10 | 11 | # Recycle Bin used on file shares 12 | $RECYCLE.BIN/ 13 | 14 | # Windows Installer files 15 | *.cab 16 | *.msi 17 | *.msm 18 | *.msp 19 | 20 | # Windows shortcuts 21 | *.lnk 22 | 23 | # ========================= 24 | # Operating System Files 25 | # ========================= 26 | 27 | # OSX 28 | # ========================= 29 | 30 | .DS_Store 31 | .AppleDouble 32 | .LSOverride 33 | 34 | # Thumbnails 35 | ._* 36 | 37 | # Files that might appear on external disk 38 | .Spotlight-V100 39 | .Trashes 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | -------------------------------------------------------------------------------- /custom_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | {{#each project.app.web.libs~}} 16 | 17 | {{/each}} 18 | 19 | 20 | 21 | 22 | 23 |

Mouse x controls bloom blur amount, Mouse y controls bloom threshold!

24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kenton Hamaluik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /assets/blur.glsl: -------------------------------------------------------------------------------- 1 | // adapted from https://github.com/mattdesl/lwjgl-basics/wiki/ShaderLesson5 2 | 3 | uniform sampler2D tex0; 4 | varying vec2 tcoord; 5 | varying vec4 color; 6 | 7 | uniform float blur; 8 | uniform vec2 dir; 9 | 10 | void main() { 11 | vec4 sum = vec4(0.0); 12 | 13 | // gaussian 14 | sum += texture2D(tex0, vec2(tcoord.x - 4.0*blur*dir.x, tcoord.y - 4.0*blur*dir.y)) * 0.0162162162; 15 | sum += texture2D(tex0, vec2(tcoord.x - 3.0*blur*dir.x, tcoord.y - 3.0*blur*dir.y)) * 0.0540540541; 16 | sum += texture2D(tex0, vec2(tcoord.x - 2.0*blur*dir.x, tcoord.y - 2.0*blur*dir.y)) * 0.1216216216; 17 | sum += texture2D(tex0, vec2(tcoord.x - 1.0*blur*dir.x, tcoord.y - 1.0*blur*dir.y)) * 0.1945945946; 18 | 19 | sum += texture2D(tex0, vec2(tcoord.x, tcoord.y)) * 0.2270270270; 20 | 21 | sum += texture2D(tex0, vec2(tcoord.x + 1.0*blur*dir.x, tcoord.y + 1.0*blur*dir.y)) * 0.1945945946; 22 | sum += texture2D(tex0, vec2(tcoord.x + 2.0*blur*dir.x, tcoord.y + 2.0*blur*dir.y)) * 0.1216216216; 23 | sum += texture2D(tex0, vec2(tcoord.x + 3.0*blur*dir.x, tcoord.y + 3.0*blur*dir.y)) * 0.0540540541; 24 | sum += texture2D(tex0, vec2(tcoord.x + 4.0*blur*dir.x, tcoord.y + 4.0*blur*dir.y)) * 0.0162162162; 25 | 26 | gl_FragColor = color * sum; 27 | } 28 | -------------------------------------------------------------------------------- /src/Main.hx: -------------------------------------------------------------------------------- 1 | import luxe.Color; 2 | import luxe.Input; 3 | import luxe.Log; 4 | import luxe.Sprite; 5 | import luxe.Vector; 6 | import phoenix.Texture; 7 | import luxe.Parcel; 8 | 9 | class Main extends luxe.Game { 10 | // normal sprites 11 | var cityScape:Sprite; 12 | 13 | var bloomEffect:BloomEffect = new BloomEffect(); 14 | 15 | override function ready() { 16 | // load the parcel 17 | Luxe.loadJSON("assets/parcel.json", function(jsonParcel) { 18 | var parcel = new Parcel(); 19 | parcel.from_json(jsonParcel.json); 20 | 21 | // show a loading bar 22 | // use a fancy custom loading bar (https://github.com/FuzzyWuzzie/CustomLuxePreloader) 23 | new DigitalCircleParcelProgress({ 24 | parcel: parcel, 25 | oncomplete: assetsLoaded 26 | }); 27 | 28 | // start loading! 29 | parcel.load(); 30 | }); 31 | } //ready 32 | 33 | function assetsLoaded(_) { 34 | // load things normally 35 | Luxe.renderer.clear_color = new Color(1, 0, 0, 1); 36 | 37 | var cityScapeTexture:Texture = Luxe.resources.find_texture('assets/edmonton.png'); 38 | cityScape = new Sprite({ 39 | texture: cityScapeTexture, 40 | pos: Luxe.screen.mid, 41 | size: new Vector(cityScapeTexture.width_actual, cityScapeTexture.height_actual), 42 | depth: 0 43 | }); 44 | 45 | bloomEffect.onload(); 46 | } 47 | 48 | override function onkeyup( e:KeyEvent ) { 49 | if(e.keycode == Key.escape) { 50 | Luxe.shutdown(); 51 | } 52 | 53 | } //onkeyup 54 | 55 | override public function onmousemove(event:MouseEvent) { 56 | // set the amount of blur in the bloom filter based on the mouse's x-axis 57 | bloomEffect.radius = 3 * event.pos.x / Luxe.screen.w; 58 | 59 | // set the bloom threshold to the mouse's y-axis 60 | bloomEffect.threshold = event.pos.y / Luxe.screen.h; 61 | } 62 | 63 | override function onprerender() { 64 | bloomEffect.onprerender(); 65 | } 66 | 67 | override function onpostrender() { 68 | bloomEffect.onpostrender(); 69 | } 70 | 71 | } //Main 72 | -------------------------------------------------------------------------------- /src/DigitalCircleParcelProgress.hx: -------------------------------------------------------------------------------- 1 | package ; 2 | 3 | import luxe.resource.Resource; 4 | import luxe.Parcel; 5 | import luxe.Visual; 6 | import luxe.Color; 7 | import luxe.options.ParcelProgressOptions; 8 | import luxe.Text; 9 | import luxe.Vector; 10 | 11 | class DigitalCircleParcelProgress { 12 | var parcel:Parcel; 13 | 14 | var ticks:Array = new Array(); 15 | var progressText:Text; 16 | 17 | var options:ParcelProgressOptions; 18 | 19 | public function new(_options:ParcelProgressOptions) { 20 | options = _options; 21 | 22 | if(options.bar == null) { 23 | options.bar = new Color( ).rgb(0xC7F464); 24 | } 25 | 26 | if(options.background == null) { 27 | options.background = new Color( ).rgb(0x556270); 28 | } 29 | 30 | // create the ticks 31 | for(i in 0...20) { 32 | var angle:Float = (i * (Math.PI / 10)) - (Math.PI / 2); 33 | var start:Vector = new Vector(30 * Math.cos(angle) + Luxe.screen.mid.x, 30 * Math.sin(angle) + Luxe.screen.mid.y); 34 | var end:Vector = new Vector(40 * Math.cos(angle) + Luxe.screen.mid.x, 40 * Math.sin(angle) + Luxe.screen.mid.y); 35 | 36 | ticks.push(new Visual({ 37 | color: options.background, 38 | no_scene: true, 39 | geometry : Luxe.draw.line({ 40 | p0: start, 41 | p1: end 42 | }), 43 | depth: 998 44 | })); 45 | } 46 | 47 | // create the percent text 48 | progressText = new luxe.Text({ 49 | text: '0%', 50 | align: center, 51 | align_vertical: center, 52 | point_size: 12, 53 | pos: Luxe.screen.mid, 54 | color: new Color().rgb(0xFF6B6B) 55 | }); 56 | 57 | // intercept the oncomplete and onprogress callbacks 58 | options.parcel.options.oncomplete = oncomplete; 59 | options.parcel.options.onprogress = onprogress; 60 | } 61 | 62 | public function onprogress(r:Resource) { 63 | var amount = options.parcel.current_count / options.parcel.total_items; 64 | for(i in 0...20) { 65 | if(amount >= (i / 20)) { 66 | ticks[i].color = options.bar; 67 | } 68 | } 69 | 70 | progressText.text = Std.int(amount * 100) + "%"; 71 | } 72 | 73 | public function oncomplete(p:Parcel) { 74 | for(tick in ticks) { 75 | tick.destroy(); 76 | } 77 | progressText.destroy(); 78 | 79 | if(options.oncomplete != null) { 80 | options.oncomplete(options.parcel); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/BloomEffect.hx: -------------------------------------------------------------------------------- 1 | package ; 2 | 3 | import luxe.Visual; 4 | import luxe.Rectangle; 5 | import phoenix.Batcher; 6 | import phoenix.geometry.QuadGeometry; 7 | import phoenix.RenderTexture; 8 | import phoenix.Shader; 9 | import luxe.Vector; 10 | import luxe.Color; 11 | 12 | /** 13 | * @author KentonHamaluik (@FuzzyWuzzie on GitHub) 14 | */ 15 | class BloomEffect { 16 | var bloomBrightShader:Shader; 17 | var bloomBlurShader:Shader; 18 | 19 | var screenRenderTexture:RenderTexture; 20 | var screenBatcher:Batcher; 21 | var brightBatcher:Batcher; 22 | 23 | var postBrightTexture:RenderTexture; 24 | var horizBlurBatcher:Batcher; 25 | var postHorizBlurTexture:RenderTexture; 26 | var vertBlurBatcher:Batcher; 27 | 28 | var screenVisual:Visual; 29 | var brightVisual:Visual; 30 | var horizBlurVisual:Visual; 31 | var vertBlurVisual:Visual; 32 | 33 | var clearColour:Color = new Color(0, 0, 0, 0); 34 | var loaded:Bool = false; 35 | var po2:Float; 36 | 37 | @:isVar public var threshold(default, set):Float; 38 | @:isVar public var radius(default, set):Float; 39 | 40 | function set_threshold(_t:Float) { 41 | if(bloomBrightShader != null) { 42 | bloomBrightShader.set_float('brightPassThreshold', _t); 43 | } 44 | return threshold = _t; 45 | } 46 | 47 | function set_radius(_r:Float) { 48 | if(bloomBlurShader != null) { 49 | bloomBlurShader.set_float('blur', _r / po2); 50 | } 51 | return radius = _r; 52 | } 53 | 54 | public function new() { 55 | 56 | } 57 | 58 | public function onload() { 59 | // calculate the next highest power of 2 from our screen resolution 60 | // so that we can determine how big we need to make the textures 61 | po2 = nextLargestPowerOf2(Math.max(Luxe.screen.w, Luxe.screen.h)); 62 | 63 | // BEGIN SHADERS 64 | bloomBrightShader = Luxe.resources.find_shader('assets/isolate_bright.glsl|default'); 65 | bloomBrightShader.set_float('brightPassThreshold', 0.5); 66 | 67 | bloomBlurShader = Luxe.resources.find_shader('assets/blur.glsl|default'); 68 | bloomBlurShader.set_float('blur', 1 / po2); 69 | bloomBlurShader.set_vector2('dir', new Vector(1, 0)); 70 | // END SHADERS 71 | 72 | // BEGIN SCREEN 73 | screenRenderTexture = new RenderTexture(Luxe.resources, new Vector(po2, po2)); 74 | 75 | screenBatcher = Luxe.renderer.create_batcher({ 76 | name: 'screenBatcher', 77 | no_add: true 78 | }); 79 | screenBatcher.view.viewport = Luxe.camera.viewport; 80 | 81 | screenVisual = new Visual({ 82 | texture: screenRenderTexture, 83 | pos: new Vector(0, Luxe.screen.h - po2), 84 | size: new Vector(po2, po2), 85 | batcher: screenBatcher, 86 | }); 87 | cast(screenVisual.geometry, QuadGeometry).flipy = true; 88 | // END SCREEN 89 | 90 | // BEGIN BRIGHTNESS CLAMPER 91 | brightBatcher = Luxe.renderer.create_batcher({ 92 | name: 'brightBatcher', 93 | no_add: true 94 | }); 95 | brightBatcher.view.viewport = Luxe.camera.viewport; 96 | 97 | brightVisual = new Visual({ 98 | texture: screenRenderTexture, 99 | pos: new Vector(), 100 | size: new Vector(po2, po2), 101 | batcher: brightBatcher, 102 | shader: bloomBrightShader 103 | }); 104 | 105 | postBrightTexture = new RenderTexture(Luxe.resources, new Vector(po2, po2)); 106 | // END BRIGHTNESS CLAMPER 107 | 108 | // BEGIN HORIZONTAL BLUR 109 | postHorizBlurTexture = new RenderTexture(Luxe.resources, new Vector(po2, po2)); 110 | 111 | horizBlurBatcher = Luxe.renderer.create_batcher({ 112 | name: 'horizBlurBatcher', 113 | no_add: true 114 | }); 115 | horizBlurBatcher.view.viewport = Luxe.camera.viewport; 116 | 117 | horizBlurVisual = new Visual({ 118 | texture: postBrightTexture, 119 | pos: new Vector(), 120 | size: new Vector(po2, po2), 121 | batcher: horizBlurBatcher, 122 | shader: bloomBlurShader 123 | }); 124 | // END HORIZONTAL BLUR 125 | 126 | // BEGIN VERTICAL BLUR 127 | vertBlurBatcher = Luxe.renderer.create_batcher({ 128 | name: 'vertBlurBatcher', 129 | no_add: true 130 | }); 131 | vertBlurBatcher.view.viewport = Luxe.camera.viewport; 132 | 133 | vertBlurVisual = new Visual({ 134 | texture: postHorizBlurTexture, 135 | pos: new Vector(0, Luxe.screen.h - po2), 136 | size: new Vector(po2, po2), 137 | batcher: vertBlurBatcher, 138 | shader: bloomBlurShader 139 | }); 140 | cast(vertBlurVisual.geometry, QuadGeometry).flipy = true; 141 | // END VERTICAL BLUR 142 | 143 | // set the default uniform values 144 | radius = 2; 145 | threshold = 0.5; 146 | 147 | loaded = true; 148 | } 149 | 150 | public function onprerender() { 151 | if(!loaded) { 152 | // if the parcel hasn't loaded yet, don't bother with this stuff 153 | return; 154 | } 155 | 156 | // render everything to our screen render texture 157 | Luxe.renderer.target = screenRenderTexture; 158 | } 159 | 160 | public function onpostrender() { 161 | if(!loaded) { 162 | // if the parcel hasn't loaded yet, don't bother with this stuff 163 | return; 164 | } 165 | 166 | // by now, everything will have been rendered to the screenRenderTexture 167 | 168 | // do another pass, rendering only the _bright_ areas of the image 169 | // this result will be stored in `postBrightTexture` 170 | Luxe.renderer.target = postBrightTexture; 171 | Luxe.renderer.clear(clearColour); 172 | brightBatcher.draw(); 173 | 174 | // do another pass, which will blur the `postBrightTexture` image 175 | // and store the result in the `postHorizBlurTexture` 176 | Luxe.renderer.target = postHorizBlurTexture; 177 | Luxe.renderer.clear(clearColour); 178 | // set the blur direction for the shader 179 | bloomBlurShader.set_vector2('dir', new Vector(1, 0)); 180 | horizBlurBatcher.draw(); 181 | 182 | // do another pass, this time rendering to the screen (finally!) 183 | Luxe.renderer.target = null; 184 | Luxe.renderer.clear(clearColour); 185 | 186 | // draw our saved screen image (it will have been rendered normally in the onrender() function) 187 | screenBatcher.draw(); 188 | 189 | // now draw the bloom effect on top of the saved screen image 190 | // using additive blending 191 | Luxe.renderer.blend_mode(BlendMode.src_alpha, BlendMode.one); 192 | 193 | // set the blur shader that this is using to blur in the vertical direction 194 | // so that we get a nice 2D gaussian blur 195 | bloomBlurShader.set_vector2('dir', new Vector(0, 1)); 196 | vertBlurBatcher.draw(); 197 | 198 | // return to default blending 199 | Luxe.renderer.blend_mode(); 200 | } 201 | 202 | private static function nextLargestPowerOf2(dimen:Float):Float { 203 | var y:Float = Math.floor(Math.log(dimen)/Math.log(2)); 204 | return Math.pow(2, y + 1); 205 | } 206 | } --------------------------------------------------------------------------------