From f8526e1ac797dfd1e2ca1e89eb1daebbfc085c4a Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 24 Jun 2019 19:39:57 -0400 Subject: [PATCH 1/5] Bloom --- .../bloomshaders/alpha_bloom.fragment.glsl | 23 + .../bloomshaders/alpha_gaussian.fragment.glsl | 26 ++ .../alpha_threshold.fragment.glsl | 15 + core/assets/bloomshaders/bloom.fragment.glsl | 23 + .../assets/bloomshaders/blurspace.vertex.glsl | 31 ++ .../bloomshaders/gaussian.fragment.glsl | 26 ++ .../bloomshaders/maskedtreshold.fragment.glsl | 17 + .../bloomshaders/screenspace.vertex.glsl | 13 + .../bloomshaders/threshold.fragment.glsl | 15 + .../src/io/anuke/mindustry/core/Renderer.java | 60 ++- .../io/anuke/mindustry/graphics/Bloom.java | 417 ++++++++++++++++++ .../anuke/mindustry/graphics/Pixelator.java | 2 +- 12 files changed, 645 insertions(+), 23 deletions(-) create mode 100644 core/assets/bloomshaders/alpha_bloom.fragment.glsl create mode 100644 core/assets/bloomshaders/alpha_gaussian.fragment.glsl create mode 100644 core/assets/bloomshaders/alpha_threshold.fragment.glsl create mode 100644 core/assets/bloomshaders/bloom.fragment.glsl create mode 100644 core/assets/bloomshaders/blurspace.vertex.glsl create mode 100644 core/assets/bloomshaders/gaussian.fragment.glsl create mode 100644 core/assets/bloomshaders/maskedtreshold.fragment.glsl create mode 100644 core/assets/bloomshaders/screenspace.vertex.glsl create mode 100644 core/assets/bloomshaders/threshold.fragment.glsl create mode 100644 core/src/io/anuke/mindustry/graphics/Bloom.java diff --git a/core/assets/bloomshaders/alpha_bloom.fragment.glsl b/core/assets/bloomshaders/alpha_bloom.fragment.glsl new file mode 100644 index 0000000000..00444c1dca --- /dev/null +++ b/core/assets/bloomshaders/alpha_bloom.fragment.glsl @@ -0,0 +1,23 @@ +#ifdef GL_ES +#define LOWP lowp +#define MED mediump +precision lowp float; +#else +#define LOWP +#define MED +#endif +uniform sampler2D u_texture0; +uniform sampler2D u_texture1; +uniform float BloomIntensity; +uniform float OriginalIntensity; + +varying MED vec2 v_texCoords; + +void main() +{ + + vec4 original = texture2D(u_texture0, v_texCoords) * OriginalIntensity; + vec4 bloom = texture2D(u_texture1, v_texCoords) * BloomIntensity; + original = original * (vec4(1.0) - bloom); + gl_FragColor = original + bloom; +} \ No newline at end of file diff --git a/core/assets/bloomshaders/alpha_gaussian.fragment.glsl b/core/assets/bloomshaders/alpha_gaussian.fragment.glsl new file mode 100644 index 0000000000..e060efc278 --- /dev/null +++ b/core/assets/bloomshaders/alpha_gaussian.fragment.glsl @@ -0,0 +1,26 @@ +#ifdef GL_ES +#define LOWP lowp +#define MED mediump +precision lowp float; +#else +#define LOWP +#define MED +#endif + +uniform sampler2D u_texture; +varying MED vec2 v_texCoords0; +varying MED vec2 v_texCoords1; +varying MED vec2 v_texCoords2; +varying MED vec2 v_texCoords3; +varying MED vec2 v_texCoords4; +const float center = 0.2270270270; +const float close = 0.3162162162; +const float far = 0.0702702703; +void main() +{ + gl_FragColor = far * texture2D(u_texture, v_texCoords0) + + close * texture2D(u_texture, v_texCoords1) + + center * texture2D(u_texture, v_texCoords2) + + close * texture2D(u_texture, v_texCoords3) + + far * texture2D(u_texture, v_texCoords4); +} diff --git a/core/assets/bloomshaders/alpha_threshold.fragment.glsl b/core/assets/bloomshaders/alpha_threshold.fragment.glsl new file mode 100644 index 0000000000..698e4e712f --- /dev/null +++ b/core/assets/bloomshaders/alpha_threshold.fragment.glsl @@ -0,0 +1,15 @@ +#ifdef GL_ES +#define LOWP lowp +#define MED mediump +precision lowp float; +#else +#define LOWP +#define MED +#endif +uniform sampler2D u_texture0; +uniform vec2 threshold; +varying MED vec2 v_texCoords; +void main() +{ + gl_FragColor = (texture2D(u_texture0, v_texCoords) - vec4(threshold.r)) * threshold.g; +} \ No newline at end of file diff --git a/core/assets/bloomshaders/bloom.fragment.glsl b/core/assets/bloomshaders/bloom.fragment.glsl new file mode 100644 index 0000000000..93bbfe3737 --- /dev/null +++ b/core/assets/bloomshaders/bloom.fragment.glsl @@ -0,0 +1,23 @@ +#ifdef GL_ES +#define LOWP lowp +#define MED mediump +precision lowp float; +#else +#define LOWP +#define MED +#endif +uniform sampler2D u_texture0; +uniform sampler2D u_texture1; +uniform float BloomIntensity; +uniform float OriginalIntensity; + +varying MED vec2 v_texCoords; + +void main() +{ + + vec3 original = texture2D(u_texture0, v_texCoords).rgb; + vec3 bloom = texture2D(u_texture1, v_texCoords).rgb * BloomIntensity; + original = OriginalIntensity * (original - original * bloom); + gl_FragColor.rgb = original + bloom; +} \ No newline at end of file diff --git a/core/assets/bloomshaders/blurspace.vertex.glsl b/core/assets/bloomshaders/blurspace.vertex.glsl new file mode 100644 index 0000000000..1d7135d9ab --- /dev/null +++ b/core/assets/bloomshaders/blurspace.vertex.glsl @@ -0,0 +1,31 @@ +#ifdef GL_ES +#define MED mediump +#else +#define MED +#endif + +attribute vec4 a_position; +attribute vec2 a_texCoord0; +uniform vec2 dir; +uniform vec2 size; +varying MED vec2 v_texCoords0; +varying MED vec2 v_texCoords1; +varying MED vec2 v_texCoords2; +varying MED vec2 v_texCoords3; +varying MED vec2 v_texCoords4; +const vec2 futher = vec2(3.2307692308, 3.2307692308); +const vec2 closer = vec2(1.3846153846, 1.3846153846); +void main() +{ + vec2 sizeAndDir = dir / size; + vec2 f = futher*sizeAndDir; + vec2 c = closer*sizeAndDir; + + v_texCoords0 = a_texCoord0 - f; + v_texCoords1 = a_texCoord0 - c; + v_texCoords2 = a_texCoord0; + v_texCoords3 = a_texCoord0 + c; + v_texCoords4 = a_texCoord0 + f; + + gl_Position = a_position; +} \ No newline at end of file diff --git a/core/assets/bloomshaders/gaussian.fragment.glsl b/core/assets/bloomshaders/gaussian.fragment.glsl new file mode 100644 index 0000000000..5373349f85 --- /dev/null +++ b/core/assets/bloomshaders/gaussian.fragment.glsl @@ -0,0 +1,26 @@ +#ifdef GL_ES +#define LOWP lowp +#define MED mediump +precision lowp float; +#else +#define LOWP +#define MED +#endif + +uniform sampler2D u_texture; +varying MED vec2 v_texCoords0; +varying MED vec2 v_texCoords1; +varying MED vec2 v_texCoords2; +varying MED vec2 v_texCoords3; +varying MED vec2 v_texCoords4; +const float center = 0.2270270270; +const float close = 0.3162162162; +const float far = 0.0702702703; +void main() +{ + gl_FragColor.rgb = far * texture2D(u_texture, v_texCoords0).rgb + + close * texture2D(u_texture, v_texCoords1).rgb + + center * texture2D(u_texture, v_texCoords2).rgb + + close * texture2D(u_texture, v_texCoords3).rgb + + far * texture2D(u_texture, v_texCoords4).rgb; +} diff --git a/core/assets/bloomshaders/maskedtreshold.fragment.glsl b/core/assets/bloomshaders/maskedtreshold.fragment.glsl new file mode 100644 index 0000000000..a65064e380 --- /dev/null +++ b/core/assets/bloomshaders/maskedtreshold.fragment.glsl @@ -0,0 +1,17 @@ +#ifdef GL_ES +#define LOWP lowp +#define MED mediump +precision lowp float; +#else +#define LOWP +#define MED +#endif +uniform sampler2D u_texture0; +uniform vec2 threshold; +varying MED vec2 v_texCoords; +void main() +{ + vec4 tex = texture2D(u_texture0, v_texCoords); + vec3 colors = (tex.rgb - threshold.r) * threshold.g * tex.a; + gl_FragColor = vec4(colors, tex.a); +} \ No newline at end of file diff --git a/core/assets/bloomshaders/screenspace.vertex.glsl b/core/assets/bloomshaders/screenspace.vertex.glsl new file mode 100644 index 0000000000..8e521c9a75 --- /dev/null +++ b/core/assets/bloomshaders/screenspace.vertex.glsl @@ -0,0 +1,13 @@ +#ifdef GL_ES +#define MED mediump +#else +#define MED +#endif +attribute vec4 a_position; +attribute vec2 a_texCoord0; +varying MED vec2 v_texCoords; +void main() +{ + v_texCoords = a_texCoord0; + gl_Position = a_position; +} \ No newline at end of file diff --git a/core/assets/bloomshaders/threshold.fragment.glsl b/core/assets/bloomshaders/threshold.fragment.glsl new file mode 100644 index 0000000000..52de3f18ed --- /dev/null +++ b/core/assets/bloomshaders/threshold.fragment.glsl @@ -0,0 +1,15 @@ +#ifdef GL_ES +#define LOWP lowp +#define MED mediump +precision lowp float; +#else +#define LOWP +#define MED +#endif +uniform sampler2D u_texture0; +uniform vec2 threshold; +varying MED vec2 v_texCoords; +void main() +{ + gl_FragColor.rgb = (texture2D(u_texture0, v_texCoords).rgb - vec3(threshold.x)) * threshold.y; +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index fef6d6bcaa..75398e713e 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -35,6 +35,7 @@ public class Renderer implements ApplicationListener{ public final Pixelator pixelator = new Pixelator(); public FrameBuffer shieldBuffer = new FrameBuffer(2, 2); + private Bloom bloom; private Color clearColor; private float targetscale = io.anuke.arc.scene.ui.layout.Unit.dp.scl(4); private float camerascale = targetscale; @@ -43,6 +44,7 @@ public class Renderer implements ApplicationListener{ public Renderer(){ camera = new Camera(); + bloom = new Bloom(true); Lines.setCircleVertices(20); Shaders.init(); @@ -130,9 +132,16 @@ public class Renderer implements ApplicationListener{ minimap.dispose(); shieldBuffer.dispose(); blocks.dispose(); + bloom.dispose(); Events.fire(new DisposeEvent()); } + @Override + public void resize(int width, int height){ + bloom.dispose(); + bloom = new Bloom(true); + } + void updateShake(float scale){ if(shaketime > 0){ float intensity = shakeIntensity * (settings.getInt("screenshake", 4) / 4f) * scale; @@ -163,9 +172,9 @@ public class Renderer implements ApplicationListener{ blocks.floor.drawFloor(); - drawAndInterpolate(groundEffectGroup, e -> e instanceof BelowLiquidTrait); - drawAndInterpolate(puddleGroup); - drawAndInterpolate(groundEffectGroup, e -> !(e instanceof BelowLiquidTrait)); + draw(groundEffectGroup, e -> e instanceof BelowLiquidTrait); + draw(puddleGroup); + draw(groundEffectGroup, e -> !(e instanceof BelowLiquidTrait)); blocks.processBlocks(); @@ -196,11 +205,18 @@ public class Renderer implements ApplicationListener{ drawAllTeams(true); - drawAndInterpolate(bulletGroup); - drawAndInterpolate(effectGroup); + Draw.flush(); + bloom.setClearColor(0f, 0f, 0f, 0f); + bloom.capture(); + + draw(bulletGroup); + draw(effectGroup); + + Draw.flush(); + bloom.render(); overlays.drawBottom(); - drawAndInterpolate(playerGroup, p -> true, Player::drawBuildRequests); + draw(playerGroup, p -> true, Player::drawBuildRequests); if(Entities.countInBounds(shieldGroup) > 0){ if(settings.getBool("animatedshields")){ @@ -223,7 +239,7 @@ public class Renderer implements ApplicationListener{ overlays.drawTop(); - drawAndInterpolate(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName); + draw(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName); Draw.color(); Draw.flush(); @@ -240,12 +256,12 @@ public class Renderer implements ApplicationListener{ for(EntityGroup group : unitGroups){ if(!group.isEmpty()){ - drawAndInterpolate(group, unit -> !unit.isDead(), draw::accept); + draw(group, unit -> !unit.isDead(), draw::accept); } } if(!playerGroup.isEmpty()){ - drawAndInterpolate(playerGroup, unit -> !unit.isDead(), draw::accept); + draw(playerGroup, unit -> !unit.isDead(), draw::accept); } Draw.color(); @@ -257,12 +273,12 @@ public class Renderer implements ApplicationListener{ for(EntityGroup group : unitGroups){ if(!group.isEmpty()){ - drawAndInterpolate(group, unit -> unit.isFlying() && !unit.isDead(), baseUnit -> baseUnit.drawShadow(trnsX, trnsY)); + draw(group, unit -> unit.isFlying() && !unit.isDead(), baseUnit -> baseUnit.drawShadow(trnsX, trnsY)); } } if(!playerGroup.isEmpty()){ - drawAndInterpolate(playerGroup, unit -> unit.isFlying() && !unit.isDead(), player -> player.drawShadow(trnsX, trnsY)); + draw(playerGroup, unit -> unit.isFlying() && !unit.isDead(), player -> player.drawShadow(trnsX, trnsY)); } Draw.color(); @@ -275,27 +291,27 @@ public class Renderer implements ApplicationListener{ if(group.count(p -> p.isFlying() == flying) + playerGroup.count(p -> p.isFlying() == flying && p.getTeam() == team) == 0 && flying) continue; - drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder); - drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team && !p.isDead(), Unit::drawUnder); + draw(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder); + draw(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team && !p.isDead(), Unit::drawUnder); - drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawAll); - drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawAll); + draw(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawAll); + draw(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawAll); blocks.drawTeamBlocks(Layer.turret, team); - drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver); - drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver); + draw(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver); + draw(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver); } } - public void drawAndInterpolate(EntityGroup group){ - drawAndInterpolate(group, t -> true, DrawTrait::draw); + public void draw(EntityGroup group){ + draw(group, t -> true, DrawTrait::draw); } - public void drawAndInterpolate(EntityGroup group, Predicate toDraw){ - drawAndInterpolate(group, toDraw, DrawTrait::draw); + public void draw(EntityGroup group, Predicate toDraw){ + draw(group, toDraw, DrawTrait::draw); } - public void drawAndInterpolate(EntityGroup group, Predicate toDraw, Consumer drawer){ + public void draw(EntityGroup group, Predicate toDraw, Consumer drawer){ Entities.draw(group, toDraw, drawer); } diff --git a/core/src/io/anuke/mindustry/graphics/Bloom.java b/core/src/io/anuke/mindustry/graphics/Bloom.java new file mode 100644 index 0000000000..734992d8c0 --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/Bloom.java @@ -0,0 +1,417 @@ +package io.anuke.mindustry.graphics; + +import io.anuke.arc.Core; +import io.anuke.arc.graphics.*; +import io.anuke.arc.graphics.Pixmap.Format; +import io.anuke.arc.graphics.VertexAttributes.Usage; +import io.anuke.arc.graphics.glutils.FrameBuffer; +import io.anuke.arc.graphics.glutils.Shader; + +/** + * Bloomlib allow easy but efficient way to add bloom effect as post process + * effect + * + * @author kalle_h + */ +public class Bloom{ + + /** + * To use implement bloom more like a glow. Texture alpha channel can be + * used as mask which part are glowing and which are not. see more info at: + * http://www.gamasutra.com/view/feature/2107/realtime_glow.php + *

+ * NOTE: need to be set before bloom instance is created. After that this + * does nothing. + */ + public static boolean useAlphaChannelAsMask = false; + + /** how many blur pass */ + public int blurPasses = 1; + + private Shader tresholdShader; + private Shader bloomShader; + + private Mesh fullScreenQuad; + + private Texture pingPongTex1; + private Texture pingPongTex2; + private Texture original; + + private FrameBuffer frameBuffer; + private FrameBuffer pingPongBuffer1; + private FrameBuffer pingPongBuffer2; + + private Shader blurShader; + + private float bloomIntensity; + private float originalIntensity; + private float threshold; + private int w; + private int h; + private boolean blending = false; + private boolean capturing = false; + private float r = 0f; + private float g = 0f; + private float b = 0f; + private float a = 1f; + private boolean disposeFBO = true; + + /** + * IMPORTANT NOTE CALL THIS WHEN RESUMING + */ + public void resume(){ + bloomShader.begin(); + { + bloomShader.setUniformi("u_texture0", 0); + bloomShader.setUniformi("u_texture1", 1); + } + bloomShader.end(); + + setSize(w, h); + setThreshold(threshold); + setBloomIntesity(bloomIntensity); + setOriginalIntesity(originalIntensity); + + original = frameBuffer.getTexture(); + pingPongTex1 = pingPongBuffer1.getTexture(); + pingPongTex2 = pingPongBuffer2.getTexture(); + } + + /** + * Initialize bloom class that capsulate original scene capturate, + * tresholding, gaussian blurring and blending. Default values: depth = true + * blending = false 32bits = true + */ + public Bloom(){ + initialize(Core.graphics.getWidth() / 4, Core.graphics.getHeight() / 4, + null, false, false, true); + } + + public Bloom(boolean useBlending){ + initialize(Core.graphics.getWidth() / 4, Core.graphics.getHeight() / 4, + null, false, useBlending, true); + } + + /** + * Initialize bloom class that capsulate original scene capturate, + * tresholding, gaussian blurring and blending. + * + * @param FBO_W + * @param FBO_H how big fbo is used for bloom texture, smaller = more blur and + * lot faster but aliasing can be problem + * @param hasDepth do rendering need depth buffer + * @param useBlending does fbo need alpha channel and is blending enabled when final + * image is rendered. This allow to combine background graphics + * and only do blooming on certain objects param use32bitFBO does + * fbo use higher precision than 16bits. + */ + public Bloom(int FBO_W, int FBO_H, boolean hasDepth, boolean useBlending, + boolean use32bitFBO){ + initialize(FBO_W, FBO_H, null, hasDepth, useBlending, use32bitFBO); + + } + + /** + * EXPERT FUNCTIONALITY. no error checking. Use this only if you know what + * you are doing. Remember that bloom.capture() clear the screen so use + * continue instead if that is a problem. + *

+ * Initialize bloom class that capsulate original scene capturate, + * tresholding, gaussian blurring and blending. + *

+ * * @param sceneIsCapturedHere diposing is user responsibility. + * + * @param FBO_W + * @param FBO_H how big fbo is used for bloom texture, smaller = more blur and + * lot faster but aliasing can be problem + * @param useBlending does fbo need alpha channel and is blending enabled when final + * image is rendered. This allow to combine background graphics + * and only do blooming on certain objects param use32bitFBO does + * fbo use higher precision than 16bits. + */ + public Bloom(int FBO_W, int FBO_H, FrameBuffer sceneIsCapturedHere, + boolean useBlending, boolean use32bitFBO){ + + initialize(FBO_W, FBO_H, sceneIsCapturedHere, false, useBlending, + use32bitFBO); + disposeFBO = false; + } + + private void initialize(int FBO_W, int FBO_H, FrameBuffer fbo, + boolean hasDepth, boolean useBlending, boolean use32bitFBO){ + blending = useBlending; + Format format = null; + + if(use32bitFBO){ + if(useBlending){ + format = Format.RGBA8888; + }else{ + format = Format.RGB888; + } + + }else{ + if(useBlending){ + format = Format.RGBA4444; + }else{ + format = Format.RGB565; + } + } + if(fbo == null){ + frameBuffer = new FrameBuffer(format, Core.graphics.getWidth(), + Core.graphics.getHeight(), hasDepth); + }else{ + frameBuffer = fbo; + } + + pingPongBuffer1 = new FrameBuffer(format, FBO_W, FBO_H, false); + + pingPongBuffer2 = new FrameBuffer(format, FBO_W, FBO_H, false); + + original = frameBuffer.getTexture(); + pingPongTex1 = pingPongBuffer1.getTexture(); + pingPongTex2 = pingPongBuffer2.getTexture(); + + fullScreenQuad = createFullScreenQuad(); + final String alpha = useBlending ? "alpha_" : ""; + + bloomShader = createShader("screenspace", alpha + "bloom"); + + if(useAlphaChannelAsMask){ + tresholdShader = createShader("screenspace", "maskedtreshold"); + }else{ + tresholdShader = createShader("screenspace", alpha + "threshold"); + } + + blurShader = createShader("blurspace", alpha + "gaussian"); + + setSize(FBO_W, FBO_H); + setBloomIntesity(2.5f); + setOriginalIntesity(0.8f); + setThreshold(0.5f); + + bloomShader.begin(); + { + bloomShader.setUniformi("u_texture0", 0); + bloomShader.setUniformi("u_texture1", 1); + } + bloomShader.end(); + } + + /** + * Set clearing color for capturing buffer + * + * @param r + * @param g + * @param b + * @param a + */ + public void setClearColor(float r, float g, float b, float a){ + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + /** + * Call this before rendering scene. + */ + public void capture(){ + if(!capturing){ + capturing = true; + frameBuffer.begin(); + Core.gl.glClearColor(r, g, b, a); + Core.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + + } + } + + /** + * Pause capturing to fbo. + */ + public void capturePause(){ + if(capturing){ + capturing = false; + frameBuffer.end(); + } + } + + /** Start capturing again after pause, no clearing is done to framebuffer */ + public void captureContinue(){ + if(!capturing){ + capturing = true; + frameBuffer.begin(); + } + } + + /** + * Call this after scene. Renders the bloomed scene. + */ + public void render(){ + if(capturing){ + capturing = false; + frameBuffer.end(); + } + + Core.gl.glDisable(GL20.GL_BLEND); + Core.gl.glDisable(GL20.GL_DEPTH_TEST); + Core.gl.glDepthMask(false); + + gaussianBlur(); + + if(blending){ + Core.gl.glEnable(GL20.GL_BLEND); + Core.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + } + + pingPongTex1.bind(1); + original.bind(0); + bloomShader.begin(); + { + fullScreenQuad.render(bloomShader, GL20.GL_TRIANGLE_FAN); + } + bloomShader.end(); + + } + + private void gaussianBlur(){ + + // cut bright areas of the picture and blit to smaller fbo + + original.bind(0); + pingPongBuffer1.begin(); + { + tresholdShader.begin(); + { + // tresholdShader.setUniformi("u_texture0", 0); + fullScreenQuad.render(tresholdShader, GL20.GL_TRIANGLE_FAN, 0, + 4); + } + tresholdShader.end(); + } + pingPongBuffer1.end(); + + for(int i = 0; i < blurPasses; i++){ + + pingPongTex1.bind(0); + + // horizontal + pingPongBuffer2.begin(); + { + blurShader.begin(); + { + blurShader.setUniformf("dir", 1f, 0f); + fullScreenQuad.render(blurShader, GL20.GL_TRIANGLE_FAN, 0, + 4); + } + blurShader.end(); + } + pingPongBuffer2.end(); + + pingPongTex2.bind(0); + // vertical + pingPongBuffer1.begin(); + { + blurShader.begin(); + { + blurShader.setUniformf("dir", 0f, 1f); + + fullScreenQuad.render(blurShader, GL20.GL_TRIANGLE_FAN, 0, + 4); + } + blurShader.end(); + } + pingPongBuffer1.end(); + } + } + + /** + * set intensity for bloom. higher mean more brightening for spots that are + * over threshold + * + * @param intensity multiplier for blurred texture in combining phase. must be + * positive. + */ + public void setBloomIntesity(float intensity){ + bloomIntensity = intensity; + bloomShader.begin(); + { + bloomShader.setUniformf("BloomIntensity", intensity); + } + bloomShader.end(); + } + + /** + * set intensity for original scene. under 1 mean darkening and over 1 means + * lightening + * + * @param intensity multiplier for captured texture in combining phase. must be + * positive. + */ + public void setOriginalIntesity(float intensity){ + originalIntensity = intensity; + bloomShader.begin(); + { + bloomShader.setUniformf("OriginalIntensity", intensity); + } + bloomShader.end(); + } + + /** + * Treshold for bright parts. everything under threshold is clamped to 0 + * + * @param threshold must be in range 0..1 + */ + public void setThreshold(float threshold){ + this.threshold = threshold; + tresholdShader.begin(); + { + tresholdShader.setUniformf("threshold", threshold, + 1f / (1 - threshold)); + } + tresholdShader.end(); + } + + private void setSize(int FBO_W, int FBO_H){ + w = FBO_W; + h = FBO_H; + blurShader.begin(); + blurShader.setUniformf("size", FBO_W, FBO_H); + blurShader.end(); + } + + /** + * Call this when application is exiting. + */ + public void dispose(){ + if(disposeFBO) + frameBuffer.dispose(); + + fullScreenQuad.dispose(); + + pingPongBuffer1.dispose(); + pingPongBuffer2.dispose(); + + blurShader.dispose(); + bloomShader.dispose(); + tresholdShader.dispose(); + + } + + private static Mesh createFullScreenQuad(){ + float[] verts = {-1, -1, 0, 0, 1, -1, 1, 0, 1, 1, 1, 1, -1, 1, 0, 1}; + Mesh tmpMesh = new Mesh(true, 4, 0, new VertexAttribute( + Usage.Position, 2, "a_position"), new VertexAttribute( + Usage.TextureCoordinates, 2, "a_texCoord0")); + + tmpMesh.setVertices(verts); + return tmpMesh; + + } + + private static Shader createShader(String vertexName, String fragmentName){ + String vertexShader = Core.files.internal("bloomshaders/" + vertexName + ".vertex.glsl").readString(); + String fragmentShader = Core.files.internal("bloomshaders/" + fragmentName + ".fragment.glsl").readString(); + return new Shader(vertexShader, fragmentShader); + } + +} diff --git a/core/src/io/anuke/mindustry/graphics/Pixelator.java b/core/src/io/anuke/mindustry/graphics/Pixelator.java index e8a9e0d2f3..8cc252eebd 100644 --- a/core/src/io/anuke/mindustry/graphics/Pixelator.java +++ b/core/src/io/anuke/mindustry/graphics/Pixelator.java @@ -55,7 +55,7 @@ public class Pixelator implements Disposable{ Draw.rect(Draw.wrap(buffer.getTexture()), Core.camera.position.x, Core.camera.position.y, Core.camera.width, -Core.camera.height); Draw.blend(); - renderer.drawAndInterpolate(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName); + renderer.draw(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName); Core.camera.position.set(px, py); Core.settings.put("animatedwater", hadWater); From 6fa9abd896d7b2a4dccf32e58c503b47ad0b6d91 Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 28 Jun 2019 23:04:43 -0400 Subject: [PATCH 2/5] discard --- core/assets/maps/groundZero.msav | Bin 8290 -> 8272 bytes core/assets/maps/saltFlats.msav | Bin 16470 -> 16490 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/core/assets/maps/groundZero.msav b/core/assets/maps/groundZero.msav index 04841aa794ab17df7ea166a2795e039f747c2d79..492d8ff61e3eca09f2ce8ee37d0d4dbed05135fb 100644 GIT binary patch literal 8272 zcmV-WAg|weob7s9b6iK3Uf!x(`-X)8Nst7%i4>P2fdaTnq(m+3MN4ZV)e-{_Tl~RFH>65Ca^1Gep<6(OzQo*vyPnzQhFX`n= z%PKR_uPs$sxpL*&+VYL-Yu8qn8!GHJH9>K!-y3yXA4mP3$~H&C?OtDV%?N`*aQ4C1 z!^gYP%~o@3E9zeyY&1LV?$*u5Hy1tpdbHK<-n{ZV##bD{>b?Yx<=;i{yEv>m4r`WR zv)3Q?qrt$E+_*T{ZEk?XQLkK#HaDY<;mzxI+N9rX54WxA%bc`&ai{r}e%hJKm#%#C zskZR2IUK0e*WakzPIDKQ&5gQWbbFJo%J%xvR@7C&ips2y+MSk4)ipS2w}#s)-MDg9 zWw)dD*7nf6Wcs6yw*8f_u?IJY{ZS;+C*8r#|GyniZ-=)L->6WmCZ;a6qQOSLy*q68 zy4s^vZ6~YLl*(@Oc6Xz`3OiA=r3yRIu({P~4hAZ})o<=@w}+7m2hDCvg&VzQN9Bj@ zVYAyF?WmlDh5#AOwz;cvgLbFAp?Mc|;3kIDh)>ct*GAYbb60f zVX&+HpuXE{sp`7SsBiRkb|v<7>^|-{wV)^n=2W`b?rWaS{_tUaP5)28s|mP~fLBz( z!pja!U>goxbs$-)Ix(vc6WE3WR~<;!jf9v+LQEqerjZcSaAFz{ z6WE3WR~<;!D+w_x2{9`PF)Il%D^ASH!vwbBz*PsX#o)3?A-L+mh67jh&Kl4`4MMAg zETa|2bH(voaXeQXPtn>!4%Z;GO31QoIPML{z2Uew9QOvfHy%1%gU~7=%W~CmUv=D9 z9rsnoeU;o-A39uv&?+Iza?NpHbKKV)_ch0TjojBBI$VR$DxozU%aT}C7HN4U{@aNE zuEu}Y;=fnozgOeG*W$m|`Q^^b#+bAsYl}mIy-G^Y`(qKZT5Q`U#Q%6ZwR2U)7;uIIFrZnmYphW z_c}epC!>>{&Uu=mGt^IArc}DU5ov}SQ5~<@jZS-WbD*n{W;M_XO7=2v2<~|yGmxHS zMhX_E!Pclttd>r&GfZ-<@3yr%A)eX6c7L=!P_>Dq;kqspQN1Nuy66z9Vy10$Mgy36 zu-)wFE(U)L;|k#VdT-QeNuuBB4JZw8Ns@l2zS-&NUS~G(Qg1c2^`uiuA{+kes{eXL zmBqmI-DWrHXdg_zHN3Y~?`_R{^SNAAMZ7_d%I!w$>$)S*wJ6edYbp{rwOvM?VLN`Q#pSI&*oit~>F|qor>o{%u|xe5 zT5ZL+?oA?He6`b~;&EnmqyM!s3`~6J562HwSgRFTZGRh5L@l7W_PGtfc#MVG(5Fr z_RFxFPQ1kYDt40$#jN)l)0E?_GbA&~x0s?BZv=F9upKoGNxUM$@o3nRfGEl{#^}xH zN(@!yuik*(=ejAApy}x?(5^U+63Civ?KYAK9h5pC^}g!Z&TCg1M~^jjnhPT;|M{r9 zfj+IK`=-`b1+Q}J&7qd}SXE*mCOV)4a9ul>mR8s2i`ea_ z?NXAcQGZ?ULIO44(%C)g>(FU?q?P_~EAd(y^t3FKmeh3O#Rh?O!G6@lo>$vDx-%d0 zeJWvg$z2^)+wDf99Ubx;F`}fifnI(5Vsb-FiUL@Vz8dxox8jCmsIk$s*Y0>j$bPyi zwj?iTcvE`oCibEXM0d2&iAK7ljhmW{D(J0{Dl9p4)a~?|;_1asQ*Yq5USvy6IXGT! zx;5(Q#(x9bpnXxZ-Pl&)cC+1y4<6Z0v%4WhyV>8+l#^zE2UV)s8TVAUIT~mZeJI)O zNG*T7(Lqrk?COB&>mZTb*ODV+rPXhaw)8nd*7jIc2fOWld&u{i_NDHyx8n%R`H1h8 zt!-WY_&U{(#!**KaTClz&2iMZYtl&|OgeHL?75AYa$hItwswb}Ze+B=x}~biX2W#V zmR?EIy_bj0QDU^W6Ky3%r#{!F(}y^{eeGR?(J|f9&gn2UeM*yjV-YEpSH)&W8v=ZE z+M-0AbYp3{P*RfaNJK@MugXb*TDgQURZ0j7RcITzaL{RPskC-F?KrlcWmQdOuvOX_ zutmCBo2^kvTH~anOV^!3l*V6GK16i}Jx?Pj) zRC&9N`t;h?E6VC9(^aIPO5Qb2S)Y8P{_sN`NKxE!-R;Qr1iQ!bX217~sC!pO#XzSb zNt=59L`$mlN9}HJGzsQ^pyxO@;2x#@ zxkc)7+usGuy-R73Q52}m_1^M#A)sHmxoKLhLTP34!;BT$B}JT~H4c&AYXW6Bw?z9@ zxcks$o^$8ve@?%H0-X`)74j<(s`?%Qesx01*AF@$73i!$ceviN?(K zjv3F|;3>Xd&d-qvVGu8xi+Fa8tyg zUOpn-@VDaD1sBJ@2L*&+wLWKjPTbG*uyr};O;ZdU94W@=oX=mNq32K|Bm`yB<|mHMd!IQPTCL5+0Su?TgTrqmz?sm z0OtNJExANpT=n~vKU(&!@hk4uLGnYVFfY)*rB==cbBxP>!?|;ep1-2BSw{CBT#5W< zI9ekY%=l~CYL1!E;SMmX{Ed&BBIhdCRw9=gSMElO9G&6Z3Nr(&@t5?WdFqm3r2Kc{ zT-N(<8u~7vnOw<_2Q2XdAvo}lY2sg!Jw}gE@w`v>tw1NKxGRtt{YL`TIYSZWlJr@f ziOj1b`G+P}BS{&S72@Nx5Qmfj;)lwn_2VWLL{jY^#fEr|jt)8(y2`M3X)Y4Ke-wM? z9Zo|ohH)0GR%QT#PRWd%PP#_g%l^q5Fuuz8VXQ^}6BqLAfoC%xF} z;4__3n{oK%eeJV8`)g#M=Q@s(>3d|V7*U}~Uz_oa0@;VuKJ_g27_E_ghNBhYUiH^e z7D1t|g!e?&igxq z1)e5@f|=%7ykK(JPTYg!$o_)QDkLi`I%h0o87wl%@&!a zL5@q9C4+;66rmK$og7P_#9gEM|}1&?Mcp*36H(I5(vr49e!ZbB3Wg~e$L->G5eF6iIk`P@33)9 zPro^ee1Qy#OmlGu`Y4W2xWzIH)f91QG6;3^Y!4TjRh}E=Sd}SvjT_)}Nt?x3E@=&Y zFAyG3{7KgM%cJDc<0_-ku}Yb6{%x#6vt^IsZ7ldj*nK>3obQ_%)=Lg^uDja3L9eUP zLIL+Eph6CUE-7vgtag)rTJ=}-qFKzhGS@PAg-YJww8}oyLVw!p^febB*U2DHpXC02 zuY4mqRt|cNx?oeLsKFwgRg^TW;`YO1`YLsCXSFK=HOOF9x!w_Tzcu{qVxpLHg=ON3 zU$;W~-(}{ZhO)OL@sZKvlA?8;LMv2AqQW%bm@&^81Aj3WsKJUqV~(YFz^nqgCg~J8 zKa);$HVx@P(tXX*v_Bd*HkJfuMA7uSfFyxwKP<^ll)WFSlGyP}`NTgG;=J*ur&S9m z{P%=JJn>2Vt;6GdXdhznuqP!GZ#$+TO%OXZKrG($B>q-p?dX8P6~-Q_cYzZH05iWPI$4$uSizP zY@WxjBQxIim5f(z_e~Kh7=Lk`J--V4pxYtmxPP{r;`pT~tzffvLgy5NE|=JI+X2Y; z0DL_7KCoQAPpbQE(uWExXz>ZsHl#&5A?i7*_(wLg+H&XWZ@gvSyQrwpl8QEv@@YD< z`!#= zIp(BZBbV#S)-6}T%A6t1$#)r_-!*?m@T?g%W68)oOiw5dqVsrU(u29Es3kfSOLtS* znp`F4X>UWni;oI!w>)XC7jRzz+!yk%jsIDGnFQyZ z>6e^lcjjgyQ}4vzUEJx(zl%gyYOXunFFpdJy|a~7?N*|uSk737`%Y4`b}?71?xNOx z`+SqR&VBCzzx%|wOTbIS#oymNewh0fX8@Nq{%rZVwh?Pv_PMz0_qPe{`K^KbDDXZv zz+IYLru_nc#z&;vGuQFEKwAYIh1`$*x%a4j%AfltXB;OTI{k}2=bzB__@eUxy?qbe zPstB-?~v|*vfmbQnD275!cpjV*dNUsU$dhNv@WE5PP*JabZ&p!0=>s9;OKC_`vtA* zas=k$ORKx61>J|f25}o;JHbUtbH6>hO54wp4&Uy!N$0Lp16srVGO6jW+;uXmgyPQ< z&c}D+--xW!;#p%WyK;c)Uy*Z$ zHb8_f43mx1T}cT8ON#V z$HcjBpU6JDlu_nrxX-A|b$RLnI{dcrOWG<&E`aWlOP09f+z+gy$GI8KE&99g2D!NG zc#1l^T?JjAaRi;ukS?GGw>g@pH(e0OUG6zLchNKObIuuh-3y#ipfy|uIb$L|ihrl& zEa^TGmk43*IYz}r+Qt2*GRIY(Wpsn{5zC^-P&Fw?pytM z*B$I|kNzrk`KLY(bpK4CWBcGh_X=_UoOBN7emBzNXxGOb=ANfHy08!K2yp>pVS#?& zaIOPLc4?mPP6#1pihj)eA?-Y5Z2cieN8BTq*M3UqEF<8*aK>56_|N3$aQ}(Y&XMyE z)cG8}eU>);Q_gjx&Li9}R$Z^>I?%VMpPTD`DVE!3uIm8aBhJmZL!BMJqMsvNUdu3Z zIT*un7TM;y^QN}Uq=WcQK#Mawta!rkA)#L*;{!C47sPVLgZ>n)k zP427lZSD1Hd<>K`YVx5P-%4OK{hS(S)nrkPQ)+TpjTbO_9M45Hxr$i_)VQD~2h}(L zJ_O|rHA$)QlA5dlp9Z&rZY|U#uf`?N=hWm7CM|Sp^@BuUh&~Kp7?| zk3numO)6@f2OL1F6M!}U%UDGK`cRFZQUCAX`MkBi=5qv;Ej6iuURgdz6ZjW_KMjn5&j5b|@L4r^UX3@P zkETDT#_L#nq$cOpxQSJ41K)%%eGd9{AJ#N&z)e{A$KbpKiA~t&M^@W2pf3WG1x8!) zQwcb+RR;Q}Fq*@t_JQ}M;ShYr?!xQfuz=C`q^S|48M=dKUhR|R^1vc^UV^L~Jp34Ry9`VQkx0x-u;)o1V`AqhFw5BM zBzzbaSV_PQ_=bkx27V8$v&7FVtfTk?3(tu0wS9C0wj{RE>;)FKiJ{|4{V#Q0g>Oz3 z^>FUL2J%SBBQ1}tJaY0V$fGEal03@t5K)%rGu+rrk| z6N8IYx3Gf>_D>>}O|Yp1{GnwM;?B1`!M@8M z8EHf#?~6N1b$%JI_r%&-gzjS|4TN?Y$dlq)TGnJ0vAhR)Miwjke%mrJLuJW?y5EV1 z0t}rH)5*{&X?Eh&8rZ^jU$!+SI3cEpv#Ua;+Cy+mGO#G~5`yJJX^8Z;%Ft0szalh) z60eY&j$&d(GK1vaTbNaXbkjUsNYESvd=oTAjiaEvD6QT8G&wX>0emAwSwzVbdZX1m zG`wL8blOH=8LpKArWBVXnU#Z-DmH~u(zbm{p@f;}D?|E%?6}OT$&fmYCLv|@$s?W4 z*m%dP#SU4}7bWzh)iGLSE&VhwY%@|)bj08C&@Ge_sge5BGG4YREBDmg40JmvZH9tb zB&FA}3ey%A5F^vJ`N=}JS{dEQ8lssB{cnRz^BZRo#zl z4r?@N=P-!Ta4OTP1@hw(M?8(qJ%sKqC4G^0B*|T&e5R09Hof1RB@3}Mmckug+^k}l!M5|1-L{8IxJvy zfla)J(8QWXaSHpAK}?auJi^2r0d%IVTUm49a{(C-9wPnKDOhhAaW@4xEsdy{2yL5o zA!+UO+%+uF@JWMDCH5$V{3DdlV}Gy09}Hy*x>e))sLWHTKhtYd|9eV_J4riQv(a(Q z&V4;GOR4{$w?WmXRs1jT8C-@*ZeUMpSVRB?YjMfd#_;Hu;fy!2Z&j=21qp&Zat?sD z0NxeUJkrkYrJaR|cy_yv8F_4iH?_K@xPKc(5?dx}-Nd4C60&ULn^tn`n1KG`cU^=3Jr7^^m9oX)Tho{P_N# z+WpkOz=)1LNgjqp02aG)NBr!`ChjKh{!YR@Qf}Fc4@@(pGjx|dh*-!?d(JQy7*7e@ z7nWHiRG2U2bA!B=QS0v6Vu+`%t8psKV<LDV+1LH%>v-jmAuEByT|0EoHccG547SHHRKY@~kxLFDmsCLz z;3V+@%w0yOAKK(mMuV?F@D2!S;E+Mu$fL`CA43%^#|%xOCDa=J97B1jjqy+oDMU;B z7(+R^Uz8{@L)&usA-**Wdeg(UvE;06JgffDhlw+2&Gu+zurjxISFNdM?9dj5QWy%n zA=#-W3RlV|uvI)~(NRLE?BhCGsw_?h^H9DD`VCx+WpME@4>Q&*{hU3UrX_0{FO{w~ z4TGDxrcZerNBj`@9P}~xcW^EVZU6Ylc0li1gP5zthq#I}r8tAjB#!Db`zbDTv$)cj zfhU-DIEPGAfFLvRo;2q1&_k@kble#vq(zjntUa=9z#*sYK`IsZ9%4of{{r>!C>EeSvP3HN;x}v-`e&eteVB8mlpj1vJQjt zIF$Stm<3CdMo#@CK4}Ri1jZUK~uCAd&A;W z=sL^R$ZuG4DRi3VT2ynsZBJ<*q3oQs{k0h~$K?;P1py|`qI1c`S@|-=;EBoak6W`^? z^DRg;GzIJYK_Ve2l!L&I-hnJbU$n={jJ-}v+qo%hUuZGrD1Rru>%3d~jIyg1vmLVeT>TSJrOE OquxeO`2PUL8~+3VWRjZz literal 8290 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 Uu0Or!^RndQ>XO?310l&wdV~~Q?*IS* literal 16470 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 Bhv5JK From 22656a40b2633209e181b5d261a3fc9a0d0613ab Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 28 Jun 2019 23:31:34 -0400 Subject: [PATCH 3/5] Bloom fix --- core/assets/bloomshaders/alpha_threshold.fragment.glsl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/assets/bloomshaders/alpha_threshold.fragment.glsl b/core/assets/bloomshaders/alpha_threshold.fragment.glsl index 698e4e712f..d85c0b1170 100644 --- a/core/assets/bloomshaders/alpha_threshold.fragment.glsl +++ b/core/assets/bloomshaders/alpha_threshold.fragment.glsl @@ -10,6 +10,12 @@ uniform sampler2D u_texture0; uniform vec2 threshold; varying MED vec2 v_texCoords; void main() -{ - gl_FragColor = (texture2D(u_texture0, v_texCoords) - vec4(threshold.r)) * threshold.g; +{ + vec4 color = texture2D(u_texture0, v_texCoords); + if(color.r + color.g + color.b > 0.5 * 3.0){ + gl_FragColor = color; + }else{ + gl_FragColor = vec4(0.0); + } + //gl_FragColor = (texture2D(u_texture0, v_texCoords) - vec4(threshold.r)) * threshold.g; } \ No newline at end of file From 89a4e6506fa30b5ee3ff7804d21b0123cdc85ef0 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sat, 29 Jun 2019 10:40:52 -0400 Subject: [PATCH 4/5] Bloom option --- core/assets/bundles/bundle.properties | 2 + .../src/io/anuke/mindustry/core/Renderer.java | 51 ++++++++++++++++--- .../ui/dialogs/SettingsMenuDialog.java | 1 + 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index a61e7d4ec5..c91c5a877a 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -341,6 +341,7 @@ error.alreadyconnected = Already connected. error.mapnotfound = Map file not found! error.io = Network I/O error. error.any = Unknown network error. +error.bloom = Failed to initialize bloom.\nYour device may not support it. zone.groundZero.name = Ground Zero zone.desertWastes.name = Desert Wastes @@ -485,6 +486,7 @@ setting.mutesound.name = Mute Sound setting.crashreport.name = Send Anonymous Crash Reports setting.chatopacity.name = Chat Opacity setting.playerchat.name = Display In-Game Chat +setting.bloom.name = Bloom keybind.title = Rebind Keys category.general.name = General category.view.name = View diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index 75398e713e..b11d1bfc22 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -44,7 +44,9 @@ public class Renderer implements ApplicationListener{ public Renderer(){ camera = new Camera(); - bloom = new Bloom(true); + if(settings.getBool("bloom")){ + setupBloom(); + } Lines.setCircleVertices(20); Shaders.init(); @@ -132,14 +134,45 @@ public class Renderer implements ApplicationListener{ minimap.dispose(); shieldBuffer.dispose(); blocks.dispose(); - bloom.dispose(); + if(bloom != null){ + bloom.dispose(); + bloom = null; + } Events.fire(new DisposeEvent()); } @Override public void resize(int width, int height){ - bloom.dispose(); - bloom = new Bloom(true); + if(settings.getBool("bloom")){ + setupBloom(); + } + } + + void setupBloom(){ + try{ + if(bloom != null){ + bloom.dispose(); + } + bloom = new Bloom(true); + }catch(Exception e){ + e.printStackTrace(); + settings.put("bloom", false); + settings.save(); + ui.showError("$error.bloom"); + } + } + + public void toggleBloom(boolean enabled){ + if(enabled){ + if(bloom == null){ + setupBloom(); + } + }else{ + if(bloom != null){ + bloom.dispose(); + bloom = null; + } + } } void updateShake(float scale){ @@ -206,14 +239,18 @@ public class Renderer implements ApplicationListener{ drawAllTeams(true); Draw.flush(); - bloom.setClearColor(0f, 0f, 0f, 0f); - bloom.capture(); + if(bloom != null && !pixelator.enabled()){ + bloom.setClearColor(0f, 0f, 0f, 0f); + bloom.capture(); + } draw(bulletGroup); draw(effectGroup); Draw.flush(); - bloom.render(); + if(bloom != null && !pixelator.enabled()){ + bloom.render(); + } overlays.drawBottom(); draw(playerGroup, p -> true, Player::drawBuildRequests); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java index e00133817f..60567e7730 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java @@ -218,6 +218,7 @@ public class SettingsMenuDialog extends SettingsDialog{ graphics.checkPref("indicators", true); graphics.checkPref("animatedwater", false); graphics.checkPref("animatedshields", !mobile); + graphics.checkPref("bloom", false, val -> renderer.toggleBloom(val)); graphics.checkPref("lasers", true); graphics.checkPref("pixelate", false); From 5c10a2662da6649e71419d458ad0c25da18d481d Mon Sep 17 00:00:00 2001 From: Anuken Date: Sat, 27 Jul 2019 16:12:51 -0400 Subject: [PATCH 5/5] Moved clear color --- core/src/io/anuke/mindustry/core/Renderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index b11d1bfc22..ccf047e2cf 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -154,6 +154,7 @@ public class Renderer implements ApplicationListener{ bloom.dispose(); } bloom = new Bloom(true); + bloom.setClearColor(0f, 0f, 0f, 0f); }catch(Exception e){ e.printStackTrace(); settings.put("bloom", false); @@ -240,7 +241,6 @@ public class Renderer implements ApplicationListener{ Draw.flush(); if(bloom != null && !pixelator.enabled()){ - bloom.setClearColor(0f, 0f, 0f, 0f); bloom.capture(); }